diff --git a/doom/DMUtils/PLAYPAL.lmp b/doom/DMUtils/PLAYPAL.lmp new file mode 100644 index 0000000..99daafa Binary files /dev/null and b/doom/DMUtils/PLAYPAL.lmp differ diff --git a/doom/DMUtils/colormap.lmp b/doom/DMUtils/colormap.lmp new file mode 100644 index 0000000..74b0693 Binary files /dev/null and b/doom/DMUtils/colormap.lmp differ diff --git a/doom/DMUtils/colors12.lmp b/doom/DMUtils/colors12.lmp new file mode 100644 index 0000000..e1ca5b5 Binary files /dev/null and b/doom/DMUtils/colors12.lmp differ diff --git a/doom/DMUtils/colors15.lmp b/doom/DMUtils/colors15.lmp new file mode 100644 index 0000000..a11721a Binary files /dev/null and b/doom/DMUtils/colors15.lmp differ diff --git a/doom/DMUtils/playpal1.lmp b/doom/DMUtils/playpal1.lmp new file mode 100644 index 0000000..50268f1 Binary files /dev/null and b/doom/DMUtils/playpal1.lmp differ diff --git a/doom/DMUtils/src/CmdLib.java b/doom/DMUtils/src/CmdLib.java new file mode 100644 index 0000000..a612121 --- /dev/null +++ b/doom/DMUtils/src/CmdLib.java @@ -0,0 +1,422 @@ +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +public class CmdLib { + + /* + ================ + = + = filelength + = + ================ + */ + + public static long filelength (FileInputStream handle) + { + try { + return handle.getChannel().size(); + } catch (Exception e){ + System.err.print("Error fstating"); + System.exit(1); + } + + return -1; + } + + public static long tell (FileInputStream handle) throws IOException + { + return handle.getChannel().position(); + } + + /* + char *getcwd (File path, int length) + { + return path.getgetwd(path); + } + */ + + /* + ============================================================================= + + MISC FUNCTIONS + + ============================================================================= + */ + + /* + ================= + = + = Error + = + = For abnormal program terminations + = + ================= + */ + + public static void Error (String error, String... params) + { + + System.err.printf(error,params); + System.err.printf ("\n"); + System.exit (1); + } + + + /* + ================= + = + = CheckParm + = + = Checks for the given parameter in the program's command line arguments + = + = Returns the argument number (1 to argc-1) or 0 if not present + = + ================= + */ + + public static int CheckParm (String check,String[] myargv) + { + int i; + char parm; + + for (i = 1;i=0) + { + if (path.charAt(src) == '.') + return path; // it has an extension + src--; + } + + return path.concat(extension); + } + + public static String StripFilename (String path) + { + File tmp=new File(path); + + return tmp.getName(); + } + + /** Return the filename without extension, and stripped + * of the path. + * + * @param s + * @return + */ + + public static final String StripExtension(String s) { + + String separator = System.getProperty("file.separator"); + String filename; + + // Remove the path upto the filename. + int lastSeparatorIndex = s.lastIndexOf(separator); + if (lastSeparatorIndex == -1) { + filename = s; + } else { + filename = s.substring(lastSeparatorIndex + 1); + } + + // Remove the extension. + int extensionIndex = filename.lastIndexOf("."); + if (extensionIndex == -1) + return filename; + + return filename.substring(0, extensionIndex); + } + + + /** + * This method is supposed to return the "name" part of a filename. It was + * intended to return length-limited (max 8 chars) strings to use as lump + * indicators. There's normally no need to enforce this behavior, as there's + * nothing preventing the engine from INTERNALLY using lump names with >8 + * chars. However, just to be sure... + * + * @param path + * @param limit Set to any value >0 to enforce a length limit + * @param whole keep extension if set to true + * @return + */ + + public static final String ExtractFileBase(String path, int limit, boolean whole) { + + if (path==null) return path; + + int src = path.length() - 1; + + String separator = System.getProperty("file.separator"); + src = path.lastIndexOf(separator)+1; + + if (src < 0) // No separator + src = 0; + + int len = path.lastIndexOf('.'); + if (whole || len<0 ) len=path.length()-src; // No extension. + else len-= src; + + // copy UP to the specific number of characters, or all + if (limit > 0) len = Math.min(limit, len); + + return path.substring(src, src + len); + } + + public static long ParseNum (String str) + { + if (str.charAt(0) == '$') + return Integer.parseInt(str.substring(1), 16); + if (str.charAt(0) == '0' && str.charAt(1) == 'x') + return Integer.parseInt(str.substring(2), 16); + return Integer.parseInt(str); + } + + + public static int GetKey () throws IOException + { + + return 0;// System.in..read()&0xff; + } + + + /* + ============================================================================ + + BYTE ORDER FUNCTIONS + + ============================================================================ + */ + +// #ifdef __BIG_ENDIAN__ + + public static short LittleShort (short l) + { + byte b1,b2; + + b1 = (byte) (l&0xFF); + b2 = (byte) (l>>8); + + return (short) ((b1<<8)|(b2&0xFF)); + } + + public static short BigShort (short l) + { + return l; + } + + + public static long LittleLong (long l) + { + byte b1,b2,b3,b4; + + b1 = (byte) (l&255); + b2 = (byte) ((l>>8)&255); + b3 = (byte) ((l>>16)&255); + b4 = (byte) ((l>>24)&255); + + return ((long)b1<<24) + ((long)b2<<16) + ((long)b3<<8) + b4; + } + + public static long BigLong (long l) + { + return l; + } + + + /* + + + short BigShort (short l) + { + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; + } + + short LittleShort (short l) + { + return l; + } + + + long BigLong (long l) + { + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((long)b1<<24) + ((long)b2<<16) + ((long)b3<<8) + b4; + } + + public static long LittleLong (long l) + { + return l; + } */ + + +} \ No newline at end of file diff --git a/doom/DMUtils/src/dcolors.java b/doom/DMUtils/src/dcolors.java new file mode 100644 index 0000000..eb8dfc3 --- /dev/null +++ b/doom/DMUtils/src/dcolors.java @@ -0,0 +1,393 @@ +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class dcolors { + public static final String VERSION= "1.1"; + + public final static int playpal[] = { + 0x00,0x00,0x00,0x1F,0x17,0x0B,0x17,0x0F,0x07,0x4B,0x4B,0x4B,0xFF,0xFF,0xFF,0x1B, + 0x1B,0x1B,0x13,0x13,0x13,0x0B,0x0B,0x0B,0x07,0x07,0x07,0x2F,0x37,0x1F,0x23,0x2B, + 0x0F,0x17,0x1F,0x07,0x0F,0x17,0x00,0x4F,0x3B,0x2B,0x47,0x33,0x23,0x3F,0x2B,0x1B, + 0xFF,0xB7,0xB7,0xF7,0xAB,0xAB,0xF3,0xA3,0xA3,0xEB,0x97,0x97,0xE7,0x8F,0x8F,0xDF, + 0x87,0x87,0xDB,0x7B,0x7B,0xD3,0x73,0x73,0xCB,0x6B,0x6B,0xC7,0x63,0x63,0xBF,0x5B, + 0x5B,0xBB,0x57,0x57,0xB3,0x4F,0x4F,0xAF,0x47,0x47,0xA7,0x3F,0x3F,0xA3,0x3B,0x3B, + 0x9B,0x33,0x33,0x97,0x2F,0x2F,0x8F,0x2B,0x2B,0x8B,0x23,0x23,0x83,0x1F,0x1F,0x7F, + 0x1B,0x1B,0x77,0x17,0x17,0x73,0x13,0x13,0x6B,0x0F,0x0F,0x67,0x0B,0x0B,0x5F,0x07, + 0x07,0x5B,0x07,0x07,0x53,0x07,0x07,0x4F,0x00,0x00,0x47,0x00,0x00,0x43,0x00,0x00, + 0xFF,0xEB,0xDF,0xFF,0xE3,0xD3,0xFF,0xDB,0xC7,0xFF,0xD3,0xBB,0xFF,0xCF,0xB3,0xFF, + 0xC7,0xA7,0xFF,0xBF,0x9B,0xFF,0xBB,0x93,0xFF,0xB3,0x83,0xF7,0xAB,0x7B,0xEF,0xA3, + 0x73,0xE7,0x9B,0x6B,0xDF,0x93,0x63,0xD7,0x8B,0x5B,0xCF,0x83,0x53,0xCB,0x7F,0x4F, + 0xBF,0x7B,0x4B,0xB3,0x73,0x47,0xAB,0x6F,0x43,0xA3,0x6B,0x3F,0x9B,0x63,0x3B,0x8F, + 0x5F,0x37,0x87,0x57,0x33,0x7F,0x53,0x2F,0x77,0x4F,0x2B,0x6B,0x47,0x27,0x5F,0x43, + 0x23,0x53,0x3F,0x1F,0x4B,0x37,0x1B,0x3F,0x2F,0x17,0x33,0x2B,0x13,0x2B,0x23,0x0F, + 0xEF,0xEF,0xEF,0xE7,0xE7,0xE7,0xDF,0xDF,0xDF,0xDB,0xDB,0xDB,0xD3,0xD3,0xD3,0xCB, + 0xCB,0xCB,0xC7,0xC7,0xC7,0xBF,0xBF,0xBF,0xB7,0xB7,0xB7,0xB3,0xB3,0xB3,0xAB,0xAB, + 0xAB,0xA7,0xA7,0xA7,0x9F,0x9F,0x9F,0x97,0x97,0x97,0x93,0x93,0x93,0x8B,0x8B,0x8B, + 0x83,0x83,0x83,0x7F,0x7F,0x7F,0x77,0x77,0x77,0x6F,0x6F,0x6F,0x6B,0x6B,0x6B,0x63, + 0x63,0x63,0x5B,0x5B,0x5B,0x57,0x57,0x57,0x4F,0x4F,0x4F,0x47,0x47,0x47,0x43,0x43, + 0x43,0x3B,0x3B,0x3B,0x37,0x37,0x37,0x2F,0x2F,0x2F,0x27,0x27,0x27,0x23,0x23,0x23, + 0x77,0xFF,0x6F,0x6F,0xEF,0x67,0x67,0xDF,0x5F,0x5F,0xCF,0x57,0x5B,0xBF,0x4F,0x53, + 0xAF,0x47,0x4B,0x9F,0x3F,0x43,0x93,0x37,0x3F,0x83,0x2F,0x37,0x73,0x2B,0x2F,0x63, + 0x23,0x27,0x53,0x1B,0x1F,0x43,0x17,0x17,0x33,0x0F,0x13,0x23,0x0B,0x0B,0x17,0x07, + 0xBF,0xA7,0x8F,0xB7,0x9F,0x87,0xAF,0x97,0x7F,0xA7,0x8F,0x77,0x9F,0x87,0x6F,0x9B, + 0x7F,0x6B,0x93,0x7B,0x63,0x8B,0x73,0x5B,0x83,0x6B,0x57,0x7B,0x63,0x4F,0x77,0x5F, + 0x4B,0x6F,0x57,0x43,0x67,0x53,0x3F,0x5F,0x4B,0x37,0x57,0x43,0x33,0x53,0x3F,0x2F, + 0x9F,0x83,0x63,0x8F,0x77,0x53,0x83,0x6B,0x4B,0x77,0x5F,0x3F,0x67,0x53,0x33,0x5B, + 0x47,0x2B,0x4F,0x3B,0x23,0x43,0x33,0x1B,0x7B,0x7F,0x63,0x6F,0x73,0x57,0x67,0x6B, + 0x4F,0x5B,0x63,0x47,0x53,0x57,0x3B,0x47,0x4F,0x33,0x3F,0x47,0x2B,0x37,0x3F,0x27, + 0xFF,0xFF,0x73,0xEB,0xDB,0x57,0xD7,0xBB,0x43,0xC3,0x9B,0x2F,0xAF,0x7B,0x1F,0x9B, + 0x5B,0x13,0x87,0x43,0x07,0x73,0x2B,0x00,0xFF,0xFF,0xFF,0xFF,0xDB,0xDB,0xFF,0xBB, + 0xBB,0xFF,0x9B,0x9B,0xFF,0x7B,0x7B,0xFF,0x5F,0x5F,0xFF,0x3F,0x3F,0xFF,0x1F,0x1F, + 0xFF,0x00,0x00,0xEF,0x00,0x00,0xE3,0x00,0x00,0xD7,0x00,0x00,0xCB,0x00,0x00,0xBF, + 0x00,0x00,0xB3,0x00,0x00,0xA7,0x00,0x00,0x9B,0x00,0x00,0x8B,0x00,0x00,0x7F,0x00, + 0x00,0x73,0x00,0x00,0x67,0x00,0x00,0x5B,0x00,0x00,0x4F,0x00,0x00,0x43,0x00,0x00, + 0xE7,0xE7,0xFF,0xC7,0xC7,0xFF,0xAB,0xAB,0xFF,0x8F,0x8F,0xFF,0x73,0x73,0xFF,0x53, + 0x53,0xFF,0x37,0x37,0xFF,0x1B,0x1B,0xFF,0x00,0x00,0xFF,0x00,0x00,0xE3,0x00,0x00, + 0xCB,0x00,0x00,0xB3,0x00,0x00,0x9B,0x00,0x00,0x83,0x00,0x00,0x6B,0x00,0x00,0x53, + 0xFF,0xFF,0xFF,0xFF,0xEB,0xDB,0xFF,0xD7,0xBB,0xFF,0xC7,0x9B,0xFF,0xB3,0x7B,0xFF, + 0xA3,0x5B,0xFF,0x8F,0x3B,0xFF,0x7F,0x1B,0xF3,0x73,0x17,0xEB,0x6F,0x0F,0xDF,0x67, + 0x0F,0xD7,0x5F,0x0B,0xCB,0x57,0x07,0xC3,0x4F,0x00,0xB7,0x47,0x00,0xAF,0x43,0x00, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xD7,0xFF,0xFF,0xB3,0xFF,0xFF,0x8F,0xFF,0xFF,0x6B,0xFF, + 0xFF,0x47,0xFF,0xFF,0x23,0xFF,0xFF,0x00,0xA7,0x3F,0x00,0x9F,0x37,0x00,0x93,0x2F, + 0x00,0x87,0x23,0x00,0x4F,0x3B,0x27,0x43,0x2F,0x1B,0x37,0x23,0x13,0x2F,0x1B,0x0B, + 0x00,0x00,0x53,0x00,0x00,0x47,0x00,0x00,0x3B,0x00,0x00,0x2F,0x00,0x00,0x23,0x00, + 0x00,0x17,0x00,0x00,0x0B,0x00,0x00,0x00,0xFF,0x9F,0x43,0xFF,0xE7,0x4B,0xFF,0x7B, + 0xFF,0xFF,0x00,0xFF,0xCF,0x00,0xCF,0x9F,0x00,0x9B,0x6F,0x00,0x6B,0xA7,0x6B,0x6B + }; + + /* + ============================================================================= + + DCOLORS + + by John Carmack + + ============================================================================= + */ + + public static final int NUMLIGHTS = 32; + public static final int GRAYCOLORMAP = 32; + + static private byte[] palette=new byte[768]; + + static private byte[][] lightpalette=new byte[NUMLIGHTS+2][256]; + static private short[][] color12s=new short[NUMLIGHTS+2][256]; + static private short[][] color15s=new short[NUMLIGHTS+2][256]; + + + /* + ===================== + = + = ColorShiftPalette + = + = at shift = 0, the colors are normal + = at shift = steps, the colors are all the given rgb + ===================== + */ + + static private void ColorShiftPalette (byte[] inpal, byte[] outpal + , int r, int g, int b, int shift, int steps) + { + int i; + int dr, dg, db; + int in_p, out_p; + + in_p = 0; + out_p = 0; + + for (i=0 ; i<256 ; i++) + { + dr = r - inpal[in_p+0]; + dg = g - inpal[in_p+1]; + db = b - inpal[in_p+2]; + + outpal[out_p+0] = (byte) (inpal[in_p+0] + dr*shift/steps); + outpal[out_p+1] = (byte) (inpal[in_p+1] + dg*shift/steps); + outpal[out_p+2] = (byte) (inpal[in_p+2] + db*shift/steps); + + in_p += 3; + out_p += 3; + } + } + + /* + =============== + = + = BestColor + = + =============== + */ + + static private byte BestColor (int r, int g, int b, byte[] palette, int rangel, int rangeh) + { + int i; + long dr, dg, db; + long bestdistortion, distortion; + int bestcolor; + int pal; + + // + // let any color go to 0 as a last resort + // + bestdistortion = ( (long)r*r + (long)g*g + (long)b*b )*2; + bestcolor = 0; + + pal = rangel*3; + for (i=rangel ; i<= rangeh ; i++) + { + dr = r - (int)(0xFF&palette[pal+0]); + dg = g - (int)(0xFF&palette[pal+1]); + db = b - (int)(0xFF&palette[pal+2]); + pal += 3; + distortion = dr*dr + dg*dg + db*db; + if (distortion < bestdistortion) + { + if (distortion==0) + return (byte) i; // perfect match + + bestdistortion = distortion; + bestcolor = i; + } + } + + return (byte) bestcolor; + } + + + /* + ===================== + = + = RF_BuildLights + = + = 0 is full palette + = NUMLIGHTS and NUMLIGHTS+1 are all black + = + ===================== + */ + + static private void RF_BuildLights () + { + int l,c; + int red,green,blue, ri, gi, bi; + int palsrc; + short color12,color15; + + for (l=0;l255?255:red+4))>>3; + ri = ri > 31 ? 31 : ri; + gi = (((green+4)>255?255:green+4))>>3; + gi = gi > 31 ? 31 : gi; + bi = (((blue+4)>255?255:blue+4))>>3; + bi = bi > 31 ? 31 : bi; + + // RGB555 for HiColor + return (short) ((ri<<10) + (gi<<5) + bi); + } + + public static final short getRGBA4444(int red,int green,int blue){ + int ri,gi,bi; + + ri = (((red+8)>255?255:red+8))>>4; + ri = ri > 15 ? 15 : ri; + gi = (((green+8)>255?255:green+8))>>4; + gi = gi > 15 ? 15 : gi; + bi = (((blue+8)>255?255:blue+8))>>4; + bi = bi > 15 ? 15 : bi; + + // RGBA 4-4-4-4 packed for NeXT + + return (short) ((ri<<12)+ (gi<<8) +(bi<<4)+15); + //return (short)(0xDEAD); + } + + /* + ===================== + = + = BuildSpecials + = + = Red and gray colormaps + = + ===================== + */ + + static private void BuildSpecials () + { + int c,gray; + float red, green, blue; + int palsrc; + + palsrc = 0; + for (c=0;c<256;c++) + { + red = (float) ((0xFF&palette[palsrc++]) / 256.0); + green = (float) ((0xFF&palette[palsrc++]) / 256.0); + blue = (float) ((0xFF&palette[palsrc++]) / 256.0); + + gray = (int) (255*(1.0-(red*0.299 + green*0.587 + blue*0.144))); + + color12s[GRAYCOLORMAP][c] = getRGBA4444(gray,gray,gray); + System.out.printf("%4x for %x\n",color12s[GRAYCOLORMAP][c],gray); + color15s[GRAYCOLORMAP][c] = getRGB555(gray,gray,gray); + + lightpalette[GRAYCOLORMAP][c] = BestColor(gray,gray,gray,palette,0,255); + } + + /* + memcpy (screen,lightpalette[GRAYCOLORMAP],256); + screen+=320; + memcpy (screen,lightpalette[GRAYCOLORMAP],256); + screen+=320; + memcpy (screen,lightpalette[GRAYCOLORMAP],256); + screen+=320; */ + } + + + /* + ==================== + = + = main + = + ==================== + */ + + public static void main (String[] argv) throws IOException + { + int i; + OutputStream handle; + InputStream palhandle; + byte []pic; + byte[] outpal=new byte[768]; + + System.out.printf ("\nDCOLORS %s by John Carmack, copyright (c) 1992 Id Software\n",VERSION); +/* + if (argv.length != 2) { + CmdLib.Error("dcolors picture.lbm"); + + } */ + // + // load lbm for base palette + // + //LoadLBM (argv[1],&pic, &palette); + //VGAMode (); + //SetPalette (palette); + //memcpy ( screen, pic, 64000); + //free (pic); // don't care whats on it + + // + // build palette shifts + // + palhandle=CmdLib.SafeOpenRead("PLAYPAL.lmp"); + CmdLib.SafeRead(palhandle,palette); + palhandle.close(); + + for (i=0;i + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different; for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read . \ No newline at end of file diff --git a/doom/src/automap/IAutoMap.java b/doom/src/automap/IAutoMap.java new file mode 100644 index 0000000..2b35c4b --- /dev/null +++ b/doom/src/automap/IAutoMap.java @@ -0,0 +1,52 @@ +package automap; + +import doom.SourceCode.AM_Map; +import static doom.SourceCode.AM_Map.AM_Responder; +import static doom.SourceCode.AM_Map.AM_Stop; +import doom.event_t; + +public interface IAutoMap { + + // Used by ST StatusBar stuff. + public final int AM_MSGHEADER = (('a' << 24) + ('m' << 16)); + public final int AM_MSGENTERED = (AM_MSGHEADER | ('e' << 8)); + public final int AM_MSGEXITED = (AM_MSGHEADER | ('x' << 8)); + + // Color ranges for automap. Actual colors are bit-depth dependent. + public final int REDRANGE = 16; + public final int BLUERANGE = 8; + public final int GREENRANGE = 16; + public final int GRAYSRANGE = 16; + public final int BROWNRANGE = 16; + public final int YELLOWRANGE = 1; + + public final int YOURRANGE = 0; + public final int WALLRANGE = REDRANGE; + public final int TSWALLRANGE = GRAYSRANGE; + public final int FDWALLRANGE = BROWNRANGE; + public final int CDWALLRANGE = YELLOWRANGE; + public final int THINGRANGE = GREENRANGE; + public final int SECRETWALLRANGE = WALLRANGE; + public final int GRIDRANGE = 0; + + // Called by main loop. + @AM_Map.C(AM_Responder) + public boolean Responder(event_t ev); + + // Called by main loop. + public void Ticker(); + + // Called by main loop, + // called instead of view drawer if automap active. + public void Drawer(); + + // Added to be informed of gamma changes - Good Sign 2017/04/05 + public void Repalette(); + + // Called to force the automap to quit + // if the level is completed while it is up. + @AM_Map.C(AM_Stop) + public void Stop(); + + public void Start(); +} \ No newline at end of file diff --git a/doom/src/automap/Map.java b/doom/src/automap/Map.java new file mode 100644 index 0000000..bfe5d27 --- /dev/null +++ b/doom/src/automap/Map.java @@ -0,0 +1,1590 @@ +package automap; + +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: Map.java,v 1.37 2012/09/24 22:36:28 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// +// $Log: Map.java,v $ +// Revision 1.37 2012/09/24 22:36:28 velktron +// Map get color +// +// Revision 1.36 2012/09/24 17:16:23 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.34.2.4 2012/09/24 16:58:06 velktron +// TrueColor, Generics. +// +// Revision 1.34.2.3 2012/09/20 14:06:43 velktron +// Generic automap +// +// Revision 1.34.2.2 2011/11/27 18:19:19 velktron +// Configurable colors, more parametrizable. +// +// Revision 1.34.2.1 2011/11/14 00:27:11 velktron +// A barely functional HiColor branch. Most stuff broken. DO NOT USE +// +// Revision 1.34 2011/11/03 18:11:14 velktron +// Fixed long-standing issue with 0-rot vector being reduced to pixels. Fixed +// broken map panning functionality after keymap change. +// +// Revision 1.33 2011/11/01 23:48:43 velktron +// Using FillRect +// +// Revision 1.32 2011/11/01 19:03:10 velktron +// Using screen number constants +// +// Revision 1.31 2011/10/23 18:10:32 velktron +// Generic compliance for DoomVideoInterface +// +// Revision 1.30 2011/10/07 16:08:23 velktron +// Now using g.Keys and line_t +// +// Revision 1.29 2011/09/29 13:25:09 velktron +// Eliminated "intermediate" AbstractAutoMap. Map implements IAutoMap directly. +// +// Revision 1.28 2011/07/28 16:35:03 velktron +// Well, we don't need to know that anymore. +// +// Revision 1.27 2011/06/18 23:16:34 velktron +// Added extreme scale safeguarding (e.g. for Europe.wad). +// +// Revision 1.26 2011/05/30 15:45:44 velktron +// AbstractAutoMap and IAutoMap +// +// Revision 1.25 2011/05/24 11:31:47 velktron +// Adapted to IDoomStatusBar +// +// Revision 1.24 2011/05/23 16:57:39 velktron +// Migrated to VideoScaleInfo. +// +// Revision 1.23 2011/05/17 16:50:02 velktron +// Switched to DoomStatus +// +// Revision 1.22 2011/05/10 10:39:18 velktron +// Semi-playable Techdemo v1.3 milestone +// +// Revision 1.21 2010/12/14 17:55:59 velktron +// Fixed weapon bobbing, added translucent column drawing, separated rendering +// commons. +// +// Revision 1.20 2010/12/12 19:06:18 velktron +// Tech Demo v1.1 release. +// +// Revision 1.19 2010/11/17 23:55:06 velktron +// Kind of playable/controllable. +// +// Revision 1.18 2010/11/12 13:37:25 velktron +// Rationalized the LUT system - now it's 100% procedurally generated. +// +// Revision 1.17 2010/10/01 16:47:51 velktron +// Fixed tab interception. +// +// Revision 1.16 2010/09/27 15:07:44 velktron +// meh +// +// Revision 1.15 2010/09/27 02:27:29 velktron +// BEASTLY update +// +// Revision 1.14 2010/09/23 07:31:11 velktron +// fuck +// +// Revision 1.13 2010/09/13 15:39:17 velktron +// Moving towards an unified gameplay approach... +// +// Revision 1.12 2010/09/08 21:09:01 velktron +// Better display "driver". +// +// Revision 1.11 2010/09/08 15:22:18 velktron +// x,y coords in some structs as value semantics. Possible speed increase? +// +// Revision 1.10 2010/09/06 16:02:59 velktron +// Implementation of palettes. +// +// Revision 1.9 2010/09/02 15:56:54 velktron +// Bulk of unified renderer copyediting done. +// +// Some changes like e.g. global separate limits class and instance methods for +// seg_t and node_t introduced. +// +// Revision 1.8 2010/09/01 15:53:42 velktron +// Graphics data loader implemented....still need to figure out how column +// caching works, though. +// +// Revision 1.7 2010/08/27 23:46:57 velktron +// Introduced Buffered renderer, which makes tapping directly into byte[] screen +// buffers mapped to BufferedImages possible. +// +// Revision 1.6 2010/08/26 16:43:42 velktron +// Automap functional, biatch. +// +// Revision 1.5 2010/08/25 00:50:59 velktron +// Some more work... +// +// Revision 1.4 2010/08/22 18:04:21 velktron +// Automap +// +// Revision 1.3 2010/08/19 23:14:49 velktron +// Automap +// +// Revision 1.2 2010/08/10 16:41:57 velktron +// Threw some work into map loading. +// +// Revision 1.1 2010/07/20 15:52:56 velktron +// LOTS of changes, Automap almost complete. Use of fixed_t inside methods +// severely limited. +// +// Revision 1.1 2010/06/30 08:58:51 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an +// idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, +// and there's still mixed C code in there. I suggest you load everything up in +// Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of +// how a few of the implemented stuff works. +// +// +// DESCRIPTION: the automap code +// +// ----------------------------------------------------------------------------- +import static data.Defines.MAPBLOCKUNITS; +import static data.Defines.PLAYERRADIUS; +import static data.Defines.pw_allmap; +import static data.Defines.pw_invisibility; +import static data.Limits.MAXINT; +import static data.Limits.MAXPLAYERS; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.Tables.toBAMIndex; +import doom.DoomMain; +import doom.SourceCode.AM_Map; +import static doom.SourceCode.AM_Map.AM_Responder; +import static doom.englsh.AMSTR_FOLLOWOFF; +import static doom.englsh.AMSTR_FOLLOWON; +import static doom.englsh.AMSTR_GRIDOFF; +import static doom.englsh.AMSTR_GRIDON; +import static doom.englsh.AMSTR_MARKEDSPOT; +import static doom.englsh.AMSTR_MARKSCLEARED; +import doom.event_t; +import doom.evtype_t; +import doom.player_t; +import g.Signals.ScanCode; +import static g.Signals.ScanCode.SC_0; +import static g.Signals.ScanCode.SC_C; +import static g.Signals.ScanCode.SC_DOWN; +import static g.Signals.ScanCode.SC_EQUALS; +import static g.Signals.ScanCode.SC_F; +import static g.Signals.ScanCode.SC_G; +import static g.Signals.ScanCode.SC_LEFT; +import static g.Signals.ScanCode.SC_M; +import static g.Signals.ScanCode.SC_MINUS; +import static g.Signals.ScanCode.SC_RIGHT; +import static g.Signals.ScanCode.SC_TAB; +import static g.Signals.ScanCode.SC_UP; +import java.awt.Rectangle; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import m.cheatseq_t; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import p.mobj_t; +import static rr.line_t.ML_DONTDRAW; +import static rr.line_t.ML_MAPPED; +import static rr.line_t.ML_SECRET; +import rr.patch_t; +import static utils.GenericCopy.malloc; +import static utils.GenericCopy.memcpy; +import static utils.GenericCopy.memset; +import static v.DoomGraphicSystem.V_NOSCALESTART; +import v.graphics.Plotter; +import static v.renderers.DoomScreen.FG; + +public class Map implements IAutoMap { + + /////////////////// Status objects /////////////////// + final DoomMain DOOM; + + /** + * Configurable colors - now an enum + * - Good Sign 2017/04/05 + * + * Use colormap-specific colors to support extended modes. + * Moved hardcoding in here. Potentially configurable. + */ + enum Color { + CLOSE_TO_BLACK(1, (byte) 246), + REDS(16, (byte) 176 /*(256 - 5 * 16)*/), + BLUES(8, (byte) 200 /*(256 - 4 * 16 + 8)*/), + GREENS(16, (byte) 112 /*(7 * 16)*/), + GRAYS(16, (byte) 96 /*(6 * 16)*/), + BROWNS(16, (byte) 64 /*(4 * 16)*/), + YELLOWS(8, (byte) 160 /*(256 - 32)*/), + BLACK(1, (byte) 0), + WHITE(1, (byte) 4), + GRAYS_DARKER_25(13, (byte) (GRAYS.value + 4)), + DARK_GREYS(8, (byte) (GRAYS.value + GRAYS.range / 2)), + DARK_REDS(8, (byte) (REDS.value + REDS.range / 2)); + + final static int NUM_LITES = 8; + final static int[] LITE_LEVELS_FULL_RANGE = {0, 4, 7, 10, 12, 14, 15, 15}; + final static int[] LITE_LEVELS_HALF_RANGE = {0, 2, 3, 5, 6, 6, 7, 7}; + final byte[] liteBlock; + final byte value; + final int range; + + Color(int range, byte value) { + this.range = range; + this.value = value; + if (range >= NUM_LITES) { + this.liteBlock = new byte[NUM_LITES]; + } else { + this.liteBlock = null; + } + } + + static { + for (Color c : values()) { + switch (c.range) { + case 16: + for (int i = 0; i < NUM_LITES; ++i) { + c.liteBlock[i] = (byte) (c.value + LITE_LEVELS_FULL_RANGE[i]); + } + break; + case 8: + for (int i = 0; i < LITE_LEVELS_HALF_RANGE.length; ++i) { + c.liteBlock[i] = (byte) (c.value + LITE_LEVELS_HALF_RANGE[i]); + } + } + } + } + } + // For use if I do walls with outsides/insides + + // Automap colors + final static Color BACKGROUND = Color.BLACK, + YOURCOLORS = Color.WHITE, + WALLCOLORS = Color.REDS, + TELECOLORS = Color.DARK_REDS, + TSWALLCOLORS = Color.GRAYS, + FDWALLCOLORS = Color.BROWNS, + CDWALLCOLORS = Color.YELLOWS, + THINGCOLORS = Color.GREENS, + SECRETWALLCOLORS = Color.REDS, + GRIDCOLORS = Color.DARK_GREYS, + MAPPOWERUPSHOWNCOLORS = Color.GRAYS, + CROSSHAIRCOLORS = Color.GRAYS; + + final static EnumSet GENERATE_LITE_LEVELS_FOR = EnumSet.of( + TELECOLORS, + WALLCOLORS, + FDWALLCOLORS, + CDWALLCOLORS, + TSWALLCOLORS, + SECRETWALLCOLORS, + MAPPOWERUPSHOWNCOLORS, + THINGCOLORS + ); + + final static Color THEIR_COLORS[] = { + Color.GREENS, + Color.GRAYS, + Color.BROWNS, + Color.REDS + }; + + // drawing stuff + public static final ScanCode AM_PANDOWNKEY = SC_DOWN; + public static final ScanCode AM_PANUPKEY = SC_UP; + public static final ScanCode AM_PANRIGHTKEY = SC_RIGHT; + public static final ScanCode AM_PANLEFTKEY = SC_LEFT; + public static final ScanCode AM_ZOOMINKEY = SC_EQUALS; + public static final ScanCode AM_ZOOMOUTKEY = SC_MINUS; + public static final ScanCode AM_STARTKEY = SC_TAB; + public static final ScanCode AM_ENDKEY = SC_TAB; + public static final ScanCode AM_GOBIGKEY = SC_0; + public static final ScanCode AM_FOLLOWKEY = SC_F; + public static final ScanCode AM_GRIDKEY = SC_G; + public static final ScanCode AM_MARKKEY = SC_M; + public static final ScanCode AM_CLEARMARKKEY = SC_C; + public static final int AM_NUMMARKPOINTS = 10; + + // (fixed_t) scale on entry + public static final int INITSCALEMTOF = (int) (.2 * FRACUNIT); + + // how much the automap moves window per tic in frame-buffer coordinates + // moves 140 pixels in 1 second + public static final int F_PANINC = 4; + + // how much zoom-in per tic + // goes to 2x in 1 second + public static final int M_ZOOMIN = ((int) (1.02 * FRACUNIT)); + + // how much zoom-out per tic + // pulls out to 0.5x in 1 second + public static final int M_ZOOMOUT = ((int) (FRACUNIT / 1.02)); + + final EnumMap fixedColorSources = new EnumMap<>(Color.class); + final EnumMap litedColorSources = new EnumMap<>(Color.class); + + public Map(final DoomMain DOOM) { + // Some initializing... + this.DOOM = DOOM; + this.markpoints = malloc(mpoint_t::new, mpoint_t[]::new, AM_NUMMARKPOINTS); + + f_oldloc = new mpoint_t(); + m_paninc = new mpoint_t(); + + this.plotter = DOOM.graphicSystem.createPlotter(FG); + this.plr = DOOM.players[DOOM.displayplayer]; + Repalette(); + // Pre-scale stuff. + finit_width = DOOM.vs.getScreenWidth(); + finit_height = DOOM.vs.getScreenHeight() - 32 * DOOM.vs.getSafeScaling(); + } + + @Override + public final void Repalette() { + GENERATE_LITE_LEVELS_FOR.stream() + .forEach((c) -> { + if (c.liteBlock != null) { + litedColorSources.put(c, DOOM.graphicSystem.convertPalettedBlock(c.liteBlock)); + } + }); + + Arrays.stream(Color.values()) + .forEach((c) -> { + V converted = DOOM.graphicSystem.convertPalettedBlock(c.value); + @SuppressWarnings("unchecked") + V extended = (V) Array.newInstance(converted.getClass().getComponentType(), Color.NUM_LITES); + memset(extended, 0, Color.NUM_LITES, converted, 0, 1); + fixedColorSources.put(c, extended); + }); + } + + /** translates between frame-buffer and map distances */ + private int FTOM(int x) { + return FixedMul(((x) << 16), scale_ftom); + } + + /** translates between frame-buffer and map distances */ + private int MTOF(int x) { + return FixedMul((x), scale_mtof) >> 16; + } + + /** translates between frame-buffer and map coordinates */ + private int CXMTOF(int x) { + return (f_x + MTOF((x) - m_x)); + } + + /** translates between frame-buffer and map coordinates */ + private int CYMTOF(int y) { + return (f_y + (f_h - MTOF((y) - m_y))); + } + + // the following is crap + public static final short LINE_NEVERSEE = ML_DONTDRAW; + + // This seems to be the minimum viable scale before things start breaking + // up. + private static final int MINIMUM_SCALE = (int) (0.7 * FRACUNIT); + + // This seems to be the limit for some maps like europe.wad + private static final int MINIMUM_VIABLE_SCALE = FRACUNIT >> 5; + + // + // The vector graphics for the automap. + /** + * A line drawing of the player pointing right, starting from the middle. + */ + protected mline_t[] player_arrow; + + protected int NUMPLYRLINES; + + protected mline_t[] cheat_player_arrow; + + protected int NUMCHEATPLYRLINES; + + protected mline_t[] triangle_guy; + + protected int NUMTRIANGLEGUYLINES; + + protected mline_t[] thintriangle_guy; + + protected int NUMTHINTRIANGLEGUYLINES; + + protected void initVectorGraphics() { + + int R = ((8 * PLAYERRADIUS) / 7); + player_arrow + = new mline_t[]{ + new mline_t(-R + R / 8, 0, R, 0), // ----- + new mline_t(R, 0, R - R / 2, R / 4), // ---- + new mline_t(R, 0, R - R / 2, -R / 4), + new mline_t(-R + R / 8, 0, -R - R / 8, R / 4), // >--- + new mline_t(-R + R / 8, 0, -R - R / 8, -R / 4), + new mline_t(-R + 3 * R / 8, 0, -R + R / 8, R / 4), // >>-- + new mline_t(-R + 3 * R / 8, 0, -R + R / 8, -R / 4)}; + + NUMPLYRLINES = player_arrow.length; + + cheat_player_arrow + = new mline_t[]{ + new mline_t(-R + R / 8, 0, R, 0), // ----- + new mline_t(R, 0, R - R / 2, R / 6), // ---- + new mline_t(R, 0, R - R / 2, -R / 6), + new mline_t(-R + R / 8, 0, -R - R / 8, R / 6), // >---- + new mline_t(-R + R / 8, 0, -R - R / 8, -R / 6), + new mline_t(-R + 3 * R / 8, 0, -R + R / 8, R / 6), // >>---- + new mline_t(-R + 3 * R / 8, 0, -R + R / 8, -R / 6), + new mline_t(-R / 2, 0, -R / 2, -R / 6), // >>-d-- + new mline_t(-R / 2, -R / 6, -R / 2 + R / 6, -R / 6), + new mline_t(-R / 2 + R / 6, -R / 6, -R / 2 + R / 6, R / 4), + new mline_t(-R / 6, 0, -R / 6, -R / 6), // >>-dd- + new mline_t(-R / 6, -R / 6, 0, -R / 6), + new mline_t(0, -R / 6, 0, R / 4), + new mline_t(R / 6, R / 4, R / 6, -R / 7), // >>-ddt + new mline_t(R / 6, -R / 7, R / 6 + R / 32, -R / 7 - R / 32), + new mline_t(R / 6 + R / 32, -R / 7 - R / 32, + R / 6 + R / 10, -R / 7)}; + + NUMCHEATPLYRLINES = cheat_player_arrow.length; + + R = (FRACUNIT); + triangle_guy + = new mline_t[]{new mline_t(-.867 * R, -.5 * R, .867 * R, -.5 * R), + new mline_t(.867 * R, -.5 * R, 0, R), + new mline_t(0, R, -.867 * R, -.5 * R)}; + + NUMTRIANGLEGUYLINES = triangle_guy.length; + + thintriangle_guy + = new mline_t[]{new mline_t(-.5 * R, -.7 * R, R, 0), + new mline_t(R, 0, -.5 * R, .7 * R), + new mline_t(-.5 * R, .7 * R, -.5 * R, -.7 * R)}; + + NUMTHINTRIANGLEGUYLINES = thintriangle_guy.length; + } + + /** Planned overlay mode */ + protected int overlay = 0; + + protected int cheating = 0; + + protected boolean grid = false; + + protected int leveljuststarted = 1; // kluge until AM_LevelInit() is called + + protected int finit_width; + + protected int finit_height; + + // location of window on screen + protected int f_x; + + protected int f_y; + + // size of window on screen + protected int f_w; + + protected int f_h; + + protected Rectangle f_rect; + + /** used for funky strobing effect */ + protected int lightlev; + + /** pseudo-frame buffer */ + //protected V fb; + /** + * I've made this awesome change to draw map lines on the renderer + * - Good Sign 2017/04/05 + */ + protected final Plotter plotter; + + protected int amclock; + + /** (fixed_t) how far the window pans each tic (map coords) */ + protected mpoint_t m_paninc; + + /** (fixed_t) how far the window zooms in each tic (map coords) */ + protected int mtof_zoommul; + + /** (fixed_t) how far the window zooms in each tic (fb coords) */ + protected int ftom_zoommul; + + /** (fixed_t) LL x,y where the window is on the map (map coords) */ + protected int m_x, m_y; + + /** (fixed_t) UR x,y where the window is on the map (map coords) */ + protected int m_x2, m_y2; + + /** (fixed_t) width/height of window on map (map coords) */ + protected int m_w, m_h; + + /** (fixed_t) based on level size */ + protected int min_x, min_y, max_x, max_y; + + /** (fixed_t) max_x-min_x */ + protected int max_w; // + + /** (fixed_t) max_y-min_y */ + protected int max_h; + + /** (fixed_t) based on player size */ + protected int min_w, min_h; + + /** (fixed_t) used to tell when to stop zooming out */ + protected int min_scale_mtof; + + /** (fixed_t) used to tell when to stop zooming in */ + protected int max_scale_mtof; + + /** (fixed_t) old stuff for recovery later */ + protected int old_m_w, old_m_h, old_m_x, old_m_y; + + /** old location used by the Follower routine */ + protected mpoint_t f_oldloc; + + /** (fixed_t) used by MTOF to scale from map-to-frame-buffer coords */ + protected int scale_mtof = INITSCALEMTOF; + + /** used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof) */ + protected int scale_ftom; + + /** the player represented by an arrow */ + protected player_t plr; + + /** numbers used for marking by the automap */ + private final patch_t[] marknums = new patch_t[10]; + + /** where the points are */ + private final mpoint_t[] markpoints; + + /** next point to be assigned */ + private int markpointnum = 0; + + /** specifies whether to follow the player around */ + protected boolean followplayer = true; + + protected char[] cheat_amap_seq = {0xb2, 0x26, 0x26, 0x2e, 0xff}; // iddt + + protected cheatseq_t cheat_amap = new cheatseq_t(cheat_amap_seq, 0); + + // MAES: STROBE cheat. It's not even cheating, strictly speaking. + private final char cheat_strobe_seq[] = {0x6e, 0xa6, 0xea, 0x2e, 0x6a, 0xf6, + 0x62, 0xa6, 0xff // vestrobe +}; + + private final cheatseq_t cheat_strobe = new cheatseq_t(cheat_strobe_seq, 0); + + private boolean stopped = true; + + // extern boolean viewactive; + // extern byte screens[][DOOM.vs.getScreenWidth()*DOOM.vs.getScreenHeight()]; + /** + * Calculates the slope and slope according to the x-axis of a line segment + * in map coordinates (with the upright y-axis n' all) so that it can be + * used with the brain-dead drawing stuff. + * + * @param ml + * @param is + */ + public final void getIslope(mline_t ml, islope_t is) { + int dx, dy; + + dy = ml.ay - ml.by; + dx = ml.bx - ml.ax; + if (dy == 0) { + is.islp = (dx < 0 ? -MAXINT : MAXINT); + } else { + is.islp = FixedDiv(dx, dy); + } + if (dx == 0) { + is.slp = (dy < 0 ? -MAXINT : MAXINT); + } else { + is.slp = FixedDiv(dy, dx); + } + + } + + // + // + // + public final void activateNewScale() { + m_x += m_w / 2; + m_y += m_h / 2; + m_w = FTOM(f_w); + m_h = FTOM(f_h); + m_x -= m_w / 2; + m_y -= m_h / 2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + plotter.setThickness( + Math.min(MTOF(FRACUNIT), DOOM.graphicSystem.getScalingX()), + Math.min(MTOF(FRACUNIT), DOOM.graphicSystem.getScalingY()) + ); + } + + // + // + // + public final void saveScaleAndLoc() { + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; + } + + private void restoreScaleAndLoc() { + + m_w = old_m_w; + m_h = old_m_h; + if (!followplayer) { + m_x = old_m_x; + m_y = old_m_y; + } else { + m_x = plr.mo.x - m_w / 2; + m_y = plr.mo.y - m_h / 2; + } + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + + // Change the scaling multipliers + scale_mtof = FixedDiv(f_w << FRACBITS, m_w); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + + plotter.setThickness( + Math.min(MTOF(FRACUNIT), Color.NUM_LITES), + Math.min(MTOF(FRACUNIT), Color.NUM_LITES) + ); + } + + /** + * adds a marker at the current location + */ + public final void addMark() { + markpoints[markpointnum].x = m_x + m_w / 2; + markpoints[markpointnum].y = m_y + m_h / 2; + markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS; + + } + + /** + * Determines bounding box of all vertices, sets global variables + * controlling zoom range. + */ + public final void findMinMaxBoundaries() { + int a; // fixed_t + int b; + + min_x = min_y = MAXINT; + max_x = max_y = -MAXINT; + + for (int i = 0; i < DOOM.levelLoader.numvertexes; i++) { + if (DOOM.levelLoader.vertexes[i].x < min_x) { + min_x = DOOM.levelLoader.vertexes[i].x; + } else if (DOOM.levelLoader.vertexes[i].x > max_x) { + max_x = DOOM.levelLoader.vertexes[i].x; + } + + if (DOOM.levelLoader.vertexes[i].y < min_y) { + min_y = DOOM.levelLoader.vertexes[i].y; + } else if (DOOM.levelLoader.vertexes[i].y > max_y) { + max_y = DOOM.levelLoader.vertexes[i].y; + } + } + + max_w = max_x - min_x; + max_h = max_y - min_y; + + min_w = 2 * PLAYERRADIUS; // const? never changed? + min_h = 2 * PLAYERRADIUS; + + a = FixedDiv(f_w << FRACBITS, max_w); + b = FixedDiv(f_h << FRACBITS, max_h); + + min_scale_mtof = a < b ? a : b; + if (min_scale_mtof < 0) { + // MAES: safeguard against negative scaling e.g. in Europe.wad + // This seems to be the limit. + min_scale_mtof = MINIMUM_VIABLE_SCALE; + } + max_scale_mtof = FixedDiv(f_h << FRACBITS, 2 * PLAYERRADIUS); + + } + + public final void changeWindowLoc() { + if (m_paninc.x != 0 || m_paninc.y != 0) { + followplayer = false; + f_oldloc.x = MAXINT; + } + + m_x += m_paninc.x; + m_y += m_paninc.y; + + if (m_x + m_w / 2 > max_x) { + m_x = max_x - m_w / 2; + } else if (m_x + m_w / 2 < min_x) { + m_x = min_x - m_w / 2; + } + + if (m_y + m_h / 2 > max_y) { + m_y = max_y - m_h / 2; + } else if (m_y + m_h / 2 < min_y) { + m_y = min_y - m_h / 2; + } + + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + } + + public final void initVariables() { + int pnum; + + DOOM.automapactive = true; + f_oldloc.x = MAXINT; + amclock = 0; + lightlev = 0; + + m_paninc.x = m_paninc.y = 0; + ftom_zoommul = FRACUNIT; + mtof_zoommul = FRACUNIT; + + m_w = FTOM(f_w); + m_h = FTOM(f_h); + + // find player to center on initially + if (!DOOM.playeringame[pnum = DOOM.consoleplayer]) { + for (pnum = 0; pnum < MAXPLAYERS; pnum++) { + //System.out.println(pnum); + if (DOOM.playeringame[pnum]) { + break; + } + } + } + plr = DOOM.players[pnum]; + m_x = plr.mo.x - m_w / 2; + m_y = plr.mo.y - m_h / 2; + this.changeWindowLoc(); + + // for saving & restoring + old_m_x = m_x; + old_m_y = m_y; + old_m_w = m_w; + old_m_h = m_h; + + // inform the status bar of the change + DOOM.statusBar.NotifyAMEnter(); + } + + // + // + // + public final void loadPics() { + int i; + String namebuf; + + for (i = 0; i < 10; i++) { + namebuf = ("AMMNUM" + i); + marknums[i] = DOOM.wadLoader.CachePatchName(namebuf); + } + + } + + public final void unloadPics() { + int i; + + for (i = 0; i < 10; i++) { + DOOM.wadLoader.UnlockLumpNum(marknums[i]); + } + } + + public final void clearMarks() { + int i; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) { + markpoints[i].x = -1; // means empty + } + markpointnum = 0; + } + + /** + * should be called at the start of every level right now, i figure it out + * myself + */ + public final void LevelInit() { + leveljuststarted = 0; + + f_x = f_y = 0; + f_w = finit_width; + f_h = finit_height; + f_rect = new Rectangle(0, 0, f_w, f_h); + + // scanline=new byte[f_h*f_w]; + this.clearMarks(); + + this.findMinMaxBoundaries(); + scale_mtof = FixedDiv(min_scale_mtof, MINIMUM_SCALE); + if (scale_mtof > max_scale_mtof) { + scale_mtof = min_scale_mtof; + } + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + + plotter.setThickness( + Math.min(MTOF(FRACUNIT), DOOM.graphicSystem.getScalingX()), + Math.min(MTOF(FRACUNIT), DOOM.graphicSystem.getScalingY()) + ); + } + + @Override + public final void Stop() { + this.unloadPics(); + DOOM.automapactive = false; + // This is the only way to notify the status bar responder that we're + // exiting the automap. + DOOM.statusBar.NotifyAMExit(); + stopped = true; + } + + // More "static" stuff. + protected int lastlevel = -1, lastepisode = -1; + + @Override + public final void Start() { + if (!stopped) { + Stop(); + } + + stopped = false; + if (lastlevel != DOOM.gamemap || lastepisode != DOOM.gameepisode) { + this.LevelInit(); + lastlevel = DOOM.gamemap; + lastepisode = DOOM.gameepisode; + } + this.initVectorGraphics(); + this.LevelInit(); + this.initVariables(); + this.loadPics(); + } + + /** + * set the window scale to the maximum size + */ + public final void minOutWindowScale() { + scale_mtof = min_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + plotter.setThickness(DOOM.graphicSystem.getScalingX(), DOOM.graphicSystem.getScalingY()); + this.activateNewScale(); + } + + /** + * set the window scale to the minimum size + */ + public final void maxOutWindowScale() { + scale_mtof = max_scale_mtof; + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + plotter.setThickness(0, 0); + this.activateNewScale(); + } + + /** These belong to AM_Responder */ + protected boolean cheatstate = false, bigstate = false; + + /** static char buffer[20] in AM_Responder */ + protected String buffer; + + /** + * Handle events (user inputs) in automap mode + */ + @Override + @AM_Map.C(AM_Responder) + public final boolean Responder(event_t ev) { + boolean rc; + rc = false; + + // System.out.println(ev.data1==AM_STARTKEY); + if (!DOOM.automapactive) { + if (ev.isKey(AM_STARTKEY, evtype_t.ev_keyup)) { + this.Start(); + DOOM.viewactive = false; + rc = true; + } + } else if (ev.isType(evtype_t.ev_keydown)) { + rc = true; + if (ev.isKey(AM_PANRIGHTKEY)) { // pan right + if (!followplayer) { + m_paninc.x = FTOM(F_PANINC); + } else { + rc = false; + } + } else if (ev.isKey(AM_PANLEFTKEY)) { // pan left + if (!followplayer) { + m_paninc.x = -FTOM(F_PANINC); + } else { + rc = false; + } + } else if (ev.isKey(AM_PANUPKEY)) { // pan up + if (!followplayer) { + m_paninc.y = FTOM(F_PANINC); + } else { + rc = false; + } + } else if (ev.isKey(AM_PANDOWNKEY)) { // pan down + if (!followplayer) { + m_paninc.y = -FTOM(F_PANINC); + } else { + rc = false; + } + } else if (ev.isKey(AM_ZOOMOUTKEY)) { // zoom out + mtof_zoommul = M_ZOOMOUT; + ftom_zoommul = M_ZOOMIN; + } else if (ev.isKey(AM_ZOOMINKEY)) { // zoom in + mtof_zoommul = M_ZOOMIN; + ftom_zoommul = M_ZOOMOUT; + } else if (ev.isKey(AM_GOBIGKEY)) { + bigstate = !bigstate; + if (bigstate) { + this.saveScaleAndLoc(); + this.minOutWindowScale(); + } else { + this.restoreScaleAndLoc(); + } + } else if (ev.isKey(AM_FOLLOWKEY)) { + followplayer = !followplayer; + f_oldloc.x = MAXINT; + plr.message = followplayer ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF; + } else if (ev.isKey(AM_GRIDKEY)) { + grid = !grid; + plr.message = grid ? AMSTR_GRIDON : AMSTR_GRIDOFF; + } else if (ev.isKey(AM_MARKKEY)) { + buffer = (AMSTR_MARKEDSPOT + " " + markpointnum); + plr.message = buffer; + this.addMark(); + } else if (ev.isKey(AM_CLEARMARKKEY)) { + this.clearMarks(); + plr.message = AMSTR_MARKSCLEARED; + } else { + cheatstate = false; + rc = false; + } + + if (!DOOM.deathmatch && ev.ifKeyAsciiChar(cheat_amap::CheckCheat)) { + rc = false; + cheating = (cheating + 1) % 3; + } + + /** + * MAES: brought back strobe effect + * Good Sign: setting can be saved/loaded from config + */ + if (ev.ifKeyAsciiChar(cheat_strobe::CheckCheat)) { + DOOM.mapstrobe = !DOOM.mapstrobe; + } + } else if (ev.isType(evtype_t.ev_keyup)) { + rc = false; + if (ev.isKey(AM_PANRIGHTKEY)) { + if (!followplayer) { + m_paninc.x = 0; + } + } else if (ev.isKey(AM_PANLEFTKEY)) { + if (!followplayer) { + m_paninc.x = 0; + } + } else if (ev.isKey(AM_PANUPKEY)) { + if (!followplayer) { + m_paninc.y = 0; + } + } else if (ev.isKey(AM_PANDOWNKEY)) { + if (!followplayer) { + m_paninc.y = 0; + } + } else if (ev.isKey(AM_ZOOMOUTKEY) || ev.isKey(AM_ZOOMINKEY)) { + mtof_zoommul = FRACUNIT; + ftom_zoommul = FRACUNIT; + } else if (ev.isKey(AM_ENDKEY)) { + bigstate = false; + DOOM.viewactive = true; + this.Stop(); + } + } + + return rc; + + } + + /** + * Zooming + */ + private void changeWindowScale() { + + // Change the scaling multipliers + scale_mtof = FixedMul(scale_mtof, mtof_zoommul); + scale_ftom = FixedDiv(FRACUNIT, scale_mtof); + + if (scale_mtof < min_scale_mtof) { + this.minOutWindowScale(); + } else if (scale_mtof > max_scale_mtof) { + this.maxOutWindowScale(); + } else { + this.activateNewScale(); + } + + } + + // + // + // + private void doFollowPlayer() { + + if (f_oldloc.x != plr.mo.x || f_oldloc.y != plr.mo.y) { + m_x = FTOM(MTOF(plr.mo.x)) - m_w / 2; + m_y = FTOM(MTOF(plr.mo.y)) - m_h / 2; + m_x2 = m_x + m_w; + m_y2 = m_y + m_h; + f_oldloc.x = plr.mo.x; + f_oldloc.y = plr.mo.y; + + // m_x = FTOM(MTOF(plr.mo.x - m_w/2)); + // m_y = FTOM(MTOF(plr.mo.y - m_h/2)); + // m_x = plr.mo.x - m_w/2; + // m_y = plr.mo.y - m_h/2; + } + + } + + private void updateLightLev() { + // Change light level + // no more buggy nexttic - Good Sign 2017/04/01 + // no more additional lightlevelcnt - Good Sign 2017/04/05 + // no more even lightlev and changed to array access - Good Sign 2017/04/08 + if (amclock % 6 == 0) { + final int sourceLength = Color.NUM_LITES; + final V intermeditate = DOOM.graphicSystem.convertPalettedBlock((byte) 0); + litedColorSources.forEach((c, source) -> { + memcpy(source, sourceLength - 1, intermeditate, 0, 1); + memcpy(source, 0, source, 1, sourceLength - 1); + memcpy(intermeditate, 0, source, 0, 1); + }); + } + } + + /** + * Updates on Game Tick + */ + @Override + public final void Ticker() { + if (!DOOM.automapactive || DOOM.menuactive) { + return; + } + + amclock++; + + if (followplayer) { + this.doFollowPlayer(); + } + + // Change the zoom if necessary + if (ftom_zoommul != FRACUNIT) { + this.changeWindowScale(); + } + + // Change x,y location + if ((m_paninc.x | m_paninc.y) != 0) { + this.changeWindowLoc(); + } + + // Update light level + if (DOOM.mapstrobe) { + updateLightLev(); + } + } + + // private static int BUFFERSIZE=f_h*f_w; + /** + * Automap clipping of lines. Based on Cohen-Sutherland clipping algorithm + * but with a slightly faster reject and precalculated slopes. If the speed + * is needed, use a hash algorithm to handle the common cases. + */ + private int tmpx, tmpy;// =new fpoint_t(); + + private boolean clipMline(mline_t ml, fline_t fl) { + + // System.out.print("Asked to clip from "+FixedFloat.toFloat(ml.a.x)+","+FixedFloat.toFloat(ml.a.y)); + // System.out.print(" to clip "+FixedFloat.toFloat(ml.b.x)+","+FixedFloat.toFloat(ml.b.y)+"\n"); + // These were supposed to be "registers", so they exhibit by-ref + // properties. + int outcode1 = 0; + int outcode2 = 0; + int outside; + + int dx; + int dy; + /* + * fl.a.x=0; fl.a.y=0; fl.b.x=0; fl.b.y=0; + */ + + // do trivial rejects and outcodes + if (ml.ay > m_y2) { + outcode1 = TOP; + } else if (ml.ay < m_y) { + outcode1 = BOTTOM; + } + + if (ml.by > m_y2) { + outcode2 = TOP; + } else if (ml.by < m_y) { + outcode2 = BOTTOM; + } + + if ((outcode1 & outcode2) != 0) { + return false; // trivially outside + } + if (ml.ax < m_x) { + outcode1 |= LEFT; + } else if (ml.ax > m_x2) { + outcode1 |= RIGHT; + } + + if (ml.bx < m_x) { + outcode2 |= LEFT; + } else if (ml.bx > m_x2) { + outcode2 |= RIGHT; + } + + if ((outcode1 & outcode2) != 0) { + return false; // trivially outside + } + // transform to frame-buffer coordinates. + fl.ax = CXMTOF(ml.ax); + fl.ay = CYMTOF(ml.ay); + fl.bx = CXMTOF(ml.bx); + fl.by = CYMTOF(ml.by); + + // System.out.println(">>>>>> ("+fl.a.x+" , "+fl.a.y+" ),("+fl.b.x+" , "+fl.b.y+" )"); + outcode1 = DOOUTCODE(fl.ax, fl.ay); + outcode2 = DOOUTCODE(fl.bx, fl.by); + + if ((outcode1 & outcode2) != 0) { + return false; + } + + while ((outcode1 | outcode2) != 0) { + // may be partially inside box + // find an outside point + if (outcode1 != 0) { + outside = outcode1; + } else { + outside = outcode2; + } + + // clip to each side + if ((outside & TOP) != 0) { + dy = fl.ay - fl.by; + dx = fl.bx - fl.ax; + tmpx = fl.ax + (dx * (fl.ay)) / dy; + tmpy = 0; + } else if ((outside & BOTTOM) != 0) { + dy = fl.ay - fl.by; + dx = fl.bx - fl.ax; + tmpx = fl.ax + (dx * (fl.ay - f_h)) / dy; + tmpy = f_h - 1; + } else if ((outside & RIGHT) != 0) { + dy = fl.by - fl.ay; + dx = fl.bx - fl.ax; + tmpy = fl.ay + (dy * (f_w - 1 - fl.ax)) / dx; + tmpx = f_w - 1; + } else if ((outside & LEFT) != 0) { + dy = fl.by - fl.ay; + dx = fl.bx - fl.ax; + tmpy = fl.ay + (dy * (-fl.ax)) / dx; + tmpx = 0; + } + + if (outside == outcode1) { + fl.ax = tmpx; + fl.ay = tmpy; + outcode1 = DOOUTCODE(fl.ax, fl.ay); + } else { + fl.bx = tmpx; + fl.by = tmpy; + outcode2 = DOOUTCODE(fl.bx, fl.by); + } + + if ((outcode1 & outcode2) != 0) { + return false; // trivially outside + } + } + + return true; + } + + protected static int LEFT = 1, RIGHT = 2, BOTTOM = 4, TOP = 8; + + /** + * MAES: the result was supposed to be passed in an "oc" parameter by + * reference. Not convenient, so I made some changes... + * + * @param mx + * @param my + */ + private int DOOUTCODE(int mx, int my) { + int oc = 0; + if ((my) < 0) { + (oc) |= TOP; + } else if ((my) >= f_h) { + (oc) |= BOTTOM; + } + if ((mx) < 0) { + (oc) |= LEFT; + } else if ((mx) >= f_w) { + (oc) |= RIGHT; + } + return oc; + } + + /** Not my idea ;-) */ + protected int fuck = 0; + + /** + * Clip lines, draw visible parts of lines. + */ + protected int singlepixel = 0; + + private void drawMline(mline_t ml, V colorSource) { + // fl.reset(); + if (this.clipMline(ml, fl)) { + // if ((fl.a.x==fl.b.x)&&(fl.a.y==fl.b.y)) singlepixel++; + // draws the line using coords + DOOM.graphicSystem + .drawLine(plotter + .setColorSource(colorSource, 0) + .setPosition(fl.ax, fl.ay), + fl.bx, fl.by); + } + } + + private fline_t fl = new fline_t(); + + private mline_t ml = new mline_t(); + + /** + * Draws flat (floor/ceiling tile) aligned grid lines. + */ + private void drawGrid(V colorSource) { + int x, y; // fixed_t + int start, end; // fixed_t + + // Figure out start of vertical gridlines + start = m_x; + if (((start - DOOM.levelLoader.bmaporgx) % (MAPBLOCKUNITS << FRACBITS)) != 0) { + start + += (MAPBLOCKUNITS << FRACBITS) + - ((start - DOOM.levelLoader.bmaporgx) % (MAPBLOCKUNITS << FRACBITS)); + } + end = m_x + m_w; + + // draw vertical gridlines + ml.ay = m_y; + ml.by = m_y + m_h; + for (x = start; x < end; x += (MAPBLOCKUNITS << FRACBITS)) { + ml.ax = x; + ml.bx = x; + drawMline(ml, colorSource); + } + + // Figure out start of horizontal gridlines + start = m_y; + if (((start - DOOM.levelLoader.bmaporgy) % (MAPBLOCKUNITS << FRACBITS)) != 0) { + start + += (MAPBLOCKUNITS << FRACBITS) + - ((start - DOOM.levelLoader.bmaporgy) % (MAPBLOCKUNITS << FRACBITS)); + } + end = m_y + m_h; + + // draw horizontal gridlines + ml.ax = m_x; + ml.bx = m_x + m_w; + for (y = start; y < end; y += (MAPBLOCKUNITS << FRACBITS)) { + ml.ay = y; + ml.by = y; + drawMline(ml, colorSource); + } + + } + + protected mline_t l = new mline_t(); + + /** + * Determines visible lines, draws them. This is LineDef based, not LineSeg + * based. + */ + private void drawWalls() { + + final V teleColorSource = litedColorSources.get(TELECOLORS); + final V wallColorSource = litedColorSources.get(WALLCOLORS); + final V fdWallColorSource = litedColorSources.get(FDWALLCOLORS); + final V cdWallColorSource = litedColorSources.get(CDWALLCOLORS); + final V tsWallColorSource = litedColorSources.get(TSWALLCOLORS); + final V secretWallColorSource = litedColorSources.get(SECRETWALLCOLORS); + + for (int i = 0; i < DOOM.levelLoader.numlines; i++) { + l.ax = DOOM.levelLoader.lines[i].v1x; + l.ay = DOOM.levelLoader.lines[i].v1y; + l.bx = DOOM.levelLoader.lines[i].v2x; + l.by = DOOM.levelLoader.lines[i].v2y; + if ((cheating | (DOOM.levelLoader.lines[i].flags & ML_MAPPED)) != 0) { + if (((DOOM.levelLoader.lines[i].flags & LINE_NEVERSEE) & ~cheating) != 0) { + continue; + } + if (DOOM.levelLoader.lines[i].backsector == null) { + drawMline(l, wallColorSource); + } else { + if (DOOM.levelLoader.lines[i].special == 39) { // teleporters + drawMline(l, teleColorSource); + } else if ((DOOM.levelLoader.lines[i].flags & ML_SECRET) != 0) // secret + // door + { + if (cheating != 0) { + drawMline(l, secretWallColorSource); + } else { + drawMline(l, wallColorSource); + } + } else if (DOOM.levelLoader.lines[i].backsector.floorheight != DOOM.levelLoader.lines[i].frontsector.floorheight) { + drawMline(l, fdWallColorSource); // floor level change + } else if (DOOM.levelLoader.lines[i].backsector.ceilingheight != DOOM.levelLoader.lines[i].frontsector.ceilingheight) { + drawMline(l, cdWallColorSource); // ceiling level change + } else if (cheating != 0) { + drawMline(l, tsWallColorSource); + } + } + } // If we have allmap... + else if (plr.powers[pw_allmap] != 0) { + // Some are never seen even with that! + if ((DOOM.levelLoader.lines[i].flags & LINE_NEVERSEE) == 0) { + drawMline(l, litedColorSources.get(MAPPOWERUPSHOWNCOLORS)); + } + } + } + + // System.out.println("Single pixel draws: "+singlepixel+" out of "+P.lines.length); + // singlepixel=0; + } + + // + // Rotation in 2D. + // Used to rotate player arrow line character. + // + private int rotx, roty; + + /** + * Rotation in 2D. Used to rotate player arrow line character. + * + * @param x + * fixed_t + * @param y + * fixed_t + * @param a + * angle_t -> this should be a LUT-ready BAM. + */ + private void rotate(int x, int y, int a) { + // int tmpx; + + rotx = FixedMul(x, finecosine[a]) - FixedMul(y, finesine[a]); + + roty = FixedMul(x, finesine[a]) + FixedMul(y, finecosine[a]); + + // rotx.val = tmpx; + } + + private void drawLineCharacter(mline_t[] lineguy, int lineguylines, + int scale, // fixed_t + int angle, // This should be a LUT-ready angle. + V colorSource, + int x, // fixed_t + int y // fixed_t + ) { + int i; + final boolean rotate = (angle != 0); + mline_t l = new mline_t(); + + for (i = 0; i < lineguylines; i++) { + l.ax = lineguy[i].ax; + l.ay = lineguy[i].ay; + + if (scale != 0) { + l.ax = FixedMul(scale, l.ax); + l.ay = FixedMul(scale, l.ay); + } + + if (rotate) { + rotate(l.ax, l.ay, angle); + // MAES: assign rotations + l.ax = rotx; + l.ay = roty; + } + + l.ax += x; + l.ay += y; + + l.bx = lineguy[i].bx; + l.by = lineguy[i].by; + + if (scale != 0) { + l.bx = FixedMul(scale, l.bx); + l.by = FixedMul(scale, l.by); + } + + if (rotate) { + rotate(l.bx, l.by, angle); + // MAES: assign rotations + l.bx = rotx; + l.by = roty; + } + + l.bx += x; + l.by += y; + + drawMline(l, colorSource); + } + } + + public final void drawPlayers() { + player_t p; + + int their_color = -1; + V colorSource; + + // System.out.println(Long.toHexString(plr.mo.angle)); + if (!DOOM.netgame) { + if (cheating != 0) { + drawLineCharacter(cheat_player_arrow, NUMCHEATPLYRLINES, 0, + toBAMIndex(plr.mo.angle), fixedColorSources.get(Color.WHITE), plr.mo.x, + plr.mo.y); + } else { + drawLineCharacter(player_arrow, NUMPLYRLINES, 0, + toBAMIndex(plr.mo.angle), fixedColorSources.get(Color.WHITE), plr.mo.x, + plr.mo.y); + } + return; + } + + for (int i = 0; i < MAXPLAYERS; i++) { + their_color++; + p = DOOM.players[i]; + + if ((DOOM.deathmatch && !DOOM.singledemo) && p != plr) { + continue; + } + + if (!DOOM.playeringame[i]) { + continue; + } + + if (p.powers[pw_invisibility] != 0) { + colorSource = fixedColorSources.get(Color.CLOSE_TO_BLACK); + } else { + colorSource = fixedColorSources.get(THEIR_COLORS[their_color]); + } + + drawLineCharacter(player_arrow, NUMPLYRLINES, 0, (int) p.mo.angle, colorSource, p.mo.x, p.mo.y); + } + + } + + final void drawThings(Color colors, int colorrange) { + mobj_t t; + V colorSource = litedColorSources.get(colors); // Ain't gonna change + + for (int i = 0; i < DOOM.levelLoader.numsectors; i++) { + // MAES: get first on the list. + t = DOOM.levelLoader.sectors[i].thinglist; + while (t != null) { + drawLineCharacter(thintriangle_guy, NUMTHINTRIANGLEGUYLINES, + 16 << FRACBITS, toBAMIndex(t.angle), colorSource, t.x, t.y); + t = (mobj_t) t.snext; + } + } + } + + public final void drawMarks() { + int i, fx, fy, w, h; + + for (i = 0; i < AM_NUMMARKPOINTS; i++) { + if (markpoints[i].x != -1) { + w = marknums[i].width; + h = marknums[i].height; + // Nothing wrong with v1.9 IWADs, but I wouldn't put my hand on + // the fire for older ones. + // w = 5; // because something's wrong with the wad, i guess + // h = 6; // because something's wrong with the wad, i guess + fx = CXMTOF(markpoints[i].x); + fy = CYMTOF(markpoints[i].y); + if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h) { + DOOM.graphicSystem.DrawPatchScaled(FG, marknums[i], DOOM.vs, fx, fy, V_NOSCALESTART); + } + } + } + + } + + private void drawCrosshair(V colorSource) { + /*plotter.setPosition( + DOOM.videoRenderer.getScreenWidth() / 2, + DOOM.videoRenderer.getScreenHeight()/ 2 + ).setColorSource(colorSource, 0) + .plot();*/ + //fb[(f_w * (f_h + 1)) / 2] = (short) color; // single point for now + } + + @Override + public final void Drawer() { + if (!DOOM.automapactive) { + return; + } + // System.out.println("Drawing map"); + if (overlay < 1) { + DOOM.graphicSystem.FillRect(FG, f_rect, BACKGROUND.value); // BACKGROUND + } + if (grid) { + drawGrid(fixedColorSources.get(GRIDCOLORS)); + } + + drawWalls(); + drawPlayers(); + if (cheating == 2) { + drawThings(THINGCOLORS, THINGRANGE); + } + drawCrosshair(fixedColorSources.get(CROSSHAIRCOLORS)); + + drawMarks(); + + //DOOM.videoRenderer.MarkRect(f_x, f_y, f_w, f_h); + } +} \ No newline at end of file diff --git a/doom/src/automap/fline_t.java b/doom/src/automap/fline_t.java new file mode 100644 index 0000000..085c9c2 --- /dev/null +++ b/doom/src/automap/fline_t.java @@ -0,0 +1,38 @@ +package automap; + +public class fline_t { + + /* + * public fline_t(){ + a=new fpoint_t(); + b=new fpoint_t(); + } + + public fline_t(fpoint_t a, fpoint_t b){ + this.a=a; + this.b=b; + } + */ + public fline_t(int ax, int ay, int bx, int by) { + this.ay = ay; + this.ax = ax; + this.by = by; + this.bx = bx; + } + + public fline_t() { + // TODO Auto-generated constructor stub + } + + public int ax, ay, bx, by; + /* + public fpoint_t a, b; + + public void reset() { + this.a.x=0; + this.a.y=0; + this.b.x=0; + this.b.y=0; + + }*/ +} \ No newline at end of file diff --git a/doom/src/automap/fpoint_t.java b/doom/src/automap/fpoint_t.java new file mode 100644 index 0000000..cdeca28 --- /dev/null +++ b/doom/src/automap/fpoint_t.java @@ -0,0 +1,16 @@ +package automap; + +public class fpoint_t { + + int x, y; + + public fpoint_t() { + this(0, 0); + } + + public fpoint_t(int x, int y) { + this.x = x; + this.y = y; + } + +} \ No newline at end of file diff --git a/doom/src/automap/islope_t.java b/doom/src/automap/islope_t.java new file mode 100644 index 0000000..50ccc09 --- /dev/null +++ b/doom/src/automap/islope_t.java @@ -0,0 +1,7 @@ +package automap; + +public class islope_t { + + /** fixed_t */ + int slp, islp; +} \ No newline at end of file diff --git a/doom/src/automap/mline_t.java b/doom/src/automap/mline_t.java new file mode 100644 index 0000000..c00313f --- /dev/null +++ b/doom/src/automap/mline_t.java @@ -0,0 +1,49 @@ +package automap; + +/** used only in automap */ +public class mline_t { + + public mline_t() { + this(0, 0, 0, 0); + } + + public int ax, ay, bx, by; + + public mline_t(int ax, int ay, int bx, int by) { + this.ax = ax; + this.ay = ay; + this.bx = bx; + this.by = by; + } + + public mline_t(double ax, double ay, double bx, double by) { + this.ax = (int) ax; + this.ay = (int) ay; + this.bx = (int) bx; + this.by = (int) by; + } + + /* + public mline_t(mpoint_t a, mpoint_t b) { + this.a = a; + this.b = b; + } + + public mline_t(int ax,int ay,int bx,int by) { + this.a = new mpoint_t(ax,ay); + this.b = new mpoint_t(bx,by); + } + + public mline_t(double ax,double ay,double bx,double by) { + this.a = new mpoint_t(ax,ay); + this.b = new mpoint_t(bx,by); + } + + public mpoint_t a, b; + public int ax; + + public String toString(){ + return a.toString()+" - "+ b.toString(); + } + */ +} \ No newline at end of file diff --git a/doom/src/automap/mpoint_t.java b/doom/src/automap/mpoint_t.java new file mode 100644 index 0000000..3e64a2b --- /dev/null +++ b/doom/src/automap/mpoint_t.java @@ -0,0 +1,33 @@ +package automap; + +import m.fixed_t; + +public class mpoint_t { + + public mpoint_t(fixed_t x, fixed_t y) { + this.x = x.val; + this.y = y.val; + } + + public mpoint_t(int x, int y) { + this.x = x; + this.y = y; + } + + public mpoint_t(double x, double y) { + this.x = (int) x; + this.y = (int) y; + } + + public mpoint_t() { + this.x = 0; + this.y = 0; + } + + /** fixed_t */ + public int x, y; + + public String toString() { + return (Integer.toHexString(x) + " , " + Integer.toHexString(y)); + } +}; \ No newline at end of file diff --git a/doom/src/awt/DisplayModePicker.java b/doom/src/awt/DisplayModePicker.java new file mode 100644 index 0000000..57a0259 --- /dev/null +++ b/doom/src/awt/DisplayModePicker.java @@ -0,0 +1,90 @@ +package awt; + +import java.awt.DisplayMode; +import java.awt.GraphicsDevice; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class DisplayModePicker { + + protected GraphicsDevice device; + protected DisplayMode default_mode; + + public DisplayModePicker(GraphicsDevice device) { + this.device = device; + this.default_mode = device.getDisplayMode(); + } + + public DisplayMode pickClosest(int width, int height) { + + DisplayMode[] modes = device.getDisplayModes(); + List picks = new ArrayList<>(); + + WidthComparator wc = new WidthComparator(); + HeightComparator hc = new HeightComparator(); + + // Filter out those with too small dimensions. + for (DisplayMode dm : modes) { + if (dm.getWidth() >= width && dm.getHeight() >= height) { + picks.add(dm); + } + } + + if (!picks.isEmpty()) { + Collections.sort(picks, wc.thenComparing(hc)); + } + + // First one is the minimum that satisfies the desired criteria. + return picks.get(0); + } + + /** + * Return offsets to center rasters too oddly shaped to fit entirely into + * a standard display mode (unfortunately, this means most stuff > 640 x 400), + * with doom's standard 8:5 ratio. + * + * @param width + * @param height + * @param dm + * @return array, x-offset and y-offset. + */ + public int[] getCentering(int width, int height, DisplayMode dm) { + int xy[] = new int[2]; + + xy[0] = (dm.getWidth() - width) / 2; + xy[1] = (dm.getHeight() - height) / 2; + + return xy; + } + + class WidthComparator implements Comparator { + + @Override + public int compare(DisplayMode arg0, DisplayMode arg1) { + if (arg0.getWidth() > arg1.getWidth()) { + return 1; + } + if (arg0.getWidth() < arg1.getWidth()) { + return -1; + } + return 0; + } + } + + class HeightComparator implements Comparator { + + @Override + public int compare(DisplayMode arg0, DisplayMode arg1) { + if (arg0.getHeight() > arg1.getHeight()) { + return 1; + } + if (arg0.getHeight() < arg1.getHeight()) { + return -1; + } + return 0; + } + } + +} \ No newline at end of file diff --git a/doom/src/awt/DoomFrame.java b/doom/src/awt/DoomFrame.java new file mode 100644 index 0000000..858ca14 --- /dev/null +++ b/doom/src/awt/DoomFrame.java @@ -0,0 +1,183 @@ +package awt; + +import doom.CommandVariable; +import java.awt.Component; +import java.awt.Container; +import java.awt.Graphics2D; +import java.awt.HeadlessException; +import java.awt.Image; +import static java.awt.RenderingHints.KEY_ALPHA_INTERPOLATION; +import static java.awt.RenderingHints.KEY_ANTIALIASING; +import static java.awt.RenderingHints.KEY_COLOR_RENDERING; +import static java.awt.RenderingHints.KEY_RENDERING; +import static java.awt.RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED; +import static java.awt.RenderingHints.VALUE_ANTIALIAS_OFF; +import static java.awt.RenderingHints.VALUE_COLOR_RENDER_SPEED; +import static java.awt.RenderingHints.VALUE_RENDER_SPEED; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFrame; +import mochadoom.Engine; +import mochadoom.Loggers; + +/** + * Common code for Doom's video frames + */ +public class DoomFrame> extends JFrame implements FullscreenOptions { + + private static final Logger LOGGER = Loggers.getLogger(DoomFrame.class.getName()); + + private static final long serialVersionUID = -4130528877723831825L; + + /** + * Canvas or JPanel + */ + private final Window content; + + /** + * Graphics to draw image on + */ + private volatile Graphics2D g2d; + + /** + * Provider of video content to display + */ + final Supplier imageSupplier; + + /** + * Default window size. It might change upon entering full screen, so don't consider it absolute. Due to letter + * boxing and screen doubling, stretching etc. it might be different that the screen buffer (typically, larger). + */ + final Dimension dim; + + /** + * Very generic JFrame. Along that it only initializes various properties of Doom Frame. + */ + DoomFrame(Dimension dim, Window content, Supplier imageSupplier) throws HeadlessException { + this.dim = dim; + this.content = content; + this.imageSupplier = imageSupplier; + init(); + } + + /** + * Initialize properties + */ + private void init() { + /** + * This should fix Tab key + * - Good Sign 2017/04/21 + */ + setFocusTraversalKeysEnabled(false); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setTitle(Engine.getEngine().getWindowTitle(0)); + } + + public void turnOn() { + add(content); + content.setFocusTraversalKeysEnabled(false); + if (content instanceof Container) { + setContentPane((Container) content); + } else { + getContentPane().setPreferredSize(content.getPreferredSize()); + } + + setResizable(false); + + /** + * Set it to be later then setResizable to avoid extra space on right and bottom + * - Good Sign 2017/04/09 + * + * JFrame's size is auto-set here. + */ + pack(); + + // center frame + setLocationRelativeTo(null); + + setVisible(true); + + // Gently tell the eventhandler to wake up and set itself. + requestFocus(); + content.requestFocusInWindow(); + } + + /** + * Uninitialize graphics, so it can be reset on the next repaint + */ + public void renewGraphics() { + final Graphics2D localG2d = g2d; + g2d = null; + if (localG2d != null) { + localG2d.dispose(); + } + } + + /** + * Modified update method: no context needs to passed. + * Will render only internal screens. + */ + public void update() { + if (!content.isDisplayable()) { + return; + } + + /** + * Work on a local copy of the stack - global one can become null at any moment + */ + final Graphics2D localG2d = getGraphics2D(); + + /** + * If the game starts too fast, it is possible to raise an exception there + * We don't want to bother player with "something bad happened" + * but we wouldn't just be quiet either in case of "something really bad happened" + * - Good Sign 2017/04/09 + */ + if (localG2d == null) { + LOGGER.log(Level.INFO, "Starting or switching fullscreen, have no Graphics2d yet, skipping paint"); + } else { + draw(g2d, imageSupplier.get(), dim, this); + if (showFPS) { + ++frames; + final long now = System.currentTimeMillis(); + final long lambda = now - lastTime; + if (lambda >= 100L) { + setTitle(Engine.getEngine().getWindowTitle(frames * 1000.0 / lambda)); + frames = 0; + lastTime = now; + } + } + } + } + + /** + * Techdemo v1.3: Mac OSX fix, compatible with Windows and Linux. + * Should probably run just once. Overhead is minimal + * compared to actually DRAWING the stuff. + */ + private Graphics2D getGraphics2D() { + Graphics2D localG2d; + if ((localG2d = g2d) == null) { + // add double-checked locking + synchronized (DoomFrame.class) { + if ((localG2d = g2d) == null) { + g2d = localG2d = (Graphics2D) content.getGraphics(); + localG2d.setRenderingHint(KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_SPEED); + localG2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); + localG2d.setRenderingHint(KEY_RENDERING, VALUE_RENDER_SPEED); + localG2d.setRenderingHint(KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_SPEED); + + // add fullscreen interpolation options + applyFullscreenOptions(localG2d); + } + } + } + + return localG2d; + } + + private final boolean showFPS = Engine.getCVM().bool(CommandVariable.SHOWFPS); + private long lastTime = System.currentTimeMillis(); + private int frames = 0; +} \ No newline at end of file diff --git a/doom/src/awt/DoomWindow.java b/doom/src/awt/DoomWindow.java new file mode 100644 index 0000000..cdc477f --- /dev/null +++ b/doom/src/awt/DoomWindow.java @@ -0,0 +1,161 @@ +package awt; + +import doom.CommandVariable; +import doom.event_t; +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Component; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.util.StringTokenizer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javax.swing.JPanel; +import mochadoom.Engine; + +/** + * Methods specific to Doom-System video interfacing. + * In essence, whatever you are using as a final system-specific way to display + * the screens, should be able to respond to these commands. In particular, + * screen update requests must be honored, and palette/gamma request changes + * must be intercepted before they are forwarded to the renderers (in case they + * are system-specific, rather than renderer-specific). + * + * The idea is that the final screen rendering module sees/handles as less as + * possible, and only gets a screen to render, no matter what depth it is. + */ +public interface DoomWindow> { + + /** + * Get current graphics device + */ + static GraphicsDevice getDefaultDevice() { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + } + + /** + * Get an instance of JFrame to draw anything. This will try to create compatible Canvas and + * will bing all AWT listeners + */ + static DoomWindowController createCanvasWindowController( + final Supplier imageSource, + final Consumer doomEventConsume, + final int width, final int height + ) { + final GraphicsDevice device = getDefaultDevice(); + return new DoomWindowController<>(EventHandler.class, device, imageSource, doomEventConsume, + new CanvasWindow(getDefaultDevice().getDefaultConfiguration()), width, height); + } + + /** + * Get an instance of JFrame to draw anything. This will try to create compatible Canvas and + * will bing all AWT listeners + */ + static DoomWindowController createJPanelWindowController( + final Supplier imageSource, + final Consumer doomEventConsume, + final int width, final int height + ) { + return new DoomWindowController<>(EventHandler.class, getDefaultDevice(), imageSource, + doomEventConsume, new JPanelWindow(), width, height); + } + + /** + * Incomplete. Only checks for -geom format + */ + @SuppressWarnings("UnusedAssignment") + default boolean handleGeom() { + int x = 0; + int y = 0; + + // warning: char format, different type arg + int xsign = ' '; + int ysign = ' '; + /* + String displayname; + String d; + int n; + int pnum; + + boolean oktodraw; + long attribmask; + + // Try setting the locale the US, otherwise there will be problems + // with non-US keyboards. + if (this.getInputContext() == null || !this.getInputContext().selectInputMethod(java.util.Locale.US)) { + System.err.println("Could not set the input context to US! Keyboard input will be glitchy!"); + } else { + System.err.println("Input context successfully set to US."); + } + + // check for command-line display name + displayname = Game.getCVM().get(CommandVariable.DISP, String.class, 0).orElse(null); + + // check for command-line geometry*/ + if (Engine.getCVM().present(CommandVariable.GEOM)) { + try { + String eval = Engine.getCVM().get(CommandVariable.GEOM, String.class, 0).get().trim(); + // warning: char format, different type arg 3,5 + //n = sscanf(myargv[pnum+1], "%c%d%c%d", &xsign, &x, &ysign, &y); + // OK, so we have to read a string that may contain + // ' '/'+'/'-' and a number. Twice. + StringTokenizer tk = new StringTokenizer(eval, "-+ "); + // Signs. Consider positive. + xsign = 1; + ysign = 1; + for (int i = 0; i < eval.length(); i++) { + if (eval.charAt(i) == '-') { + // First '-' on trimmed string: negagive + if (i == 0) { + xsign = -1; + } else { + ysign = -1; + } + } + } + + //this should parse two numbers. + if (tk.countTokens() == 2) { + x = xsign * Integer.parseInt(tk.nextToken()); + y = ysign * Integer.parseInt(tk.nextToken()); + } + + } catch (NumberFormatException e) { + return false; + } + } + + return true; + } + + final static class JPanelWindow extends JPanel implements DoomWindow { + + private static final long serialVersionUID = 4031722796186278753L; + + private JPanelWindow() { + init(); + } + + private void init() { + setDoubleBuffered(true); + setOpaque(true); + setBackground(Color.BLACK); + } + + @Override + public boolean isOptimizedDrawingEnabled() { + return false; + } + } + + final static class CanvasWindow extends Canvas implements DoomWindow { + + private static final long serialVersionUID = 1180777361390303859L; + + private CanvasWindow(GraphicsConfiguration config) { + super(config); + } + } +} \ No newline at end of file diff --git a/doom/src/awt/DoomWindowController.java b/doom/src/awt/DoomWindowController.java new file mode 100644 index 0000000..fd299ac --- /dev/null +++ b/doom/src/awt/DoomWindowController.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package awt; + +import doom.event_t; +import java.awt.Color; +import java.awt.Component; +import java.awt.DisplayMode; +import java.awt.GraphicsDevice; +import java.awt.Image; +import java.awt.Toolkit; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.Settings; +import mochadoom.Engine; +import mochadoom.Loggers; + +/** + * Display, its configuration and resolution related stuff, + * DoomFrame creation, full-screen related code. Window recreation control. + * That sort of things. + */ +public class DoomWindowController, H extends Enum & EventBase> implements FullscreenOptions { + + private static final Logger LOGGER = Loggers.getLogger(DoomWindow.class.getName()); + + private static final long ALL_EVENTS_MASK = 0xFFFF_FFFF_FFFF_FFFFL; + + final GraphicsDevice device; + final FullscreenFunction switcher; + final int defaultWidth, defaultHeight; + + private final E component; + private final EventObserver observer; + private DoomFrame doomFrame; + + /** + * Default window size. It might change upon entering full screen, so don't consider it absolute. Due to letter + * boxing and screen doubling, stretching etc. it might be different that the screen buffer (typically, larger). + */ + private final DimensionImpl dimension; + private boolean isFullScreen; + + DoomWindowController( + final Class handlerClass, + final GraphicsDevice device, + final Supplier imageSource, + final Consumer doomEventConsumer, + final E component, + final int defaultWidth, + final int defaultHeight + ) { + this.device = device; + this.switcher = createFullSwitcher(device); + this.component = component; + this.defaultWidth = defaultWidth; + this.defaultHeight = defaultHeight; + this.dimension = new DimensionImpl(defaultWidth, defaultHeight); + this.doomFrame = new DoomFrame<>(dimension, component, imageSource); + this.observer = new EventObserver<>(handlerClass, component, doomEventConsumer); + Toolkit.getDefaultToolkit().addAWTEventListener(observer::observe, ALL_EVENTS_MASK); + sizeInit(); + doomFrame.turnOn(); + } + + private void sizeInit() { + try { + if (!(Engine.getConfig().equals(Settings.fullscreen, Boolean.TRUE) && switchToFullScreen())) { + updateSize(); + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, + String.format("Error creating DOOM AWT frame. Exiting. Reason: %s", e.getMessage()), e); + throw e; + } + } + + public void updateFrame() { + doomFrame.update(); + } + + public EventObserver getObserver() { + return observer; + } + + public boolean switchFullscreen() { + LOGGER.log(Level.INFO, "Fullscreen switched"); + // remove the frame from view + doomFrame.dispose(); + doomFrame = new DoomFrame<>(dimension, component, doomFrame.imageSupplier); + // change all the properties + final boolean ret = switchToFullScreen(); + // now show back the frame + doomFrame.turnOn(); + return ret; + } + + /** + * FULLSCREEN SWITCH CODE TODO: it's not enough to do this without also switching the screen's resolution. + * Unfortunately, Java only has a handful of options which depend on the OS, driver, display, JVM etc. and it's not + * possible to switch to arbitrary resolutions. + * + * Therefore, a "best fit" strategy with centering is used. + */ + public final boolean switchToFullScreen() { + if (!isFullScreen) { + isFullScreen = device.isFullScreenSupported(); + if (!isFullScreen) { + return false; + } + } else { + isFullScreen = false; + } + final DisplayMode displayMode = switcher.get(defaultWidth, defaultHeight); + doomFrame.setUndecorated(isFullScreen); + + // Full-screen mode + device.setFullScreenWindow(isFullScreen ? doomFrame : null); + if (device.isDisplayChangeSupported()) { + device.setDisplayMode(displayMode); + } + + component.validate(); + dimension.setSize(displayMode); + updateSize(); + return isFullScreen; + } + + private void updateSize() { + doomFrame.setPreferredSize(isFullscreen() ? dimension : null); + component.setPreferredSize(dimension); + component.setBounds(0, 0, defaultWidth - 1, defaultHeight - 1); + component.setBackground(Color.black); + doomFrame.renewGraphics(); + } + + public boolean isFullscreen() { + return isFullScreen; + } + + private class DimensionImpl extends java.awt.Dimension implements Dimension { + + private static final long serialVersionUID = 4598094740125688728L; + private int offsetX, offsetY; + private int fitWidth, fitHeight; + + DimensionImpl(int width, int height) { + this.width = defaultWidth; + this.height = defaultHeight; + this.offsetX = offsetY = 0; + this.fitWidth = width; + this.fitHeight = height; + } + + @Override + public int width() { + return width; + } + + @Override + public int height() { + return height; + } + + @Override + public int defWidth() { + return defaultWidth; + } + + @Override + public int defHeight() { + return defaultHeight; + } + + @Override + public int fitX() { + return fitWidth; + } + + @Override + public int fitY() { + return fitHeight; + } + + @Override + public int offsX() { + return offsetX; + } + + @Override + public int offsY() { + return offsetY; + } + + private void setSize(DisplayMode mode) { + if (isFullScreen) { + this.width = mode.getWidth(); + this.height = mode.getHeight(); + this.offsetX = Dimension.super.offsX(); + this.offsetY = Dimension.super.offsY(); + this.fitWidth = Dimension.super.fitX(); + this.fitHeight = Dimension.super.fitY(); + } else { + this.width = defaultWidth; + this.height = defaultHeight; + this.offsetX = offsetY = 0; + this.fitWidth = width; + this.fitHeight = height; + } + } + } +} \ No newline at end of file diff --git a/doom/src/awt/EventBase.java b/doom/src/awt/EventBase.java new file mode 100644 index 0000000..df50957 --- /dev/null +++ b/doom/src/awt/EventBase.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package awt; + +import g.Signals; +import java.awt.AWTEvent; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.IntSupplier; + +/** + * The base for construction of Event handling dictionaries + * EventHandler is a reference implementation of this base + * + * Note the type safety with generics.It could be a complex task, but you can avoid + unchecked casts and warnings suppression. Whoa... Make my head swirl around! + - Good Sign 2017/04/24 + * + * @author Good Sign + * @param the type of handler + */ +public interface EventBase & EventBase> extends IntSupplier { + + static final Comparator EVENT_SORT = Comparator.comparingInt(IntSupplier::getAsInt); + + static & EventBase> H[] sortHandlers(H[] values) { + Arrays.sort(values, EVENT_SORT); + return values; + } + + /** + * Find event by id + * @param type of event + * @param values event values + * @param eventId the id + * @return the event (can be null) + */ + static & EventBase> H findById(H[] values, int eventId) { + final int index = Arrays.binarySearch(values, (IntSupplier) () -> eventId, EVENT_SORT); + if (index < 0) { + return null; + } + return values[index]; + } + + @SafeVarargs + static & EventBase> Relation[] Relate(H src, H... dests) { + @SuppressWarnings("unchecked") + final IntFunction[]> arrayer = Relation[]::new; + return Arrays.stream(dests) + .map(dest -> new Relation<>(src, dest)) + .toArray(arrayer); + } + + Set defaultEnabledActions(); + + Map> allActions(); + + Map> cooperations(); + + Map> adjustments(); + + default boolean hasActions(final ActionMode... modes) { + final Set actions = defaultEnabledActions(); + if (actions.isEmpty()) { + return false; + } + + for (final ActionMode m : modes) { + if (!actions.contains(m)) { + return false; + } + } + + return true; + } + + enum KeyStateSatisfaction { + SATISFIED_ATE, + GENEOROUS_PASS, + WANTS_MORE_ATE, + WANTS_MORE_PASS + } + + enum ActionMode { + PERFORM, DEPEND, CAUSE, REVERT; + } + + enum RelationAffection { + ENABLES, DISABLES, COOPERATES; + } + + enum RelationType { + ENABLE(RelationAffection.ENABLES, ActionMode.PERFORM), + ENABLE_DEPEND(RelationAffection.ENABLES, ActionMode.DEPEND), + ENABLE_CAUSE(RelationAffection.ENABLES, ActionMode.CAUSE), + ENABLE_REVERT(RelationAffection.ENABLES, ActionMode.REVERT), + DISABLE(RelationAffection.DISABLES, ActionMode.PERFORM), + DISABLE_DEPEND(RelationAffection.DISABLES, ActionMode.DEPEND), + DISABLE_CAUSE(RelationAffection.DISABLES, ActionMode.CAUSE), + DISABLE_REVERT(RelationAffection.DISABLES, ActionMode.REVERT), + DEPEND(RelationAffection.COOPERATES, ActionMode.DEPEND), + CAUSE(RelationAffection.COOPERATES, ActionMode.CAUSE), + REVERT(RelationAffection.COOPERATES, ActionMode.REVERT); + + final RelationAffection affection; + final ActionMode affectedMode; + + private RelationType(RelationAffection affection, ActionMode affectedMode) { + this.affection = affection; + this.affectedMode = affectedMode; + } + + @Override + public String toString() { + return String.format("%s on [%s]", affection, affectedMode); + } + } + + @FunctionalInterface + interface ActionMapper & EventBase> { + + void map(ActionMode mode, EventAction action); + } + + @FunctionalInterface + interface RelationMapper & EventBase> { + + void map(RelationType type, Relation[] relations); + } + + @FunctionalInterface + interface EventAction & EventBase> { + + void act(EventObserver obs, AWTEvent ev); + } + + interface KeyStateCallback & EventBase> { + + KeyStateSatisfaction call(EventObserver observer); + } + + final class KeyStateInterest & EventBase> { + + private final Set interestSet; + private final KeyStateCallback satisfiedCallback; + + public KeyStateInterest( + final KeyStateCallback satisfiedCallback, + final Signals.ScanCode interestFirstKey, + Signals.ScanCode... interestKeyChain + ) { + this.interestSet = EnumSet.of(interestFirstKey, interestKeyChain); + this.satisfiedCallback = satisfiedCallback; + } + } + + final class KeyStateHolder & EventBase> { + + private final Set holdingSet; + private final LinkedHashSet> keyInterests; + @SuppressWarnings("unchecked") + private final IntFunction[]> generator = KeyStateInterest[]::new; + + public KeyStateHolder() { + this.holdingSet = EnumSet.noneOf(Signals.ScanCode.class); + this.keyInterests = new LinkedHashSet<>(); + } + + public void removeAllKeys() { + holdingSet.clear(); + } + + public boolean contains(Signals.ScanCode sc) { + return holdingSet.contains(sc); + } + + public void addInterest(KeyStateInterest interest) { + this.keyInterests.add(interest); + } + + public void removeInterest(KeyStateInterest interest) { + this.keyInterests.remove(interest); + } + + public boolean matchInterest(final KeyStateInterest check) { + return holdingSet.containsAll(check.interestSet); + } + + public boolean notifyKeyChange(EventObserver observer, Signals.ScanCode code, boolean press) { + if (press) { + holdingSet.add(code); + + final KeyStateInterest[] matched = keyInterests.stream() + .filter(this::matchInterest) + .toArray(this.generator); + + boolean ret = false; + for (int i = 0; i < matched.length; ++i) { + switch (matched[i].satisfiedCallback.call(observer)) { + case SATISFIED_ATE: + ret = true; + case GENEOROUS_PASS: + keyInterests.remove(matched[i]); + break; + case WANTS_MORE_ATE: + ret = true; + case WANTS_MORE_PASS: + break; + } + } + + return ret; + } else { + holdingSet.remove(code); + return false; + } + } + } + + /** + * Enable/disable and remaps of actions is actually reflected here. It is only initial template in the Handler + */ + final class ActionStateHolder & EventBase> { + + private final Map> enabledActions; + private final Map>> actionsMap; + private final Map>> cooperationMap; + private final Map>> adjustmentMap; + private final EventObserver observer; + private final EnumSet emptyEnumSet; + + public boolean hasActionsEnabled(final Handler h, final ActionMode... modes) { + final Set actions = enabledActions.get(h); + if (actions.isEmpty()) { + return false; + } + + for (final ActionMode m : modes) { + if (!actions.contains(m)) { + return false; + } + } + + return true; + } + + public ActionStateHolder(final Class hClass, final EventObserver observer) { + final Handler[] values = hClass.getEnumConstants(); + this.enabledActions = populate(hClass, values, h -> { + final Set set = h.defaultEnabledActions(); + return set.isEmpty() ? EnumSet.noneOf(ActionMode.class) : EnumSet.copyOf(set); + }); + this.actionsMap = populate(hClass, values, h -> { + final Map> map = h.allActions(); + return map.isEmpty() ? new EnumMap<>(ActionMode.class) : new EnumMap<>(map); + }); + this.cooperationMap = populate(hClass, values, h -> deepCopyMap(h.cooperations())); + this.adjustmentMap = populate(hClass, values, h -> deepCopyMap(h.adjustments())); + this.observer = observer; + this.emptyEnumSet = EnumSet.noneOf(hClass); + } + + private Map> deepCopyMap(final Map> map) { + if (map.isEmpty()) { + return new EnumMap<>(RelationType.class); + } + + // shallow copy first + final EnumMap> copy = new EnumMap<>(map); + // now values + copy.replaceAll((r, l) -> EnumSet.copyOf(l)); + return copy; + } + + private Map populate(Class hClass, Handler[] values, Function mapper) { + return Arrays.stream(values).collect( + () -> new EnumMap<>(hClass), + (m, h) -> m.put(h, mapper.apply(h)), + EnumMap::putAll + ); + } + + public ActionStateHolder run(final Handler h, final ActionMode mode, final AWTEvent ev) { + if (enabledActions.get(h).contains(mode)) { + Optional.ofNullable(actionsMap.get(h).get(mode)).ifPresent(action -> action.act(observer, ev)); + } + + return this; + } + + public Map> cooperations(final Handler h) { + return cooperationMap.get(h); + } + + public Map> adjustments(final Handler h) { + return adjustmentMap.get(h); + } + + public Set cooperations(final Handler h, final RelationType type) { + return cooperationMap.get(h).getOrDefault(type, emptyEnumSet); + } + + public Set adjustments(final Handler h, final RelationType type) { + return adjustmentMap.get(h).getOrDefault(type, emptyEnumSet); + } + + @SafeVarargs + public final ActionStateHolder unmapCooperation(final Handler h, RelationType type, final Handler... targets) { + final Set set = cooperationMap.get(h).get(type); + if (set == null || set.isEmpty()) { + return this; + } + + if (targets.length == 0) { + set.clear(); + } else { + set.removeAll(Arrays.asList(targets)); + } + + return this; + } + + @SafeVarargs + public final ActionStateHolder mapCooperation(final Handler h, RelationType mode, final Handler... targets) { + cooperationMap.get(h).compute(mode, (m, set) -> { + if (set == null) { + set = EnumSet.copyOf(emptyEnumSet); + } + set.addAll(Arrays.asList(targets)); + return set; + }); + + return this; + } + + @SafeVarargs + public final ActionStateHolder restoreCooperation(final Handler h, RelationType mode, final Handler... targets) { + final Set orig = h.adjustments().get(mode); + + if (orig != null) { + final Set a = EnumSet.copyOf(orig); + final Set b = cooperationMap.get(h).get(mode); + a.retainAll(Arrays.asList(targets)); + b.addAll(a); + } else { + cooperationMap.get(h).remove(mode); + } + + return this; + } + + @SafeVarargs + public final ActionStateHolder unmapAdjustment(final Handler h, RelationType type, final Handler... targets) { + final Set set = adjustmentMap.get(h).get(type); + if (set == null || set.isEmpty()) { + return this; + } + + if (targets.length == 0) { + set.clear(); + } else { + set.removeAll(Arrays.asList(targets)); + } + + return this; + } + + @SafeVarargs + public final ActionStateHolder mapAdjustment(final Handler h, RelationType mode, final Handler... targets) { + adjustmentMap.get(h).compute(mode, (m, set) -> { + if (set == null) { + set = EnumSet.copyOf(emptyEnumSet); + } + set.addAll(Arrays.asList(targets)); + return set; + }); + + return this; + } + + @SafeVarargs + public final ActionStateHolder restoreAdjustment(final Handler h, RelationType mode, final Handler... targets) { + final Set orig = h.adjustments().get(mode); + + if (orig != null) { + final Set a = EnumSet.copyOf(orig); + final Set b = adjustmentMap.get(h).get(mode); + a.retainAll(Arrays.asList(targets)); + b.addAll(a); + } else { + adjustmentMap.get(h).remove(mode); + } + + return this; + } + + public ActionStateHolder enableAction(final Handler h, ActionMode mode) { + enabledActions.get(h).add(mode); + + return this; + } + + public ActionStateHolder disableAction(final Handler h, ActionMode mode) { + enabledActions.get(h).remove(mode); + + return this; + } + + public ActionStateHolder unmapAction(final Handler h, ActionMode mode) { + actionsMap.get(h).remove(mode); + + return this; + } + + public ActionStateHolder mapAction(final Handler h, ActionMode mode, EventAction remap) { + actionsMap.get(h).put(mode, remap); + + return this; + } + + public ActionStateHolder remapAction(final Handler h, ActionMode mode, EventAction remap) { + actionsMap.get(h).replace(mode, remap); + + return this; + } + + public ActionStateHolder restoreAction(final Handler h, ActionMode mode) { + final EventAction a = h.allActions().get(mode); + + if (a != null) { + actionsMap.get(h).put(mode, a); + } else { + actionsMap.get(h).remove(mode); + } + + return this; + } + } + + final class Relation & EventBase> { + + public final Handler sourceHandler; + public final Handler targetHandler; + + public Relation(Handler sourceHandler, Handler targetHandler) { + this.sourceHandler = sourceHandler; + this.targetHandler = targetHandler; + } + } +} \ No newline at end of file diff --git a/doom/src/awt/EventHandler.java b/doom/src/awt/EventHandler.java new file mode 100644 index 0000000..6bbc3c6 --- /dev/null +++ b/doom/src/awt/EventHandler.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package awt; + +import static awt.EventBase.Relate; +import g.Signals; +import static g.Signals.ScanCode.SC_PRTSCRN; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.WindowEvent; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Stream; + +/** + * This class is catching events thrown at him by someone, and sends them to underlying DOOM engine. + * As a way to preserve vanilla until full understand the code, everything inside of underlying engine + * is still considered black box and changes to it are minimal. + * But I've tried to make high-level API on top level effective. + * + * For example, we do not need to create some MochaDoomInputEvent for unique combination of AWTEvent and + * its type - it can be easily switched by ID value from AWTEvent's ow information method. Also, we can + * certainly know which ScanCodes we will get and what are their minimal and max values, because + * of using Enum for them (my favorite type of data structure, huh!) + * And if we know that ScanCodes can only be something very limited, and every KeyEvent (an AWTEvent for keys) + * will be translated into one of them, we only have to pre-create two copies of DOOM's event structure + * for each entry of ScanCode Enum: one for press state, one for release. + * + * Note: SysRq / Print Screen key only sends release state, so it have to send press to underlying engine + * on release or it will be ignored. + * - Good Sign 2017/04/21 + * + * The secondary purpose of this class is to automatically handle relations between different + * event handlers. Everything like "when window is not in focus, don't process keys". + * - Good Sign 2017/04/22 + * + * New way of on-event actions and definitions: + * Enums and lambdas are magic combination. Here you cave a static pre-defined constant pool of events and + * abstract reactions on them that can work with many copies of Observer if you want, and with different ways + * of listening for events. + * + * How to use: + * define enum constant with arbitrary name (i.e. JOYSTICK_SOEMTHING) + * then write its arguments: + * first argument is AWTEvent id it will react to, + * second argument is lambda accepting mapper. map function on mapper maps ActionMode to EventAction + * ActionMode.PERFORM it what you want to do when the event occured. + * Function or lambda you map through mapper.map method accepts Observer object and AWTEvent + * (you can cast your AWTEvent assuming the proper id will correspond to the proper AWTEvent) + * ActionMode.REVERT is what you want to do when some event negates effect of this event + * (i.e. when the user switched to another application, clear joystick pressed button states) + * - Good Sign 2017/04/24 + * + * @author Good Sign + */ +public enum EventHandler implements EventBase { + KEY_PRESS(KeyEvent.KEY_PRESSED, mapper -> { + mapper.map(ActionMode.REVERT, EventObserver::cancelKeys); + mapper.map(ActionMode.PERFORM, EventObserver::sendKeyDowns); + mapper.map(ActionMode.DEPEND, (observer, event) -> { + // Add keyDown for Print Screen because he doesn't send one + if (Signals.getScanCode((KeyEvent) event) == SC_PRTSCRN) { + observer.feed(SC_PRTSCRN.doomEventDown); + } + }); + }, ActionMode.REVERT, ActionMode.PERFORM, ActionMode.DEPEND), + KEY_RELEASE(KeyEvent.KEY_RELEASED, mapper -> { + mapper.map(ActionMode.PERFORM, EventObserver::sendKeyUps); + /*mapper.map(ActionMode.DEPEND_BEFORE, (observer, event) -> { + // release alt key on fullscreen switch + if (Signals.getScanCode((KeyEvent) event) == SC_ENTER) { + observer.consume(SC_LALT.doomEventUp); + } + });*/ + }, ActionMode.PERFORM/*, ActionMode.DEPEND*/), + KEY_TYPE(KeyEvent.KEY_TYPED, mapper -> { + mapper.map(ActionMode.PERFORM, EventObserver::sendKeyUps); + }, ActionMode.PERFORM), + MOUSE_PRESS(MouseEvent.MOUSE_PRESSED, mapper -> { + mapper.map(ActionMode.REVERT, EventObserver::cancelMouse); + mapper.map(ActionMode.PERFORM, (observer, ev) -> { + observer.mouseEvent.buttonOn((MouseEvent) ev); + observer.mouseEvent.x = observer.mouseEvent.y = 0; + if (observer.mouseEvent.processed) { + observer.mouseEvent.resetNotify(); + observer.feed(observer.mouseEvent); + } + }); + }, ActionMode.REVERT, ActionMode.PERFORM), + MOUSE_RELEASE(MouseEvent.MOUSE_RELEASED, mapper -> { + mapper.map(ActionMode.PERFORM, (observer, ev) -> { + observer.mouseEvent.buttonOff((MouseEvent) ev); + observer.mouseEvent.x = observer.mouseEvent.y = 0; + if (observer.mouseEvent.processed) { + observer.mouseEvent.resetNotify(); + observer.feed(observer.mouseEvent); + } + }); + }, ActionMode.PERFORM), + MOUSE_CLICK(MouseEvent.MOUSE_CLICKED, mapper -> { + // Set input method and mouse cursor, move cursor to the centre + mapper.map(ActionMode.PERFORM, EventObserver::centreCursor); + }), + MOUSE_MOVE(MouseEvent.MOUSE_MOVED, mapper -> { + mapper.map(ActionMode.PERFORM, mouseMoveAction(false)); + }, ActionMode.PERFORM), + MOUSE_DRAG(MouseEvent.MOUSE_DRAGGED, mapper -> { + mapper.map(ActionMode.PERFORM, mouseMoveAction(true)); + }, ActionMode.PERFORM), + WINDOW_ACTIVATE(WindowEvent.WINDOW_ACTIVATED, ActionMode.PERFORM, ActionMode.CAUSE), + WINDOW_DEICONIFY(WindowEvent.WINDOW_DEICONIFIED, ActionMode.PERFORM), + COMPONENT_RESIZE(ComponentEvent.COMPONENT_RESIZED, ActionMode.PERFORM), + MOUSE_ENTER(MouseEvent.MOUSE_ENTERED, mapper -> { + // Set input method and mouse cursor, move cursor to the centre + mapper.map(ActionMode.PERFORM, EventObserver::centreCursor); + }), + WINDOW_OPEN(WindowEvent.WINDOW_OPENED, mapper -> { + // Set input method and mouse cursor + mapper.map(ActionMode.PERFORM, EventObserver::centreCursor); + }, ActionMode.PERFORM), + WINDOW_GAIN_FOCUS(WindowEvent.WINDOW_GAINED_FOCUS, mapper -> { + mapper.map(ActionMode.PERFORM, EventObserver::modifyCursor); + }), + WINDOW_LOSE_FOCUS(WindowEvent.WINDOW_LOST_FOCUS, mapper -> { + mapper.map(ActionMode.PERFORM, EventObserver::restoreCursor); + }, ActionMode.PERFORM), + COMPONENT_MOVE(ComponentEvent.COMPONENT_MOVED, ActionMode.PERFORM), + MOUSE_EXIT(MouseEvent.MOUSE_EXITED, ActionMode.PERFORM), + /** + * We need to take charge of various scenarios such as what to do with mouse and keys when the + * window lose focus, how to enter/return from alt-tab/full-screen switch, how to behave + * when the use crucially drag our window over all the desktop... + */ + RELATIONS(relationMapper -> { + // Add keyDown for Print Screen because he doesn't send one + relationMapper.map(RelationType.DEPEND, Relate(KEY_RELEASE, KEY_PRESS)); + + /** + * After the window is opened, it must disable its own event, but capture all the keyboard and mouse input + */ + relationMapper.map(RelationType.DISABLE, Relate(WINDOW_OPEN, WINDOW_OPEN)); + relationMapper.map(RelationType.ENABLE, Relate(WINDOW_OPEN, + WINDOW_LOSE_FOCUS, KEY_PRESS, KEY_RELEASE, KEY_TYPE, MOUSE_ENTER, MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE + )); + + /** + * On any activation/reconfiguration/resize/restore-from-something, request focus in window + */ + relationMapper.map(RelationType.CAUSE, Relate(WINDOW_ACTIVATE, WINDOW_ACTIVATE)); + relationMapper.map(RelationType.CAUSE, Relate(WINDOW_DEICONIFY, WINDOW_ACTIVATE)); + relationMapper.map(RelationType.CAUSE, Relate(COMPONENT_RESIZE, WINDOW_ACTIVATE)); + + /** + * This set of rules are for ultimately releasing any capture on mouse and keyboard, + * and also releases all pressed keys and mouse buttons. + * + * Disables itself too, but enables event on the focus return. + */ + relationMapper.map(RelationType.REVERT, Relate(WINDOW_LOSE_FOCUS, KEY_PRESS, MOUSE_PRESS)); + relationMapper.map(RelationType.DISABLE, Relate(WINDOW_LOSE_FOCUS, WINDOW_LOSE_FOCUS, + KEY_PRESS, KEY_RELEASE, KEY_TYPE, MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE, MOUSE_ENTER + )); + + /** + * The next set of rules is for active focus gain. It could be done in two ways: + * natural, when window become visible topmost window with active borders, + * and when you click with mouse into the unfocused window. + * + * For clicky way, it must cause window focus and immediate capture of the mouse. + * Enables back losing focus, disables itself and natural focus gain. + */ + relationMapper.map(RelationType.ENABLE, Relate(WINDOW_LOSE_FOCUS, + WINDOW_GAIN_FOCUS, MOUSE_CLICK + )); + relationMapper.map(RelationType.ENABLE, Relate(MOUSE_CLICK, + WINDOW_LOSE_FOCUS, KEY_PRESS, KEY_RELEASE, KEY_TYPE, MOUSE_ENTER, MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE + )); + relationMapper.map(RelationType.DISABLE, Relate(MOUSE_CLICK, WINDOW_GAIN_FOCUS, MOUSE_CLICK)); + + /** + * For natural way, focus gain *must not* capture the mouse immediately, only after it enters the window. + * Enables back losing focus, disables itself and clicky way of capture. + */ + relationMapper.map(RelationType.ENABLE, Relate(WINDOW_GAIN_FOCUS, + WINDOW_LOSE_FOCUS, KEY_PRESS, KEY_RELEASE, KEY_TYPE, MOUSE_ENTER + )); + relationMapper.map(RelationType.DISABLE, Relate(WINDOW_GAIN_FOCUS, WINDOW_GAIN_FOCUS)); + + /** + * When the mouse returns to the window, it should be captured back, and the event disabled. + */ + relationMapper.map(RelationType.ENABLE, Relate(MOUSE_ENTER, MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE)); + relationMapper.map(RelationType.DISABLE, Relate(MOUSE_ENTER, MOUSE_ENTER)); + + /** + * The last scenario is component move. Example of it - user drags the window by its head. + * This way, first window is activated and gained focus, than component moved. Normally, the mouse would + * go into the window position and MOUSE_ENTER will be processed. We do not need it. If the user drags window, + * he then should manually click inside it to regain mouse capture - or alt-tab twice (regain window focus) + */ + relationMapper.map(RelationType.DISABLE, Relate(COMPONENT_MOVE, + MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE, MOUSE_ENTER + )); + relationMapper.map(RelationType.ENABLE, Relate(COMPONENT_MOVE, MOUSE_CLICK)); + }); + + public static void menuCaptureChanges(EventObserver observer, boolean capture) { + if (capture) { + observer.enableAction(MOUSE_MOVE, ActionMode.PERFORM); + observer.enableAction(MOUSE_DRAG, ActionMode.PERFORM); + observer.enableAction(MOUSE_PRESS, ActionMode.PERFORM); + observer.enableAction(MOUSE_RELEASE, ActionMode.PERFORM); + observer.enableAction(MOUSE_ENTER, ActionMode.PERFORM); + observer.disableAction(MOUSE_CLICK, ActionMode.PERFORM); + observer.centreCursor(null); + } else { + observer.disableAction(MOUSE_MOVE, ActionMode.PERFORM); + observer.disableAction(MOUSE_DRAG, ActionMode.PERFORM); + observer.disableAction(MOUSE_PRESS, ActionMode.PERFORM); + observer.disableAction(MOUSE_RELEASE, ActionMode.PERFORM); + observer.disableAction(MOUSE_ENTER, ActionMode.PERFORM); + observer.enableAction(MOUSE_CLICK, ActionMode.PERFORM); + observer.restoreCursor(null); + } + } + + public static void fullscreenChanges(EventObserver observer, boolean fullscreen) { + /** + * Clear any holding keys + */ + observer.cancelKeys(null); + if (fullscreen) { + /** + * When in full-screen mode, COMPONENT_RESIZE is fired when you get the game visible + * (immediately after switch, or after return from alt-tab) + */ + observer.mapRelation(COMPONENT_RESIZE, RelationType.ENABLE, WINDOW_OPEN, + WINDOW_LOSE_FOCUS, KEY_PRESS, KEY_RELEASE, KEY_TYPE, MOUSE_ENTER, MOUSE_MOVE, MOUSE_DRAG, MOUSE_PRESS, MOUSE_RELEASE + ); + + /** + * COMPONENT_MOVE is fired often in full-screen mode and does not mean that used did + * something with the window frame, actually there is no frame, there is no sense - disable it + */ + observer.disableAction(COMPONENT_MOVE, ActionMode.PERFORM); + } else { + /** + * Remove full-screen COMPONENT_RESIZE relations, if they was added earlier + */ + observer.unmapRelation(COMPONENT_RESIZE, RelationType.ENABLE); + + /** + * Immediately after return from full-screen mode, a bunch of events will occur, + * some of them will cause mouse capture to be lost. Disable them. + */ + observer.disableAction(WINDOW_LOSE_FOCUS, ActionMode.PERFORM); + observer.disableAction(COMPONENT_MOVE, ActionMode.PERFORM); + + /** + * The last of the bunch of events should be WINDOW_ACTIVATE, add a function to him + * to restore the proper reaction on events we have switched off. It also should remove + * this function after it fired. + */ + observer.mapAction(WINDOW_ACTIVATE, ActionMode.PERFORM, (ob, ev) -> { + observer.unmapAction(WINDOW_ACTIVATE, ActionMode.PERFORM); + observer.enableAction(WINDOW_LOSE_FOCUS, ActionMode.PERFORM); + observer.enableAction(COMPONENT_MOVE, ActionMode.PERFORM); + }); + } + } + + private static EventAction mouseMoveAction(boolean isDrag) { + return (observer, ev) -> { + // Do not send robot-generated moves (centering) to the underlying DOOM's event engine + if (observer.mouseEvent.robotMove) { + observer.mouseEvent.robotMove = false; + return; + } + + final int centreX = observer.component.getWidth() >> 1, centreY = observer.component.getHeight() >> 1; + if (observer.component.isShowing() && EventObserver.MOUSE_ROBOT.isPresent()) { + final Point offset = observer.component.getLocationOnScreen(); + observer.mouseEvent.moveIn((MouseEvent) ev, EventObserver.MOUSE_ROBOT.get(), offset, centreX, centreY, isDrag); + } else { + observer.mouseEvent.moveIn((MouseEvent) ev, centreX, centreY, isDrag); + } + + if (observer.mouseEvent.processed) { + observer.mouseEvent.resetNotify(); + observer.feed(observer.mouseEvent); + } + }; + } + + final int eventId; + final Set enabled; + final Map> actions; + final Map> adjustments; + final Map> cooperations; + + private EventHandler(final Consumer> relationMapper) { + this.eventId = -1; + this.actions = Collections.emptyMap(); + this.adjustments = Collections.emptyMap(); + this.cooperations = Collections.emptyMap(); + this.enabled = Collections.emptySet(); + relationMapper.accept((type, relations) + -> Stream.of(relations).forEach(relation + -> (type.affection == RelationAffection.COOPERATES + ? relation.sourceHandler.cooperations + : relation.sourceHandler.adjustments).compute(type, (t, set) -> { + (set == null ? (set = new HashSet<>()) : set).add(relation.targetHandler); + return set; + }) + ) + ); + } + + private EventHandler(final int eventId, final ActionMode... enableModes) { + this(eventId, null, enableModes); + } + + private EventHandler(final int eventId, final Consumer> actionMapper, final ActionMode... enableModes) { + this.eventId = eventId; + this.actions = new EnumMap<>(ActionMode.class); + this.enabled = EnumSet.noneOf(ActionMode.class); + this.enabled.addAll(Arrays.asList(enableModes)); + + this.adjustments = new EnumMap<>(RelationType.class); + this.cooperations = new EnumMap<>(RelationType.class); + + if (actionMapper != null) { + actionMapper.accept(actions::put); + } + } + + /** + * Interface implementation + */ + @Override + public Set defaultEnabledActions() { + return enabled; + } + + @Override + public Map> allActions() { + return actions; + } + + @Override + public Map> cooperations() { + return cooperations; + } + + @Override + public Map> adjustments() { + return adjustments; + } + + /** + * A hack to make this Enum implementation sortable by primitive integers in another way then by ordinal() + * The hack consists of implementing IntSupplier interface, this method and EVENT_SORT Comparator constant + */ + @Override + public int getAsInt() { + return eventId; + } +} \ No newline at end of file diff --git a/doom/src/awt/EventObserver.java b/doom/src/awt/EventObserver.java new file mode 100644 index 0000000..80a469a --- /dev/null +++ b/doom/src/awt/EventObserver.java @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package awt; + +import awt.EventBase.ActionMode; +import awt.EventBase.ActionStateHolder; +import awt.EventBase.EventAction; +import awt.EventBase.KeyStateHolder; +import awt.EventBase.RelationType; +import static awt.EventBase.findById; +import static awt.EventBase.sortHandlers; +import doom.event_t; +import doom.evtype_t; +import g.Signals; +import java.awt.AWTEvent; +import java.awt.AWTException; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.im.InputContext; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * Observer for AWTEvents.The description would be short in contrary to the description + of EventHandler Enum. This class uses rules in Handler extends Enum & EventBase + * to react on AWTEvent events given to him by some listener (or by fake, don't matter) and feeds them + * to someone who needs them (DOOM's internal event handling system) + * + * Also, you may use any Enum & EventBase dictionary, not just EventHandler. + * It may be useful if you design a game with several modes or with several systems, or something, + * and you need one part to react in one way, another part in another. + * + * @author Good Sign + * @param the type of the handler + */ +public class EventObserver & EventBase> { + + static final Optional MOUSE_ROBOT = createRobot(); + private static final Logger LOGGER = Loggers.getLogger(EventObserver.class.getName()); + + /** + * The Robot does not necessary gets created. When not, it throws an exception. + * We ignore that exception, and set Robot to null. So, any call to Robot + * must first check against null. So I've just made it Optional - for no headache. + * - Good Sign 2017/04/24 + * + * In my opinion, its better turn off mouse at all, then without Robot. + * But the support to run without it, though untested, must be present. + * - Good Sign 2017/04/22 + * + * Create AWT Robot for forcing mouse + * + * @author Good Sign + * @author vekltron + */ + private static Optional createRobot() { + try { + return Optional.of(new Robot()); + } catch (AWTException e) { + LOGGER.log(Level.SEVERE, "AWT Robot could not be created, mouse input focus will be loose!", e); + } + return Optional.empty(); + } + + /** + * NASTY hack to hide the cursor. + * + * Create a 'hidden' cursor by using a transparent image + * ...return the invisible cursor + * @author vekltron + */ + private Cursor createHiddenCursor() { + final Toolkit tk = Toolkit.getDefaultToolkit(); + final Dimension dim = tk.getBestCursorSize(2, 2); + if (dim.width == 0 || dim.height == 0) { + return this.initialCursor; + } + final BufferedImage transparent = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); + return tk.createCustomCursor(transparent, new Point(1, 1), "HiddenCursor"); + } + + /** + * This event here is used as a static scratch copy. When sending out + * messages, its contents are to be actually copied (struct-like). + * This avoids the continuous object creation/destruction overhead, + * And it also allows creating "sticky" status. + * + * Also, as I've made event_t.mouseevent_t fields volatile, there is + * no more need to synchronize on it in the multithread event listening solutions. + */ + protected final event_t.mouseevent_t mouseEvent = new event_t.mouseevent_t(evtype_t.ev_mouse, 0, 0, 0); + + /** + * Shared state of keys + */ + protected final KeyStateHolder keyStateHolder; + + /** + * Component (Canvas or JPanel, for exaple) to deal with + */ + protected final Component component; + + /** + * This one will be given all event_t's we produce there + */ + private final Consumer doomEventConsumer; + + /** + * Will be used to find Handler by AWTEvent's id + */ + private final Handler[] eventSortedHandlers; + + /** + * Shared state of actions + */ + private final ActionStateHolder actionStateHolder; + + /** + * Presumably a system Cursor, that is to be used on cursor restore. + */ + private final Cursor initialCursor; + + /** + * Invisible cursor on the systems who support changing cursors + */ + private final Cursor hiddenCursor; + + /** + * To construct the Observer you only need to provide it with the class of Enum used + * to contain dictionary, the Component it will be working on and acceptor of event_t's + */ + public EventObserver(Class handlerClass, Component component, Consumer doomEventConsumer) { + this.actionStateHolder = new ActionStateHolder<>(handlerClass, this); + this.eventSortedHandlers = sortHandlers(handlerClass.getEnumConstants()); + this.doomEventConsumer = doomEventConsumer; + this.component = component; + this.initialCursor = component.getCursor(); + this.hiddenCursor = createHiddenCursor(); + this.keyStateHolder = new KeyStateHolder<>(); + } + + public EventObserver addInterest(EventBase.KeyStateInterest interest) { + keyStateHolder.addInterest(interest); + return this; + } + + public EventObserver removeInterest(EventBase.KeyStateInterest interest) { + keyStateHolder.removeInterest(interest); + return this; + } + + /** + * This method is designed to acquire events from some kind of listener. + * EventHandler class do not provide listener itself - but should work with any. + */ + public void observe(final AWTEvent ev) { + final Handler handler = findById(eventSortedHandlers, ev.getID()); + if (handler == null || !actionStateHolder.hasActionsEnabled(handler, ActionMode.PERFORM)) { + return; + } + + // In case of debug. If level > FINE (most of cases) it will not affect anything + Loggers.LogEvent(LOGGER, actionStateHolder, handler, ev); + + actionStateHolder.run(handler, ActionMode.PERFORM, ev); + actionStateHolder.adjustments(handler).forEach((relation, affected) -> { + switch (relation.affection) { + case ENABLES: + affected.forEach(h -> { + actionStateHolder.enableAction(h, relation.affectedMode); + }); + return; + case DISABLES: + affected.forEach(h -> { + actionStateHolder.disableAction(h, relation.affectedMode); + }); + default: + break; + } + }); + + actionStateHolder.cooperations(handler, RelationType.CAUSE).forEach(h -> { + actionStateHolder.run(h, ActionMode.CAUSE, ev); + }); + + actionStateHolder.cooperations(handler, RelationType.REVERT).forEach(h -> { + actionStateHolder.run(h, ActionMode.REVERT, ev); + }); + } + + /** + * The way to supply underlying engine with event generated by this class + * This function is the last barrier between user input and DOOM's internal event hell. + * So there are all user key interests checked. + */ + protected void feed(final event_t ev) { + if (!ev.ifKey(sc -> keyStateHolder.notifyKeyChange(this, sc, ev.isType(evtype_t.ev_keydown)))) { + doomEventConsumer.accept(ev); + } + } + + /** + * Restore default system cursor over the window + */ + protected void restoreCursor(final AWTEvent event) { + component.setCursor(initialCursor); + } + + /** + * Hide cursor + */ + protected void modifyCursor(final AWTEvent event) { + InputContext inputContext = component.getInputContext(); + if (inputContext != null) { + inputContext.selectInputMethod(java.util.Locale.US); + } + component.setCursor(hiddenCursor); + } + + /** + * Move the cursor into the centre of the window. The event_t.mouseevent_t implementation + * would set robotMove flag for us to be able to distinguish the Robot-caused moves + * and not react on them (thus preventing look to be stuck or behave weird) + * - Good Sign 2017/04/24 + */ + protected void centreCursor(final AWTEvent event) { + if (component.isShowing()) { + int centreX = component.getWidth() >> 1; + int centreY = component.getHeight() >> 1; + MOUSE_ROBOT.ifPresent(rob -> mouseEvent.resetIn(rob, component.getLocationOnScreen(), centreX, centreY)); + } + modifyCursor(event); + } + + /** + * Forcibly clear key events in the underlying engine + */ + protected void cancelKeys(final AWTEvent ev) { + feed(event_t.CANCEL_KEYS); + keyStateHolder.removeAllKeys(); + } + + /** + * Forcibly clear mouse events in the underlying engine, discard cursor modifications + */ + protected void cancelMouse(final AWTEvent ev) { + feed(event_t.CANCEL_MOUSE); + } + + /** + * Send key releases to underlying engine + */ + protected void sendKeyUps(final AWTEvent ev) { + feed(Signals.getScanCode((KeyEvent) ev).doomEventUp); + discardInputEvent(ev); + } + + /** + * Send key presses to underlying engine + */ + protected void sendKeyDowns(final AWTEvent ev) { + feed(Signals.getScanCode((KeyEvent) ev).doomEventDown); + discardInputEvent(ev); + } + + /** + * Consumes InputEvents so they will not pass further + */ + protected void discardInputEvent(final AWTEvent ev) { + try { + ((InputEvent) ev).consume(); + } catch (ClassCastException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } + } + + protected final void enableAction(final Handler h, ActionMode mode) { + actionStateHolder.enableAction(h, mode); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("ENABLE ACTION: %s [%s]", h, mode)); + } + } + + protected final void disableAction(final Handler h, ActionMode mode) { + actionStateHolder.disableAction(h, mode); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("DISABLE ACTION: %s [%s]", h, mode)); + } + } + + @SafeVarargs + protected final void mapRelation(final Handler h, RelationType type, Handler... targets) { + if (type.affection == EventBase.RelationAffection.COOPERATES) { + actionStateHolder.mapCooperation(h, type, targets); + } else { + actionStateHolder.mapAdjustment(h, type, targets); + } + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("RELATION MAPPING: %s -> [%s] {%s}", h, type, Arrays.toString(targets))); + } + } + + @SafeVarargs + protected final void unmapRelation(final Handler h, RelationType type, Handler... targets) { + if (type.affection == EventBase.RelationAffection.COOPERATES) { + actionStateHolder.unmapCooperation(h, type, targets); + } else { + actionStateHolder.unmapAdjustment(h, type, targets); + } + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("RELATION UNMAP: %s -> [%s] {%s}", h, type, Arrays.toString(targets))); + } + } + + @SafeVarargs + protected final void restoreRelation(final Handler h, RelationType type, Handler... targets) { + if (type.affection == EventBase.RelationAffection.COOPERATES) { + actionStateHolder.restoreCooperation(h, type, targets); + } else { + actionStateHolder.restoreAdjustment(h, type, targets); + } + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("RELATION RESTORE: %s -> [%s] {%s}", h, type, Arrays.toString(targets))); + } + } + + protected void mapAction(final Handler h, ActionMode mode, EventAction remap) { + actionStateHolder.mapAction(h, mode, remap); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("ACTION MAPPING (MAP): %s [%s]", h, mode)); + } + } + + protected void remapAction(final Handler h, ActionMode mode, EventAction remap) { + actionStateHolder.remapAction(h, mode, remap); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("ACTION MAPPING (REMAP): %s [%s]", h, mode)); + } + } + + protected void unmapAction(final Handler h, ActionMode mode) { + actionStateHolder.unmapAction(h, mode); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("UNMAP ACTION: %s [%s]", h, mode)); + } + } + + protected void restoreAction(final Handler h, ActionMode mode) { + actionStateHolder.restoreAction(h, mode); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, () -> String.format("RESTORE ACTION: %s [%s]", h, mode)); + } + } +} \ No newline at end of file diff --git a/doom/src/awt/FullscreenOptions.java b/doom/src/awt/FullscreenOptions.java new file mode 100644 index 0000000..7e6ae51 --- /dev/null +++ b/doom/src/awt/FullscreenOptions.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package awt; + +import java.awt.DisplayMode; +import java.awt.Graphics2D; +import java.awt.GraphicsDevice; +import java.awt.Image; +import static java.awt.RenderingHints.KEY_INTERPOLATION; +import static java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC; +import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR; +import static java.awt.RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; +import java.awt.image.ImageObserver; +import m.Settings; +import mochadoom.Engine; + +/** + * Full-screen switch and scale governor + * + * @author Good Sign + */ +public interface FullscreenOptions { + + enum InterpolationMode { + Nearest, Bilinear, Bicubic; + } + + enum StretchMode { + Centre( + (w, defW, h, defH) -> Math.min(defW, w), + (w, defW, h, defH) -> Math.min(defH, h), + (w, defW, h, defH) -> Math.max(0, (w - defW) / 2), + (w, defW, h, defH) -> Math.max(0, (h - defH) / 2) + ), Stretch( + (w, defW, h, defH) -> w, + (w, defW, h, defH) -> h, + (w, defW, h, defH) -> 0, + (w, defW, h, defH) -> 0 + ), Fit( + (w, defW, h, defH) -> (int) (defW * minScale(w, defW, h, defH)), + (w, defW, h, defH) -> (int) (defH * minScale(w, defW, h, defH)), + (w, defW, h, defH) -> (w - (int) (defW * minScale(w, defW, h, defH))) / 2, + (w, defW, h, defH) -> (h - (int) (defH * minScale(w, defW, h, defH))) / 2 + ), Aspect_4_3( + (w, defW, h, defH) -> Fit.widthFun.fit(w, defW, h, (int) (defH * 1.2f)), + (w, defW, h, defH) -> Fit.heightFun.fit(w, defW, h, (int) (defH * 1.2f)), + (w, defW, h, defH) -> Fit.offsXFun.fit(w, defW, h, (int) (defH * 1.2f)), + (w, defW, h, defH) -> Fit.offsYFun.fit(w, defW, h, (int) (defH * 1.2f)) + ); + + final Fitter widthFun, heightFun, offsXFun, offsYFun; + + private StretchMode( + final Fitter widthFun, + final Fitter heightFun, + final Fitter offsXFun, + final Fitter offsYFun + ) { + this.widthFun = widthFun; + this.heightFun = heightFun; + this.offsXFun = offsXFun; + this.offsYFun = offsYFun; + } + + private static float minScale(int w, int defW, int h, int defH) { + float scaleX = w / (float) defW; + float scaleY = h / (float) defH; + return Math.min(scaleX, scaleY); + } + } + + enum FullMode { + Best, Native; + } + + static FullMode FULLMODE = Engine.getConfig().getValue(Settings.fullscreen_mode, FullMode.class); + static StretchMode STRETCH = Engine.getConfig().getValue(Settings.fullscreen_stretch, StretchMode.class); + static InterpolationMode INTERPOLATION = Engine.getConfig().getValue(Settings.fullscreen_interpolation, InterpolationMode.class); + + interface Dimension { + + int width(); + + int height(); + + int defWidth(); + + int defHeight(); + + default int fitX() { + return STRETCH.widthFun.fit(width(), defWidth(), height(), defHeight()); + } + + default int fitY() { + return STRETCH.heightFun.fit(width(), defWidth(), height(), defHeight()); + } + + default int offsX() { + return STRETCH.offsXFun.fit(width(), defWidth(), height(), defHeight()); + } + + default int offsY() { + return STRETCH.offsYFun.fit(width(), defWidth(), height(), defHeight()); + } + } + + interface Fitter { + + int fit(int width, int defWidth, int height, int defHeight); + } + + default void applyFullscreenOptions(Graphics2D graphics) { + switch (INTERPOLATION) { + case Nearest: + graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + break; + case Bilinear: + graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); + break; + case Bicubic: + graphics.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC); + break; + } + } + + default void draw(Graphics2D graphics, Image image, Dimension dim, ImageObserver observer) { + graphics.drawImage(image, dim.offsX(), dim.offsY(), dim.fitX(), dim.fitY(), observer); + } + + default FullscreenFunction createFullSwitcher(final GraphicsDevice device) { + switch (FULLMODE) { + case Best: + return new FullscreenSwitch(device, new DisplayModePicker(device)); + case Native: + return (w, h) -> device.getDisplayMode(); + } + + throw new Error(String.format("Unsupported mode: %s", String.valueOf(FULLMODE))); + } + + @FunctionalInterface + interface FullscreenFunction { + + DisplayMode get(int width, int height); + } + + static class FullscreenSwitch implements FullscreenFunction { + + private final GraphicsDevice dev; + private final DisplayModePicker dmp; + private DisplayMode oldDisplayMode; + private DisplayMode displayMode; + + private FullscreenSwitch(GraphicsDevice dev, DisplayModePicker dmp) { + this.dev = dev; + this.dmp = dmp; + } + + @Override + public DisplayMode get(final int width, final int height) { + if (oldDisplayMode == null) { + // In case we need to revert. + oldDisplayMode = dev.getDisplayMode(); + // TODO: what if bit depths are too small? + displayMode = dmp.pickClosest(width, height); + } else { + // We restore the original resolution + displayMode = oldDisplayMode; + oldDisplayMode = null; + } + + return displayMode; + } + } +} \ No newline at end of file diff --git a/doom/src/awt/MsgBox.java b/doom/src/awt/MsgBox.java new file mode 100644 index 0000000..281adf1 --- /dev/null +++ b/doom/src/awt/MsgBox.java @@ -0,0 +1,98 @@ +package awt; + +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Panel; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JLabel; + +/** A convenient message box class to pop up here and there. + * + * @author zyklon + * + */ +public class MsgBox extends Dialog implements ActionListener { + + /** + * + */ + private static final long serialVersionUID = -872019680203708495L; + private Button ok, can; + private boolean isOk = false; + + /* + * * @param frame parent frame + * + * @param msg message to be displayed + * + * @param okcan true : ok cancel buttons, false : ok button only + */ + public boolean isOk() { + return isOk; + } + + public MsgBox(Frame frame, String title, String msg, boolean okcan) { + super(frame, title, true); + setLayout(new BorderLayout()); + add("Center", new JLabel(msg)); + addOKCancelPanel(okcan); + createFrame(); + pack(); + setVisible(true); + if (this.can != null) { + this.can.requestFocus(); + } + } + + public MsgBox(Frame frame, String msg) { + this(frame, "Message", msg, false); + } + + private void addOKCancelPanel(boolean okcan) { + Panel p = new Panel(); + p.setLayout(new FlowLayout()); + createOKButton(p); + if (okcan == true) { + createCancelButton(p); + } + add("South", p); + } + + private void createOKButton(Panel p) { + p.add(ok = new Button("OK")); + ok.addActionListener(this); + } + + private void createCancelButton(Panel p) { + p.add(can = new Button("Cancel")); + can.addActionListener(this); + } + + private void createFrame() { + Dimension d = getToolkit().getScreenSize(); + setLocation(d.width / 3, d.height / 3); + } + + @Override + public void actionPerformed(ActionEvent ae) { + if (ae.getSource() == ok) { + isOk = true; + setVisible(false); + } else if (can != null && ae.getSource() == can) { + setVisible(false); + } + } + + /* + * public static void main(String args[]) { //Frame f = new Frame(); + * //f.setSize(200,200); //f.setVisible(true); MsgBox message = new MsgBox + * (null , "Hey you user, are you sure ?", true); if (message.isOk) + * System.out.println("Ok pressed"); if (!message.isOk) + * System.out.println("Cancel pressed"); message.dispose(); } + */ +} \ No newline at end of file diff --git a/doom/src/boom/Compatibility.java b/doom/src/boom/Compatibility.java new file mode 100644 index 0000000..dad5301 --- /dev/null +++ b/doom/src/boom/Compatibility.java @@ -0,0 +1,132 @@ +package boom; + +/** cph - move compatibility levels here so we can use them in d_server.c + * + * @author cph + * + */ +public final class Compatibility { + + /** Doom v1.2 */ + public static final int doom_12_compatibility = 0; + + public static final int doom_1666_compatibility = 1; + /* Doom v1.666 */ + public static final int doom2_19_compatibility = 2; + /* Doom & Doom 2 v1.9 */ + public static final int ultdoom_compatibility = 3; + /* Ultimate Doom and Doom95 */ + public static final int finaldoom_compatibility = 4; + /* Final Doom */ + public static final int dosdoom_compatibility = 5; + /* DosDoom 0.47 */ + public static final int tasdoom_compatibility = 6; + /* TASDoom */ + public static final int boom_compatibility_compatibility = 7; + /* Boom's compatibility mode */ + public static final int boom_201_compatibility = 8; + /* Boom v2.01 */ + public static final int boom_202_compatibility = 9; + /* Boom v2.02 */ + public static final int lxdoom_1_compatibility = 10; + /* LxDoom v1.3.2+ */ + public static final int mbf_compatibility = 11; + /* MBF */ + public static final int prboom_1_compatibility = 12; + /* PrBoom 2.03beta? */ + public static final int prboom_2_compatibility = 13; + /* PrBoom 2.1.0-2.1.1 */ + public static final int prboom_3_compatibility = 14; + /* PrBoom 2.2.x */ + public static final int prboom_4_compatibility = 15; + /* PrBoom 2.3.x */ + public static final int prboom_5_compatibility = 16; + /* PrBoom 2.4.0 */ + public static final int prboom_6_compatibility = 17; + /* Latest PrBoom */ + public static final int MAX_COMPATIBILITY_LEVEL = 18; + /* Must be last entry */ + /* Aliases follow */ + public static final int boom_compatibility = boom_201_compatibility; + /* Alias used by G_Compatibility */ + public static final int best_compatibility = prboom_6_compatibility; + + public static final prboom_comp_t[] prboom_comp = { + new prboom_comp_t(0xffffffff, 0x02020615, false, "-force_monster_avoid_hazards"), + new prboom_comp_t(0x00000000, 0x02040601, false, "-force_remove_slime_trails"), + new prboom_comp_t(0x02020200, 0x02040801, false, "-force_no_dropoff"), + new prboom_comp_t(0x00000000, 0x02040801, false, "-force_truncated_sector_specials"), + new prboom_comp_t(0x00000000, 0x02040802, false, "-force_boom_brainawake"), + new prboom_comp_t(0x00000000, 0x02040802, false, "-force_prboom_friction"), + new prboom_comp_t(0x02020500, 0x02040000, false, "-reject_pad_with_ff"), + new prboom_comp_t(0xffffffff, 0x02040802, false, "-force_lxdoom_demo_compatibility"), + new prboom_comp_t(0x00000000, 0x0202061b, false, "-allow_ssg_direct"), + new prboom_comp_t(0x00000000, 0x02040601, false, "-treat_no_clipping_things_as_not_blocking"), + new prboom_comp_t(0x00000000, 0x02040803, false, "-force_incorrect_processing_of_respawn_frame_entry"), + new prboom_comp_t(0x00000000, 0x02040601, false, "-force_correct_code_for_3_keys_doors_in_mbf"), + new prboom_comp_t(0x00000000, 0x02040601, false, "-uninitialize_crush_field_for_stairs"), + new prboom_comp_t(0x00000000, 0x02040802, false, "-force_boom_findnexthighestfloor"), + new prboom_comp_t(0x00000000, 0x02040802, false, "-allow_sky_transfer_in_boom"), + new prboom_comp_t(0x00000000, 0x02040803, false, "-apply_green_armor_class_to_armor_bonuses"), + new prboom_comp_t(0x00000000, 0x02040803, false, "-apply_blue_armor_class_to_megasphere"), + new prboom_comp_t(0x02050001, 0x02050003, false, "-wrong_fixeddiv"), + new prboom_comp_t(0x02020200, 0x02050003, false, "-force_incorrect_bobbing_in_boom"), + new prboom_comp_t(0xffffffff, 0x00000000, false, "-boom_deh_parser"), + new prboom_comp_t(0x00000000, 0x02050007, false, "-mbf_remove_thinker_in_killmobj"), + new prboom_comp_t(0x00000000, 0x02050007, false, "-do_not_inherit_friendlyness_flag_on_spawn"), + new prboom_comp_t(0x00000000, 0x02050007, false, "-do_not_use_misc12_frame_parameters_in_a_mushroom") + }; + + public static final int PC_MONSTER_AVOID_HAZARDS = 0; + public static final int PC_REMOVE_SLIME_TRAILS = 1; + public static final int PC_NO_DROPOFF = 2; + public static final int PC_TRUNCATED_SECTOR_SPECIALS = 3; + public static final int PC_BOOM_BRAINAWAKE = 4; + public static final int PC_PRBOOM_FRICTION = 5; + public static final int PC_REJECT_PAD_WITH_FF = 6; + public static final int PC_FORCE_LXDOOM_DEMO_COMPATIBILITY = 7; + public static final int PC_ALLOW_SSG_DIRECT = 8; + public static final int PC_TREAT_NO_CLIPPING_THINGS_AS_NOT_BLOCKING = 9; + public static final int PC_FORCE_INCORRECT_PROCESSING_OF_RESPAWN_FRAME_ENTRY = 10; + public static final int PC_FORCE_CORRECT_CODE_FOR_3_KEYS_DOORS_IN_MBF = 11; + public static final int PC_UNINITIALIZE_CRUSH_FIELD_FOR_STAIRS = 12; + public static final int PC_FORCE_BOOM_FINDNEXTHIGHESTFLOOR = 13; + public static final int PC_ALLOW_SKY_TRANSFER_IN_BOOM = 14; + public static final int PC_APPLY_GREEN_ARMOR_CLASS_TO_ARMOR_BONUSES = 15; + public static final int PC_APPLY_BLUE_ARMOR_CLASS_TO_MEGASPHERE = 16; + public static final int PC_WRONG_FIXEDDIV = 17; + public static final int PC_FORCE_INCORRECT_BOBBING_IN_BOOM = 18; + public static final int PC_BOOM_DEH_PARSER = 19; + public static final int PC_MBF_REMOVE_THINKER_IN_KILLMOBJ = 20; + public static final int PC_DO_NOT_INHERIT_FRIENDLYNESS_FLAG_ON_SPAWN = 21; + public static final int PC_DO_NOT_USE_MISC12_FRAME_PARAMETERS_IN_A_MUSHROOM = 21; + public static final int PC_MAX = 23; + + public enum PC { + PC_MONSTER_AVOID_HAZARDS, + PC_REMOVE_SLIME_TRAILS, + PC_NO_DROPOFF, + PC_TRUNCATED_SECTOR_SPECIALS, + PC_BOOM_BRAINAWAKE, + PC_PRBOOM_FRICTION, + PC_REJECT_PAD_WITH_FF, + PC_FORCE_LXDOOM_DEMO_COMPATIBILITY, + PC_ALLOW_SSG_DIRECT, + PC_TREAT_NO_CLIPPING_THINGS_AS_NOT_BLOCKING, + PC_FORCE_INCORRECT_PROCESSING_OF_RESPAWN_FRAME_ENTRY, + PC_FORCE_CORRECT_CODE_FOR_3_KEYS_DOORS_IN_MBF, + PC_UNINITIALIZE_CRUSH_FIELD_FOR_STAIRS, + PC_FORCE_BOOM_FINDNEXTHIGHESTFLOOR, + PC_ALLOW_SKY_TRANSFER_IN_BOOM, + PC_APPLY_GREEN_ARMOR_CLASS_TO_ARMOR_BONUSES, + PC_APPLY_BLUE_ARMOR_CLASS_TO_MEGASPHERE, + PC_WRONG_FIXEDDIV, + PC_FORCE_INCORRECT_BOBBING_IN_BOOM, + PC_BOOM_DEH_PARSER, + PC_MBF_REMOVE_THINKER_IN_KILLMOBJ, + PC_DO_NOT_INHERIT_FRIENDLYNESS_FLAG_ON_SPAWN, + PC_DO_NOT_USE_MISC12_FRAME_PARAMETERS_IN_A_MUSHROOM, + PC_MAX + }; + +} \ No newline at end of file diff --git a/doom/src/boom/DeepBSPNodesV4.java b/doom/src/boom/DeepBSPNodesV4.java new file mode 100644 index 0000000..17d6c80 --- /dev/null +++ b/doom/src/boom/DeepBSPNodesV4.java @@ -0,0 +1,50 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import static utils.GenericCopy.malloc; +import w.CacheableDoomObject; + +public class DeepBSPNodesV4 implements CacheableDoomObject { + + public static final byte[] DeepBSPHeader = { + 'x', 'N', 'd', '4', 0, 0, 0, 0 + }; + + byte[] header = new byte[8]; + mapnode_v4_t[] nodes; + int numnodes; + + public boolean formatOK() { + return Arrays.equals(header, DeepBSPHeader); + } + + public mapnode_v4_t[] getNodes() { + return nodes; + } + + @Override + public void unpack(ByteBuffer buf) throws IOException { + int length = buf.capacity(); + + // Too short, not even header. + if (length < 8) { + return; + } + + numnodes = (length - 8) / mapnode_v4_t.sizeOf(); + + if (length < 1) { + return; + } + + buf.get(header); // read header + + nodes = malloc(mapnode_v4_t::new, mapnode_v4_t[]::new, length); + + for (int i = 0; i < length; i++) { + nodes[i].unpack(buf); + } + } +} \ No newline at end of file diff --git a/doom/src/boom/E6Y.java b/doom/src/boom/E6Y.java new file mode 100644 index 0000000..5258d21 --- /dev/null +++ b/doom/src/boom/E6Y.java @@ -0,0 +1,412 @@ +package boom; + +/* Emacs style mode select -*- Java -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * + *----------------------------------------------------------------------------- + */ +public interface E6Y { + + /* +#define HU_HUDADDX (HU_HUDX) +#define HU_HUDADDY (HU_HUDY+(-1)*HU_GAPY) +#define HU_CENTERMSGX (320/2) +#define HU_CENTERMSGY ((200-ST_HEIGHT)/2 - 1 - LittleShort(hu_font[0].height)) + +#define HU_HUDADDX_D (HU_HUDX_LL) +#define HU_HUDADDY_D (HU_HUDY_LL+(-1)*HU_GAPY) + +#define HU_MSGCENTERTIMEOUT (2*TICRATE) + */ + public static final String STSTR_SECRETFOUND = "A secret is revealed!"; + + public static final int S_CANT_GL_ARB_MULTITEXTURE = 0x10000000; + public static final int S_CANT_GL_ARB_MULTISAMPLEFACTOR = 0x20000000; + + public static final int GL_COMBINE_ARB = 0x8570; + public static final int GL_RGB_SCALE_ARB = 0x8573; + + public static final char NO_INDEX = 0xFFFF; + + public static final float FOV_CORRECTION_FACTOR = 1.13776f; + public static final int FOV90 = 90; + +//public static final double DEG2RAD( a ) ( a * Pi ) / 180.0f; +//#define RAD2DEG( a ) ( a / Pi ) * 180.0f; + public class buf_overrun_item_t { + + String wadname; + int map; + int address; + } + + public class camera_t { + + long x; + long y; + long z; + long PrevX; + long PrevY; + long PrevZ; + long angle; + long pitch; + long PrevAngle; + long PrevPitch; + int type; + } + + /* + +extern int REAL_SCREENWIDTH; +extern int REAL_SCREENHEIGHT; +extern int REAL_SCREENPITCH; + +extern dboolean wasWiped; + +extern int totalleveltimes; + +extern int secretfound; +extern int messagecenter_counter; +extern int demo_skiptics; +extern int demo_tics_count; +extern int demo_curr_tic; +extern int demo_playerscount; +extern char demo_len_st[80]; + +extern int avi_shot_time; +extern int avi_shot_num; +extern const char *avi_shot_fname; +extern char avi_shot_curr_fname[PATH_MAX]; + +extern dboolean doSkip; +extern dboolean demo_stoponnext; +extern dboolean demo_stoponend; +extern dboolean demo_warp; + +extern int key_speed_up; +extern int key_speed_down; +extern int key_speed_default; +extern int speed_step; +extern int key_nextlevel; +extern int key_demo_jointogame; +extern int key_demo_endlevel; +extern int key_walkcamera; +extern int key_showalive; + +extern int hudadd_gamespeed; +extern int hudadd_leveltime; +extern int hudadd_demotime; +extern int hudadd_secretarea; +extern int hudadd_smarttotals; +extern int hudadd_demoprogressbar; +extern int movement_strafe50; +extern int movement_shorttics; +extern int movement_strafe50onturns; +extern int movement_mouselook; +extern int movement_mouseinvert; +extern int movement_maxviewpitch; +extern int mouse_handler; +extern int mouse_doubleclick_as_use; +extern int render_multisampling; +extern int render_paperitems; +extern int render_wipescreen; +extern int mouse_acceleration; +extern int demo_overwriteexisting; + +extern int render_fov; +extern int render_aspect; +extern float render_ratio; +extern float render_fovratio; +extern float render_fovy; +extern float render_multiplier; +void M_ChangeAspectRatio(void); +void M_ChangeStretch(void); + +extern int misc_fastexit; + +extern int palette_ondamage; +extern int palette_onbonus; +extern int palette_onpowers; + +extern camera_t walkcamera; + +extern fixed_t sidemove_normal[2]; +extern fixed_t sidemove_strafe50[2]; + +extern int PitchSign; +extern int mouseSensitivity_mlook; +extern angle_t viewpitch; +extern float skyscale; +extern float screen_skybox_zplane; +extern float maxNoPitch[]; +extern float tan_pitch; +extern float skyUpAngle; +extern float skyUpShift; +extern float skyXShift; +extern float skyYShift; +extern dboolean mlook_or_fov; + +extern hu_textline_t w_hudadd; +extern hu_textline_t w_centermsg; +extern hu_textline_t w_precache; +extern char hud_add[80]; +extern char hud_centermsg[80]; + +void e6y_assert(const char *format, ...); + +void ParamsMatchingCheck(); +void e6y_InitCommandLine(void); + +void P_WalkTicker (); +void P_SyncWalkcam(dboolean sync_coords, dboolean sync_sight); +void P_ResetWalkcam(void); + +extern dboolean sound_inited_once; +void e6y_I_uSleep(unsigned long usecs); +void G_SkipDemoStart(void); +void G_SkipDemoStop(void); +void G_SkipDemoCheck(void); +int G_GotoNextLevel(void); + +#ifdef GL_DOOM +void M_ChangeMouseLook(void); +void M_ChangeMaxViewPitch(void); +void M_ChangeMouseInvert(void); +void M_ChangeFOV(void); +void M_ChangeUseDetail(void); +void M_ChangeMultiSample(void); +void M_ChangeSpriteClip(void); +void M_ChangeAllowBoomColormaps(void); +void M_ChangeTextureUseHires(void); +void M_ChangeAllowFog(void); +void M_ChangeTextureHQResize(void); +#endif +void M_ChangeRenderPrecise(void); +void M_ChangeSpeed(void); +void M_ChangeScreenMultipleFactor(void); +void M_ChangeInterlacedScanning(void); +void M_MouseMLook(int choice); +void M_MouseAccel(int choice); +void M_ChangeCompTranslucency(void); +void CheckPitch(signed int *pitch); +void I_Init2(void); + +#ifdef GL_DOOM +dboolean GetMouseLook(void); +dboolean HaveMouseLook(void); +#else +#define GetMouseLook() (0) +#define HaveMouseLook() (0) +#endif + +extern float viewPitch; +extern dboolean transparentpresent; + +void R_ClearClipSegs (void); +void R_RenderBSPNode(int bspnum); + +typedef struct prboom_comp_s +{ + unsigned int minver; + unsigned int maxver; + dboolean state; + const char *cmd; +} prboom_comp_t; + +enum +{ + PC_MONSTER_AVOID_HAZARDS, + PC_REMOVE_SLIME_TRAILS, + PC_NO_DROPOFF, + PC_TRUNCATED_SECTOR_SPECIALS, + PC_BOOM_BRAINAWAKE, + PC_PRBOOM_FRICTION, + PC_REJECT_PAD_WITH_FF, + PC_FORCE_LXDOOM_DEMO_COMPATIBILITY, + PC_ALLOW_SSG_DIRECT, + PC_TREAT_NO_CLIPPING_THINGS_AS_NOT_BLOCKING, + PC_FORCE_INCORRECT_PROCESSING_OF_RESPAWN_FRAME_ENTRY, + PC_FORCE_CORRECT_CODE_FOR_3_KEYS_DOORS_IN_MBF, + PC_UNINITIALIZE_CRUSH_FIELD_FOR_STAIRS, + PC_FORCE_BOOM_FINDNEXTHIGHESTFLOOR, + PC_ALLOW_SKY_TRANSFER_IN_BOOM, + PC_APPLY_GREEN_ARMOR_CLASS_TO_ARMOR_BONUSES, + PC_APPLY_BLUE_ARMOR_CLASS_TO_MEGASPHERE, + PC_WRONG_FIXEDDIV, + PC_FORCE_INCORRECT_BOBBING_IN_BOOM, + PC_BOOM_DEH_PARSER, + PC_MBF_REMOVE_THINKER_IN_KILLMOBJ, + PC_DO_NOT_INHERIT_FRIENDLYNESS_FLAG_ON_SPAWN, + PC_DO_NOT_USE_MISC12_FRAME_PARAMETERS_IN_A_MUSHROOM, + PC_MAX +}; + +extern prboom_comp_t prboom_comp[]; + +int StepwiseSum(int value, int direction, int step, int minval, int maxval, int defval); + +enum +{ + TT_ALLKILL, + TT_ALLITEM, + TT_ALLSECRET, + + TT_TIME, + TT_TOTALTIME, + TT_TOTALKILL, + TT_TOTALITEM, + TT_TOTALSECRET, + + TT_MAX +}; + +typedef struct timetable_s +{ + char map[16]; + + int kill[MAXPLAYERS]; + int item[MAXPLAYERS]; + int secret[MAXPLAYERS]; + + int stat[TT_MAX]; +} timetable_t; + +#ifdef _WIN32 +const char* WINError(void); +#endif + +extern int stats_level; +void e6y_G_DoCompleted(void); +void e6y_WriteStats(void); + +void e6y_G_DoWorldDone(void); + +void I_ProcessWin32Mouse(void); +void I_StartWin32Mouse(void); +void I_EndWin32Mouse(void); +int AccelerateMouse(int val); +void MouseAccelChanging(void); + +extern int mlooky; +extern int realtic_clock_rate; + +extern dboolean IsDehMaxHealth; +extern dboolean IsDehMaxSoul; +extern dboolean IsDehMegaHealth; +extern dboolean DEH_mobjinfo_bits[NUMMOBJTYPES]; + +extern int deh_maxhealth; +extern int deh_max_soul; +extern int deh_mega_health; + +extern int maxhealthbonus; + +void e6y_G_Compatibility(void); + +extern dboolean zerotag_manual; +extern int comperr_zerotag; +extern int comperr_passuse; +extern int comperr_hangsolid; + +dboolean compbad_get(int *compbad); + +dboolean ProcessNoTagLines(line_t* line, sector_t **sec, int *secnum); + +#define I_FindName(a) ((a)->Name) +#define I_FindAttr(a) ((a)->Attribs) + +typedef struct +{ + unsigned int Attribs; + unsigned int Times[3*2]; + unsigned int Size[2]; + unsigned int Reserved[2]; + char Name[PATH_MAX]; + char AltName[14]; +} findstate_t; + +char* PathFindFileName(const char* pPath); +void NormalizeSlashes2(char *str); +unsigned int AfxGetFileName(const char* lpszPathName, char* lpszTitle, unsigned int nMax); +void AbbreviateName(char* lpszCanon, int cchMax, int bAtLeastName); + +//extern int viewMaxY; + +extern dboolean isskytexture; + +void D_AddDehFile (const char *file, wad_source_t source); + +extern int levelstarttic; + +void I_AfterUpdateVideoMode(void); + +extern int force_singletics_to; + +dboolean HU_DrawDemoProgress(void); + +#ifdef ALL_IN_ONE +unsigned char* GetAllInOneLumpHandle(void); +#endif + +#ifdef _MSC_VER +int GetFullPath(const char* FileName, const char* ext, char *Buffer, size_t BufferLength); +#endif + +void I_vWarning(const char *message, va_list argList); +void I_Warning(const char *message, ...); + +#define PRB_MB_OK 0x00000000 +#define PRB_MB_OKCANCEL 0x00000001 +#define PRB_MB_ABORTRETRYIGNORE 0x00000002 +#define PRB_MB_YESNOCANCEL 0x00000003 +#define PRB_MB_YESNO 0x00000004 +#define PRB_MB_RETRYCANCEL 0x00000005 +#define PRB_MB_DEFBUTTON1 0x00000000 +#define PRB_MB_DEFBUTTON2 0x00000100 +#define PRB_MB_DEFBUTTON3 0x00000200 +#define PRB_IDOK 1 +#define PRB_IDCANCEL 2 +#define PRB_IDABORT 3 +#define PRB_IDRETRY 4 +#define PRB_IDIGNORE 5 +#define PRB_IDYES 6 +#define PRB_IDNO 7 +int I_MessageBox(const char* text, unsigned int type); + +dboolean SmoothEdges(unsigned char * buffer,int w, int h); + +#ifdef _WIN32 +extern int mus_extend_volume; +void I_midiOutSetVolumes(int volume); +#endif + +#endif + */ +} \ No newline at end of file diff --git a/doom/src/boom/ZNodeSegs.java b/doom/src/boom/ZNodeSegs.java new file mode 100644 index 0000000..b2c6372 --- /dev/null +++ b/doom/src/boom/ZNodeSegs.java @@ -0,0 +1,50 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import static utils.GenericCopy.malloc; +import w.CacheableDoomObject; + +public class ZNodeSegs implements CacheableDoomObject { + + private static final byte[] DeepBSPHeader = { + 'x', 'N', 'd', '4', 0, 0, 0, 0 + }; + + byte[] header; + mapseg_znod_t[] nodes; + int numnodes; + + public boolean formatOK() { + return Arrays.equals(header, DeepBSPHeader); + } + + public mapseg_znod_t[] getNodes() { + return nodes; + } + + @Override + public void unpack(ByteBuffer buf) throws IOException { + int length = buf.capacity(); + + // Too short, not even header. + if (length < 8) { + return; + } + + numnodes = (length - 8) / mapnode_v4_t.sizeOf(); + + if (length < 1) { + return; + } + + buf.get(header); // read header + + nodes = malloc(mapseg_znod_t::new, mapseg_znod_t[]::new, length); + + for (int i = 0; i < length; i++) { + nodes[i].unpack(buf); + } + } +} \ No newline at end of file diff --git a/doom/src/boom/mapglvertex_t.java b/doom/src/boom/mapglvertex_t.java new file mode 100644 index 0000000..6b64d84 --- /dev/null +++ b/doom/src/boom/mapglvertex_t.java @@ -0,0 +1,24 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +/** fixed 32 bit gl_vert format v2.0+ (glBsp 1.91) */ +public class mapglvertex_t implements CacheableDoomObject { + + public int x, y; // fixed_t + + public static int sizeOf() { + return 8; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + x = buf.getInt(); + y = buf.getInt(); + } +} \ No newline at end of file diff --git a/doom/src/boom/mapnode_v4_t.java b/doom/src/boom/mapnode_v4_t.java new file mode 100644 index 0000000..9c924a6 --- /dev/null +++ b/doom/src/boom/mapnode_v4_t.java @@ -0,0 +1,47 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; +import w.DoomBuffer; + +/** BSP Node structure on-disk */ +public class mapnode_v4_t + implements CacheableDoomObject { + + public mapnode_v4_t() { + this.bbox = new short[2][4]; + this.children = new int[2]; + } + + /** Partition line from (x,y) to x+dx,y+dy) */ + public short x, y, dx, dy; + + /** Bounding box for each child, clip against view frustum. */ + public short[][] bbox; + + /** If NF_SUBSECTOR its a subsector, else it's a node of another subtree. + * In simpler words: if the first bit is set, strip it and use the rest + * as a subtree index. Else it's a node index. + * */ + public int[] children = new int[2]; + + public static final int sizeOf() { + return (8 + 16 + 8); + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.x = buf.getShort(); + this.y = buf.getShort(); + this.dx = buf.getShort(); + this.dy = buf.getShort(); + DoomBuffer.readShortArray(buf, this.bbox[0], 4); + DoomBuffer.readShortArray(buf, this.bbox[1], 4); + DoomBuffer.readIntArray(buf, this.children, 2); + } + +} \ No newline at end of file diff --git a/doom/src/boom/mapnode_znod_t.java b/doom/src/boom/mapnode_znod_t.java new file mode 100644 index 0000000..8e54b16 --- /dev/null +++ b/doom/src/boom/mapnode_znod_t.java @@ -0,0 +1,43 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; +import w.DoomBuffer; + +public class mapnode_znod_t implements CacheableDoomObject { + + public short x; // Partition line from (x,y) to x+dx,y+dy) + public short y; + public short dx; + public short dy; + // Bounding box for each child, clip against view frustum. + public short[][] bbox; + // If NF_SUBSECTOR its a subsector, else it's a node of another subtree. + public int[] children; + + public mapnode_znod_t() { + this.bbox = new short[2][4]; + this.children = new int[2]; + } + + public static final int sizeOf() { + return (8 + 16 + 8); + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.x = buf.getShort(); + this.y = buf.getShort(); + this.dx = buf.getShort(); + this.dy = buf.getShort(); + DoomBuffer.readShortArray(buf, this.bbox[0], 4); + DoomBuffer.readShortArray(buf, this.bbox[1], 4); + DoomBuffer.readIntArray(buf, this.children, 2); + + } + +} \ No newline at end of file diff --git a/doom/src/boom/mapseg_v4_t.java b/doom/src/boom/mapseg_v4_t.java new file mode 100644 index 0000000..2cf5d12 --- /dev/null +++ b/doom/src/boom/mapseg_v4_t.java @@ -0,0 +1,44 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +/** + * LineSeg, generated by splitting LineDefs + * using partition lines selected by BSP builder. + * MAES: this is the ON-DISK structure. The corresponding memory structure, + * segs_t, has fixed_t members. + */ +public class mapseg_v4_t implements CacheableDoomObject { + + public mapseg_v4_t() { + + } + + public int v1; + public int v2; + public char angle; + public char linedef; + public char side; + public char offset; + + public static int sizeOf() { + return 16; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.v1 = buf.getInt(); + this.v2 = buf.getInt(); + this.angle = buf.getChar(); + this.linedef = buf.getChar(); + this.side = buf.getChar(); + this.offset = buf.getChar(); + + } + +}; \ No newline at end of file diff --git a/doom/src/boom/mapseg_znod_t.java b/doom/src/boom/mapseg_znod_t.java new file mode 100644 index 0000000..050d78f --- /dev/null +++ b/doom/src/boom/mapseg_znod_t.java @@ -0,0 +1,35 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +/** ZDoom node? + * + */ +public class mapseg_znod_t implements CacheableDoomObject { + + public mapseg_znod_t() { + + } + + public int v1, v2; // Those are unsigned :-/ + public char linedef; + public byte side; + + public static int sizeOf() { + return 11; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.v1 = buf.getInt(); + this.v2 = buf.getInt(); + this.linedef = buf.getChar(); + this.side = buf.get(); + } + +}; \ No newline at end of file diff --git a/doom/src/boom/mapsubsector_v4_t.java b/doom/src/boom/mapsubsector_v4_t.java new file mode 100644 index 0000000..b1431dd --- /dev/null +++ b/doom/src/boom/mapsubsector_v4_t.java @@ -0,0 +1,26 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +public class mapsubsector_v4_t implements CacheableDoomObject { + + public char numsegs; + /** Index of first one, segs are stored sequentially. */ + public int firstseg; + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.numsegs = buf.getChar(); + this.firstseg = buf.getInt(); + } + + public static int sizeOf() { + return 6; + } + +} \ No newline at end of file diff --git a/doom/src/boom/mapsubsector_znod_t.java b/doom/src/boom/mapsubsector_znod_t.java new file mode 100644 index 0000000..b2d6c07 --- /dev/null +++ b/doom/src/boom/mapsubsector_znod_t.java @@ -0,0 +1,24 @@ +package boom; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import utils.C2JUtils; +import w.CacheableDoomObject; + +public class mapsubsector_znod_t implements CacheableDoomObject { + + public long numsegs; + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.numsegs = C2JUtils.unsigned(buf.getInt()); + } + + public static final int sizeOf() { + return 4; + } + +} \ No newline at end of file diff --git a/doom/src/boom/prboom_comp_t.java b/doom/src/boom/prboom_comp_t.java new file mode 100644 index 0000000..4b1b545 --- /dev/null +++ b/doom/src/boom/prboom_comp_t.java @@ -0,0 +1,19 @@ +package boom; + +public class prboom_comp_t { + + public prboom_comp_t(int minver, int maxver, boolean state, String cmd) { + this.minver = minver; + this.maxver = maxver; + this.state = state; + this.cmd = cmd; + } + + public int minver; + + public int maxver; + + public boolean state; + + public String cmd; +} \ No newline at end of file diff --git a/doom/src/data/Defines.java b/doom/src/data/Defines.java new file mode 100644 index 0000000..e058132 --- /dev/null +++ b/doom/src/data/Defines.java @@ -0,0 +1,279 @@ +package data; + +//import m.define; +import static data.Limits.MAXINT; +import static data.Limits.MININT; +import defines.ammotype_t; +import defines.card_t; +import doom.weapontype_t; +import g.Signals; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.MAPFRACUNIT; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: Defines.java,v 1.48 2012/09/24 17:16:22 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Internally used data structures for virtually everything, +// key definitions, lots of other stuff. +// +//----------------------------------------------------------------------------- +//#ifndef __DOOMDEF__ +//#define __DOOMDEF__ +//#include +//#include +// +// Global parameters/defines. +// +// DOOM version +public final class Defines { + + /** Seems to be 109 for shareware 1.9, wtf is this*/ + public static final int VERSION = 109; + + public static final int JAVARANDOM_MASK = 0x80; + + /** Some parts of the code may actually be better used as if in a UNIX environment */ + public static final boolean NORMALUNIX = false; + + /** If rangecheck is undefined, ost parameter validation debugging code will not be compiled */ + public static final boolean RANGECHECK = false; + +// Do or do not use external soundserver. +// The sndserver binary to be run separately +// has been introduced by Dave Taylor. +// The integrated sound support is experimental, +// and unfinished. Default is synchronous. +// Experimental asynchronous timer based is +// handled by SNDINTR. +//#define SNDSERV 1 +//#define SNDINTR 1 +// Defines suck. C sucks. +// C++ might sucks for OOP, but it sure is a better C. +// So there. +// MAES: moved static defines out of here and into VideoScaleInfo. +// State updates, number of tics / second. + public static final int BASETICRATE = 35; + public static final int TIC_MUL = 1; + public static final int TICRATE = BASETICRATE * TIC_MUL; + +// +// Difficulty/skill settings/filters. +// +// Skill flags. + public static int MTF_EASY = 1; + public static int MTF_NORMAL = 2; + public static int MTF_HARD = 4; + +// Deaf monsters/do not react to sound. + public static int MTF_AMBUSH = 8; + +//Maes: this makes it a bit less retarded. + public static final int NUMCARDS = card_t.NUMCARDS.ordinal(); + +//Maes: this makes it a bit less retarded. + public static final int NUMWEAPONS = weapontype_t.NUMWEAPONS.ordinal(); + +//Maes: this makes it a bit less retarded. + public static final int NUMAMMO = ammotype_t.NUMAMMO.ordinal(); + +// Power up artifacts. + public static final int pw_invulnerability = 0; + public static final int pw_strength = 1; + public static final int pw_invisibility = 2; + public static final int pw_ironfeet = 3; + public static final int pw_allmap = 4; + public static final int pw_infrared = 5; + public static final int NUMPOWERS = 6; + + /** Power up durations, + * how many seconds till expiration, + * assuming TICRATE is 35 ticks/second. + */ + public static final int INVULNTICS = (30 * TICRATE), + INVISTICS = (60 * TICRATE), + INFRATICS = (120 * TICRATE), + IRONTICS = (60 * TICRATE); + +// Center command from Heretic + public final static int TOCENTER = -8; + +// from r_defs.h: +//Silhouette, needed for clipping Segs (mainly) +//and sprites representing things. + public static final int SIL_NONE = 0; + public static final int SIL_BOTTOM = 1; + public static final int SIL_TOP = 2; + public static final int SIL_BOTH = 3; + +//SKY, store the number for name. + static public final String SKYFLATNAME = "F_SKY1"; + +// The sky map is 256*128*4 maps. + public static final int ANGLETOSKYSHIFT = 22; + +// From r_draw.c +// status bar height at bottom of screen + public static final int SBARHEIGHT = 32; + +// +//Different vetween registered DOOM (1994) and +//Ultimate DOOM - Final edition (retail, 1995?). +//This is supposedly ignored for commercial +//release (aka DOOM II), which had 34 maps +//in one episode. So there. + public static final int NUMEPISODES = 4; + public static final int NUMMAPS = 9; + +//in tics +//U #define PAUSELEN (TICRATE*2) +//U #define SCORESTEP 100 +//U #define ANIMPERIOD 32 +//pixel distance from "(YOU)" to "PLAYER N" +//U #define STARDIST 10 +//U #define WK 1 +// MAES 23/5/2011: moved SP_... stuff to EndLevel + public static final int BACKUPTICS = 12; + +// From Zone: +// +//ZONE MEMORY +//PU - purge tags. +//Tags < 100 are not overwritten until freed. + public static final int PU_STATIC = 1; // static entire execution time + public static final int PU_SOUND = 2; // static while playing + public static final int PU_MUSIC = 3; // static while playing + public static final int PU_DAVE = 4; // anything else Dave wants static + public static final int PU_LEVEL = 50; // static until level exited + public static final int PU_LEVSPEC = 51; // a special thinker in a level +//Tags >= 100 are purgable whenever needed. + public static final int PU_PURGELEVEL = 100; + public static final int PU_CACHE = 101; + +// From hu_lib.h: +//font stuff + static public final Signals.ScanCode HU_CHARERASE = Signals.ScanCode.SC_BACKSPACE; + + public static final int HU_MAXLINES = 4; + public static final int HU_MAXLINELENGTH = 80; + +// From hu_stuff.h +// +//Globally visible constants. +// + static public final byte HU_FONTSTART = '!'; // the first font characters + static public final byte HU_FONTEND = '_'; // the last font characters + +//Calculate # of glyphs in font. + public static final int HU_FONTSIZE = (HU_FONTEND - HU_FONTSTART + 1); + + static public final char HU_BROADCAST = 5; + + static public final Signals.ScanCode HU_MSGREFRESH = Signals.ScanCode.SC_ENTER; + static public final char HU_MSGX = 0; + static public final char HU_MSGY = 0; + static public final char HU_MSGWIDTH = 64; // in characters + static public final char HU_MSGHEIGHT = 1; // in lines + + public static final int HU_MSGTIMEOUT = (4 * TICRATE); + + public static final int SAVESTRINGSIZE = 24; + +// +// Button/action code definitions. +// From d_event.h + // Press "Fire". + public static final int BT_ATTACK = 1; + // Use button, to open doors, activate switches. + public static final int BT_USE = 2; + + // Flag: game events, not really buttons. + public static final int BT_SPECIAL = 128; + public static final int BT_SPECIALMASK = 3; + + // Flag, weapon change pending. + // If true, the next 3 bits hold weapon num. + public static final int BT_CHANGE = 4; + // The 3bit weapon mask and shift, convenience. + public static final int BT_WEAPONMASK = (8 + 16 + 32); + public static final int BT_WEAPONSHIFT = 3; + + // Pause the game. + public static final int BTS_PAUSE = 1; + // Save the game at each console. + public static final int BTS_SAVEGAME = 2; + + // Savegame slot numbers + // occupy the second byte of buttons. + public static final int BTS_SAVEMASK = (4 + 8 + 16); + public static final int BTS_SAVESHIFT = 2; + + //==================== Stuff from r_local.c ========================================= + public static final int FLOATSPEED = (FRACUNIT * 4); + + public static final int VIEWHEIGHT = (41 * FRACUNIT); + + // mapblocks are used to check movement + // against lines and things + public static final int MAPBLOCKUNITS = 128; + public static final int MAPBLOCKSIZE = (MAPBLOCKUNITS * FRACUNIT); + public static final int MAPBLOCKSHIFT = (FRACBITS + 7); + public static final int MAPBMASK = (MAPBLOCKSIZE - 1); + public static final int MAPBTOFRAC = (MAPBLOCKSHIFT - FRACBITS); + public static final int BLOCKMAPPADDING = 8 * FRACUNIT; + + // player radius for movement checking + public static final int PLAYERRADIUS = 16 * FRACUNIT; + public static final int GRAVITY = MAPFRACUNIT; + public static int USERANGE = (64 * FRACUNIT); + public static int MELEERANGE = (64 * FRACUNIT); + public static int MISSILERANGE = (32 * 64 * FRACUNIT); + + // follow a player exlusively for 3 seconds + public static int BASETHRESHOLD = 100; + + public static int PT_ADDLINES = 1; + public static int PT_ADDTHINGS = 2; + public static int PT_EARLYOUT = 4; + + // + // P_MOBJ + // + public static int ONFLOORZ = MININT; + public static int ONCEILINGZ = MAXINT; + + // Time interval for item respawning. + public static int ITEMQUESIZE = 128; + + /** Indicate a leaf. e6y: support for extended nodes */ + public static final int NF_SUBSECTOR = 0x80000000; + + /** This is the regular leaf indicator. Use for reference/conversions */ + public static final int NF_SUBSECTOR_CLASSIC = 0x8000; + + /** Player states. */ + public static final int PST_LIVE = 0, // Playing or camping. + PST_DEAD = 1, // Dead on the ground, view follows killer. + + PST_REBORN = 2; // Ready to restart/respawn??? + + public static final int FF_FULLBRIGHT = 0x8000; // flag in thing->frame + public static final int FF_FRAMEMASK = 0x7fff; + + static final String rcsid = "$Id: Defines.java,v 1.48 2012/09/24 17:16:22 velktron Exp $"; +} \ No newline at end of file diff --git a/doom/src/data/Limits.java b/doom/src/data/Limits.java new file mode 100644 index 0000000..7df8eae --- /dev/null +++ b/doom/src/data/Limits.java @@ -0,0 +1,101 @@ +package data; + +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.MAPFRACUNIT; + +/** Everything that constitutes a removable limit should go here */ +public final class Limits { + + // Obvious rendering limits + public static final int MAXVISPLANES = 128; + public static final int MAXSEGS = 32; + public static final int MAXVISSPRITES = 128; + public static final int MAXDRAWSEGS = 256; + // MAES: Moved MAXOPENINGS to renderer state, it's scale dependant. + public static final int CEILSPEED = MAPFRACUNIT; + public static final int CEILWAIT = 150; + public static final int MAXCEILINGS = 30; + + public static final int MAXANIMS = 32; + + /** Animating line specials */ + public static final int MAXLINEANIMS = 64; + + // These are only used in the renderer, effectively putting + // a limit to the size of lookup tables for screen buffers. + public static final int MAXWIDTH = 1600; + public static final int MAXHEIGHT = 1200; + + // Command line/file limits + public static final int MAXWADFILES = 20; + public static final int MAXARGVS = 100; + + // The maximum number of players, multiplayer/networking. + // Max computers/players in a game. AFFECTS SAVEGAMES. + public static final int MAXPLAYERS = 4; + public final static int MAXNETNODES = 8; + + /** Some quirky engine limits */ + public static final int MAXEVENTS = 64; + + /** max # of wall switch TYPES in a level */ + public static final int MAXSWITCHES = 50; + + /** 20 adjoining sectors max! */ + public static final int MAX_ADJOINING_SECTORS = 20; + + // 4 players, 4 buttons each at once, max. + public static final int MAXBUTTONS = 16; + + // 1 second, in ticks. + public static final int BUTTONTIME = 35; + + /** + * keep track of special lines as they are hit, but don't process them until + * the move is proven valid + */ + public static final int MAXSPECIALCROSS = 8; + public static final int MAXHEALTH = 100; + + /** + * MAXRADIUS is for precalculated sector block boxes the spider demon is + * larger, but we do not have any moving sectors nearby + */ + public static final int MAXRADIUS = 32 * FRACUNIT; + + public static final int MAXINTERCEPTS = 128; + public static final int MAXMOVE = (30 * MAPFRACUNIT); + + /** Player spawn spots for deathmatch. */ + public static final int MAX_DM_STARTS = 10; + + // C's "chars" are actually Java signed bytes. + public static final byte MAXCHAR = ((byte) 0x7f); + public static final byte MINCHAR = ((byte) 0x80); + + // 16-bit integers... + public static final short MAXSHORT = ((short) 0x7fff); + public static final short MINSHORT = ((short) 0x8000); + + // Max pos 32-bit int. + public static final int MAXINT = ((int) 0x7fffffff); + public static final long MAXLONG = ((long) 0x7fffffff); + + // Max negative 32-bit integer. These are considered to be the same. + public static final int MININT = ((int) 0x80000000); + public static final long MINLONG = ((long) 0x80000000); + + // Buffering/memory limits. + public static final int SAVEGAMESIZE = 0x2c000; + + public static final int SAVESTRINGSIZE = 24; + public static final int VERSIONSIZE = 16; + + public static final int PLATWAIT = 3; + public static final int PLATSPEED = MAPFRACUNIT; + public static final int MAXPLATS = 30; + public static final int MAXSKULLS = 20; + public static final int NUMBRAINTARGETS = 32; + public static final int NUMMOBJTYPES = mobjtype_t.NUMMOBJTYPES.ordinal(); + +} \ No newline at end of file diff --git a/doom/src/data/SineCosine.java b/doom/src/data/SineCosine.java new file mode 100644 index 0000000..08cfff3 --- /dev/null +++ b/doom/src/data/SineCosine.java @@ -0,0 +1,1071 @@ +package data; + +import static data.Tables.FINEANGLES; + +/** Sine and Cosine. + * Java can't have that much initialization data in one file, so I had to separate it. + * + * @author Maes + * + */ +public class SineCosine { + + // MAES: original size was 10240, but includes 5PI/4 periods. + // We can get away with ints on this one because of the small range. + // MAES: WTF? -64 ~ 64K range... so 17-bit accuracy? heh. + /** + * Original size was 10240, but includes 5PI/4 periods. + * We can get away with ints on this one because of the small range. + * MAES: WTF? -64 ~ 64K range... so 17-bit accuracy? heh. + */ + public static int[] finesine + = { + 25, 75, 125, 175, 226, 276, 326, 376, + 427, 477, 527, 578, 628, 678, 728, 779, + 829, 879, 929, 980, 1030, 1080, 1130, 1181, + 1231, 1281, 1331, 1382, 1432, 1482, 1532, 1583, + 1633, 1683, 1733, 1784, 1834, 1884, 1934, 1985, + 2035, 2085, 2135, 2186, 2236, 2286, 2336, 2387, + 2437, 2487, 2537, 2587, 2638, 2688, 2738, 2788, + 2839, 2889, 2939, 2989, 3039, 3090, 3140, 3190, + 3240, 3291, 3341, 3391, 3441, 3491, 3541, 3592, + 3642, 3692, 3742, 3792, 3843, 3893, 3943, 3993, + 4043, 4093, 4144, 4194, 4244, 4294, 4344, 4394, + 4445, 4495, 4545, 4595, 4645, 4695, 4745, 4796, + 4846, 4896, 4946, 4996, 5046, 5096, 5146, 5197, + 5247, 5297, 5347, 5397, 5447, 5497, 5547, 5597, + 5647, 5697, 5748, 5798, 5848, 5898, 5948, 5998, + 6048, 6098, 6148, 6198, 6248, 6298, 6348, 6398, + 6448, 6498, 6548, 6598, 6648, 6698, 6748, 6798, + 6848, 6898, 6948, 6998, 7048, 7098, 7148, 7198, + 7248, 7298, 7348, 7398, 7448, 7498, 7548, 7598, + 7648, 7697, 7747, 7797, 7847, 7897, 7947, 7997, + 8047, 8097, 8147, 8196, 8246, 8296, 8346, 8396, + 8446, 8496, 8545, 8595, 8645, 8695, 8745, 8794, + 8844, 8894, 8944, 8994, 9043, 9093, 9143, 9193, + 9243, 9292, 9342, 9392, 9442, 9491, 9541, 9591, + 9640, 9690, 9740, 9790, 9839, 9889, 9939, 9988, + 10038, 10088, 10137, 10187, 10237, 10286, 10336, 10386, + 10435, 10485, 10534, 10584, 10634, 10683, 10733, 10782, + 10832, 10882, 10931, 10981, 11030, 11080, 11129, 11179, + 11228, 11278, 11327, 11377, 11426, 11476, 11525, 11575, + 11624, 11674, 11723, 11773, 11822, 11872, 11921, 11970, + 12020, 12069, 12119, 12168, 12218, 12267, 12316, 12366, + 12415, 12464, 12514, 12563, 12612, 12662, 12711, 12760, + 12810, 12859, 12908, 12957, 13007, 13056, 13105, 13154, + 13204, 13253, 13302, 13351, 13401, 13450, 13499, 13548, + 13597, 13647, 13696, 13745, 13794, 13843, 13892, 13941, + 13990, 14040, 14089, 14138, 14187, 14236, 14285, 14334, + 14383, 14432, 14481, 14530, 14579, 14628, 14677, 14726, + 14775, 14824, 14873, 14922, 14971, 15020, 15069, 15118, + 15167, 15215, 15264, 15313, 15362, 15411, 15460, 15509, + 15557, 15606, 15655, 15704, 15753, 15802, 15850, 15899, + 15948, 15997, 16045, 16094, 16143, 16191, 16240, 16289, + 16338, 16386, 16435, 16484, 16532, 16581, 16629, 16678, + 16727, 16775, 16824, 16872, 16921, 16970, 17018, 17067, + 17115, 17164, 17212, 17261, 17309, 17358, 17406, 17455, + 17503, 17551, 17600, 17648, 17697, 17745, 17793, 17842, + 17890, 17939, 17987, 18035, 18084, 18132, 18180, 18228, + 18277, 18325, 18373, 18421, 18470, 18518, 18566, 18614, + 18663, 18711, 18759, 18807, 18855, 18903, 18951, 19000, + 19048, 19096, 19144, 19192, 19240, 19288, 19336, 19384, + 19432, 19480, 19528, 19576, 19624, 19672, 19720, 19768, + 19816, 19864, 19912, 19959, 20007, 20055, 20103, 20151, + 20199, 20246, 20294, 20342, 20390, 20438, 20485, 20533, + 20581, 20629, 20676, 20724, 20772, 20819, 20867, 20915, + 20962, 21010, 21057, 21105, 21153, 21200, 21248, 21295, + 21343, 21390, 21438, 21485, 21533, 21580, 21628, 21675, + 21723, 21770, 21817, 21865, 21912, 21960, 22007, 22054, + 22102, 22149, 22196, 22243, 22291, 22338, 22385, 22433, + 22480, 22527, 22574, 22621, 22668, 22716, 22763, 22810, + 22857, 22904, 22951, 22998, 23045, 23092, 23139, 23186, + 23233, 23280, 23327, 23374, 23421, 23468, 23515, 23562, + 23609, 23656, 23703, 23750, 23796, 23843, 23890, 23937, + 23984, 24030, 24077, 24124, 24171, 24217, 24264, 24311, + 24357, 24404, 24451, 24497, 24544, 24591, 24637, 24684, + 24730, 24777, 24823, 24870, 24916, 24963, 25009, 25056, + 25102, 25149, 25195, 25241, 25288, 25334, 25381, 25427, + 25473, 25520, 25566, 25612, 25658, 25705, 25751, 25797, + 25843, 25889, 25936, 25982, 26028, 26074, 26120, 26166, + 26212, 26258, 26304, 26350, 26396, 26442, 26488, 26534, + 26580, 26626, 26672, 26718, 26764, 26810, 26856, 26902, + 26947, 26993, 27039, 27085, 27131, 27176, 27222, 27268, + 27313, 27359, 27405, 27450, 27496, 27542, 27587, 27633, + 27678, 27724, 27770, 27815, 27861, 27906, 27952, 27997, + 28042, 28088, 28133, 28179, 28224, 28269, 28315, 28360, + 28405, 28451, 28496, 28541, 28586, 28632, 28677, 28722, + 28767, 28812, 28858, 28903, 28948, 28993, 29038, 29083, + 29128, 29173, 29218, 29263, 29308, 29353, 29398, 29443, + 29488, 29533, 29577, 29622, 29667, 29712, 29757, 29801, + 29846, 29891, 29936, 29980, 30025, 30070, 30114, 30159, + 30204, 30248, 30293, 30337, 30382, 30426, 30471, 30515, + 30560, 30604, 30649, 30693, 30738, 30782, 30826, 30871, + 30915, 30959, 31004, 31048, 31092, 31136, 31181, 31225, + 31269, 31313, 31357, 31402, 31446, 31490, 31534, 31578, + 31622, 31666, 31710, 31754, 31798, 31842, 31886, 31930, + 31974, 32017, 32061, 32105, 32149, 32193, 32236, 32280, + 32324, 32368, 32411, 32455, 32499, 32542, 32586, 32630, + 32673, 32717, 32760, 32804, 32847, 32891, 32934, 32978, + 33021, 33065, 33108, 33151, 33195, 33238, 33281, 33325, + 33368, 33411, 33454, 33498, 33541, 33584, 33627, 33670, + 33713, 33756, 33799, 33843, 33886, 33929, 33972, 34015, + 34057, 34100, 34143, 34186, 34229, 34272, 34315, 34358, + 34400, 34443, 34486, 34529, 34571, 34614, 34657, 34699, + 34742, 34785, 34827, 34870, 34912, 34955, 34997, 35040, + 35082, 35125, 35167, 35210, 35252, 35294, 35337, 35379, + 35421, 35464, 35506, 35548, 35590, 35633, 35675, 35717, + 35759, 35801, 35843, 35885, 35927, 35969, 36011, 36053, + 36095, 36137, 36179, 36221, 36263, 36305, 36347, 36388, + 36430, 36472, 36514, 36555, 36597, 36639, 36681, 36722, + 36764, 36805, 36847, 36889, 36930, 36972, 37013, 37055, + 37096, 37137, 37179, 37220, 37262, 37303, 37344, 37386, + 37427, 37468, 37509, 37551, 37592, 37633, 37674, 37715, + 37756, 37797, 37838, 37879, 37920, 37961, 38002, 38043, + 38084, 38125, 38166, 38207, 38248, 38288, 38329, 38370, + 38411, 38451, 38492, 38533, 38573, 38614, 38655, 38695, + 38736, 38776, 38817, 38857, 38898, 38938, 38979, 39019, + 39059, 39100, 39140, 39180, 39221, 39261, 39301, 39341, + 39382, 39422, 39462, 39502, 39542, 39582, 39622, 39662, + 39702, 39742, 39782, 39822, 39862, 39902, 39942, 39982, + 40021, 40061, 40101, 40141, 40180, 40220, 40260, 40300, + 40339, 40379, 40418, 40458, 40497, 40537, 40576, 40616, + 40655, 40695, 40734, 40773, 40813, 40852, 40891, 40931, + 40970, 41009, 41048, 41087, 41127, 41166, 41205, 41244, + 41283, 41322, 41361, 41400, 41439, 41478, 41517, 41556, + 41595, 41633, 41672, 41711, 41750, 41788, 41827, 41866, + 41904, 41943, 41982, 42020, 42059, 42097, 42136, 42174, + 42213, 42251, 42290, 42328, 42366, 42405, 42443, 42481, + 42520, 42558, 42596, 42634, 42672, 42711, 42749, 42787, + 42825, 42863, 42901, 42939, 42977, 43015, 43053, 43091, + 43128, 43166, 43204, 43242, 43280, 43317, 43355, 43393, + 43430, 43468, 43506, 43543, 43581, 43618, 43656, 43693, + 43731, 43768, 43806, 43843, 43880, 43918, 43955, 43992, + 44029, 44067, 44104, 44141, 44178, 44215, 44252, 44289, + 44326, 44363, 44400, 44437, 44474, 44511, 44548, 44585, + 44622, 44659, 44695, 44732, 44769, 44806, 44842, 44879, + 44915, 44952, 44989, 45025, 45062, 45098, 45135, 45171, + 45207, 45244, 45280, 45316, 45353, 45389, 45425, 45462, + 45498, 45534, 45570, 45606, 45642, 45678, 45714, 45750, + 45786, 45822, 45858, 45894, 45930, 45966, 46002, 46037, + 46073, 46109, 46145, 46180, 46216, 46252, 46287, 46323, + 46358, 46394, 46429, 46465, 46500, 46536, 46571, 46606, + 46642, 46677, 46712, 46747, 46783, 46818, 46853, 46888, + 46923, 46958, 46993, 47028, 47063, 47098, 47133, 47168, + 47203, 47238, 47273, 47308, 47342, 47377, 47412, 47446, + 47481, 47516, 47550, 47585, 47619, 47654, 47688, 47723, + 47757, 47792, 47826, 47860, 47895, 47929, 47963, 47998, + 48032, 48066, 48100, 48134, 48168, 48202, 48237, 48271, + 48305, 48338, 48372, 48406, 48440, 48474, 48508, 48542, + 48575, 48609, 48643, 48676, 48710, 48744, 48777, 48811, + 48844, 48878, 48911, 48945, 48978, 49012, 49045, 49078, + 49112, 49145, 49178, 49211, 49244, 49278, 49311, 49344, + 49377, 49410, 49443, 49476, 49509, 49542, 49575, 49608, + 49640, 49673, 49706, 49739, 49771, 49804, 49837, 49869, + 49902, 49935, 49967, 50000, 50032, 50065, 50097, 50129, + 50162, 50194, 50226, 50259, 50291, 50323, 50355, 50387, + 50420, 50452, 50484, 50516, 50548, 50580, 50612, 50644, + 50675, 50707, 50739, 50771, 50803, 50834, 50866, 50898, + 50929, 50961, 50993, 51024, 51056, 51087, 51119, 51150, + 51182, 51213, 51244, 51276, 51307, 51338, 51369, 51401, + 51432, 51463, 51494, 51525, 51556, 51587, 51618, 51649, + 51680, 51711, 51742, 51773, 51803, 51834, 51865, 51896, + 51926, 51957, 51988, 52018, 52049, 52079, 52110, 52140, + 52171, 52201, 52231, 52262, 52292, 52322, 52353, 52383, + 52413, 52443, 52473, 52503, 52534, 52564, 52594, 52624, + 52653, 52683, 52713, 52743, 52773, 52803, 52832, 52862, + 52892, 52922, 52951, 52981, 53010, 53040, 53069, 53099, + 53128, 53158, 53187, 53216, 53246, 53275, 53304, 53334, + 53363, 53392, 53421, 53450, 53479, 53508, 53537, 53566, + 53595, 53624, 53653, 53682, 53711, 53739, 53768, 53797, + 53826, 53854, 53883, 53911, 53940, 53969, 53997, 54026, + 54054, 54082, 54111, 54139, 54167, 54196, 54224, 54252, + 54280, 54308, 54337, 54365, 54393, 54421, 54449, 54477, + 54505, 54533, 54560, 54588, 54616, 54644, 54672, 54699, + 54727, 54755, 54782, 54810, 54837, 54865, 54892, 54920, + 54947, 54974, 55002, 55029, 55056, 55084, 55111, 55138, + 55165, 55192, 55219, 55246, 55274, 55300, 55327, 55354, + 55381, 55408, 55435, 55462, 55489, 55515, 55542, 55569, + 55595, 55622, 55648, 55675, 55701, 55728, 55754, 55781, + 55807, 55833, 55860, 55886, 55912, 55938, 55965, 55991, + 56017, 56043, 56069, 56095, 56121, 56147, 56173, 56199, + 56225, 56250, 56276, 56302, 56328, 56353, 56379, 56404, + 56430, 56456, 56481, 56507, 56532, 56557, 56583, 56608, + 56633, 56659, 56684, 56709, 56734, 56760, 56785, 56810, + 56835, 56860, 56885, 56910, 56935, 56959, 56984, 57009, + 57034, 57059, 57083, 57108, 57133, 57157, 57182, 57206, + 57231, 57255, 57280, 57304, 57329, 57353, 57377, 57402, + 57426, 57450, 57474, 57498, 57522, 57546, 57570, 57594, + 57618, 57642, 57666, 57690, 57714, 57738, 57762, 57785, + 57809, 57833, 57856, 57880, 57903, 57927, 57950, 57974, + 57997, 58021, 58044, 58067, 58091, 58114, 58137, 58160, + 58183, 58207, 58230, 58253, 58276, 58299, 58322, 58345, + 58367, 58390, 58413, 58436, 58459, 58481, 58504, 58527, + 58549, 58572, 58594, 58617, 58639, 58662, 58684, 58706, + 58729, 58751, 58773, 58795, 58818, 58840, 58862, 58884, + 58906, 58928, 58950, 58972, 58994, 59016, 59038, 59059, + 59081, 59103, 59125, 59146, 59168, 59190, 59211, 59233, + 59254, 59276, 59297, 59318, 59340, 59361, 59382, 59404, + 59425, 59446, 59467, 59488, 59509, 59530, 59551, 59572, + 59593, 59614, 59635, 59656, 59677, 59697, 59718, 59739, + 59759, 59780, 59801, 59821, 59842, 59862, 59883, 59903, + 59923, 59944, 59964, 59984, 60004, 60025, 60045, 60065, + 60085, 60105, 60125, 60145, 60165, 60185, 60205, 60225, + 60244, 60264, 60284, 60304, 60323, 60343, 60363, 60382, + 60402, 60421, 60441, 60460, 60479, 60499, 60518, 60537, + 60556, 60576, 60595, 60614, 60633, 60652, 60671, 60690, + 60709, 60728, 60747, 60766, 60785, 60803, 60822, 60841, + 60859, 60878, 60897, 60915, 60934, 60952, 60971, 60989, + 61007, 61026, 61044, 61062, 61081, 61099, 61117, 61135, + 61153, 61171, 61189, 61207, 61225, 61243, 61261, 61279, + 61297, 61314, 61332, 61350, 61367, 61385, 61403, 61420, + 61438, 61455, 61473, 61490, 61507, 61525, 61542, 61559, + 61577, 61594, 61611, 61628, 61645, 61662, 61679, 61696, + 61713, 61730, 61747, 61764, 61780, 61797, 61814, 61831, + 61847, 61864, 61880, 61897, 61913, 61930, 61946, 61963, + 61979, 61995, 62012, 62028, 62044, 62060, 62076, 62092, + 62108, 62125, 62141, 62156, 62172, 62188, 62204, 62220, + 62236, 62251, 62267, 62283, 62298, 62314, 62329, 62345, + 62360, 62376, 62391, 62407, 62422, 62437, 62453, 62468, + 62483, 62498, 62513, 62528, 62543, 62558, 62573, 62588, + 62603, 62618, 62633, 62648, 62662, 62677, 62692, 62706, + 62721, 62735, 62750, 62764, 62779, 62793, 62808, 62822, + 62836, 62850, 62865, 62879, 62893, 62907, 62921, 62935, + 62949, 62963, 62977, 62991, 63005, 63019, 63032, 63046, + 63060, 63074, 63087, 63101, 63114, 63128, 63141, 63155, + 63168, 63182, 63195, 63208, 63221, 63235, 63248, 63261, + 63274, 63287, 63300, 63313, 63326, 63339, 63352, 63365, + 63378, 63390, 63403, 63416, 63429, 63441, 63454, 63466, + 63479, 63491, 63504, 63516, 63528, 63541, 63553, 63565, + 63578, 63590, 63602, 63614, 63626, 63638, 63650, 63662, + 63674, 63686, 63698, 63709, 63721, 63733, 63745, 63756, + 63768, 63779, 63791, 63803, 63814, 63825, 63837, 63848, + 63859, 63871, 63882, 63893, 63904, 63915, 63927, 63938, + 63949, 63960, 63971, 63981, 63992, 64003, 64014, 64025, + 64035, 64046, 64057, 64067, 64078, 64088, 64099, 64109, + 64120, 64130, 64140, 64151, 64161, 64171, 64181, 64192, + 64202, 64212, 64222, 64232, 64242, 64252, 64261, 64271, + 64281, 64291, 64301, 64310, 64320, 64330, 64339, 64349, + 64358, 64368, 64377, 64387, 64396, 64405, 64414, 64424, + 64433, 64442, 64451, 64460, 64469, 64478, 64487, 64496, + 64505, 64514, 64523, 64532, 64540, 64549, 64558, 64566, + 64575, 64584, 64592, 64601, 64609, 64617, 64626, 64634, + 64642, 64651, 64659, 64667, 64675, 64683, 64691, 64699, + 64707, 64715, 64723, 64731, 64739, 64747, 64754, 64762, + 64770, 64777, 64785, 64793, 64800, 64808, 64815, 64822, + 64830, 64837, 64844, 64852, 64859, 64866, 64873, 64880, + 64887, 64895, 64902, 64908, 64915, 64922, 64929, 64936, + 64943, 64949, 64956, 64963, 64969, 64976, 64982, 64989, + 64995, 65002, 65008, 65015, 65021, 65027, 65033, 65040, + 65046, 65052, 65058, 65064, 65070, 65076, 65082, 65088, + 65094, 65099, 65105, 65111, 65117, 65122, 65128, 65133, + 65139, 65144, 65150, 65155, 65161, 65166, 65171, 65177, + 65182, 65187, 65192, 65197, 65202, 65207, 65212, 65217, + 65222, 65227, 65232, 65237, 65242, 65246, 65251, 65256, + 65260, 65265, 65270, 65274, 65279, 65283, 65287, 65292, + 65296, 65300, 65305, 65309, 65313, 65317, 65321, 65325, + 65329, 65333, 65337, 65341, 65345, 65349, 65352, 65356, + 65360, 65363, 65367, 65371, 65374, 65378, 65381, 65385, + 65388, 65391, 65395, 65398, 65401, 65404, 65408, 65411, + 65414, 65417, 65420, 65423, 65426, 65429, 65431, 65434, + 65437, 65440, 65442, 65445, 65448, 65450, 65453, 65455, + 65458, 65460, 65463, 65465, 65467, 65470, 65472, 65474, + 65476, 65478, 65480, 65482, 65484, 65486, 65488, 65490, + 65492, 65494, 65496, 65497, 65499, 65501, 65502, 65504, + 65505, 65507, 65508, 65510, 65511, 65513, 65514, 65515, + 65516, 65518, 65519, 65520, 65521, 65522, 65523, 65524, + 65525, 65526, 65527, 65527, 65528, 65529, 65530, 65530, + 65531, 65531, 65532, 65532, 65533, 65533, 65534, 65534, + 65534, 65535, 65535, 65535, 65535, 65535, 65535, 65535, + 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65534, + 65534, 65534, 65533, 65533, 65532, 65532, 65531, 65531, + 65530, 65530, 65529, 65528, 65527, 65527, 65526, 65525, + 65524, 65523, 65522, 65521, 65520, 65519, 65518, 65516, + 65515, 65514, 65513, 65511, 65510, 65508, 65507, 65505, + 65504, 65502, 65501, 65499, 65497, 65496, 65494, 65492, + 65490, 65488, 65486, 65484, 65482, 65480, 65478, 65476, + 65474, 65472, 65470, 65467, 65465, 65463, 65460, 65458, + 65455, 65453, 65450, 65448, 65445, 65442, 65440, 65437, + 65434, 65431, 65429, 65426, 65423, 65420, 65417, 65414, + 65411, 65408, 65404, 65401, 65398, 65395, 65391, 65388, + 65385, 65381, 65378, 65374, 65371, 65367, 65363, 65360, + 65356, 65352, 65349, 65345, 65341, 65337, 65333, 65329, + 65325, 65321, 65317, 65313, 65309, 65305, 65300, 65296, + 65292, 65287, 65283, 65279, 65274, 65270, 65265, 65260, + 65256, 65251, 65246, 65242, 65237, 65232, 65227, 65222, + 65217, 65212, 65207, 65202, 65197, 65192, 65187, 65182, + 65177, 65171, 65166, 65161, 65155, 65150, 65144, 65139, + 65133, 65128, 65122, 65117, 65111, 65105, 65099, 65094, + 65088, 65082, 65076, 65070, 65064, 65058, 65052, 65046, + 65040, 65033, 65027, 65021, 65015, 65008, 65002, 64995, + 64989, 64982, 64976, 64969, 64963, 64956, 64949, 64943, + 64936, 64929, 64922, 64915, 64908, 64902, 64895, 64887, + 64880, 64873, 64866, 64859, 64852, 64844, 64837, 64830, + 64822, 64815, 64808, 64800, 64793, 64785, 64777, 64770, + 64762, 64754, 64747, 64739, 64731, 64723, 64715, 64707, + 64699, 64691, 64683, 64675, 64667, 64659, 64651, 64642, + 64634, 64626, 64617, 64609, 64600, 64592, 64584, 64575, + 64566, 64558, 64549, 64540, 64532, 64523, 64514, 64505, + 64496, 64487, 64478, 64469, 64460, 64451, 64442, 64433, + 64424, 64414, 64405, 64396, 64387, 64377, 64368, 64358, + 64349, 64339, 64330, 64320, 64310, 64301, 64291, 64281, + 64271, 64261, 64252, 64242, 64232, 64222, 64212, 64202, + 64192, 64181, 64171, 64161, 64151, 64140, 64130, 64120, + 64109, 64099, 64088, 64078, 64067, 64057, 64046, 64035, + 64025, 64014, 64003, 63992, 63981, 63971, 63960, 63949, + 63938, 63927, 63915, 63904, 63893, 63882, 63871, 63859, + 63848, 63837, 63825, 63814, 63803, 63791, 63779, 63768, + 63756, 63745, 63733, 63721, 63709, 63698, 63686, 63674, + 63662, 63650, 63638, 63626, 63614, 63602, 63590, 63578, + 63565, 63553, 63541, 63528, 63516, 63504, 63491, 63479, + 63466, 63454, 63441, 63429, 63416, 63403, 63390, 63378, + 63365, 63352, 63339, 63326, 63313, 63300, 63287, 63274, + 63261, 63248, 63235, 63221, 63208, 63195, 63182, 63168, + 63155, 63141, 63128, 63114, 63101, 63087, 63074, 63060, + 63046, 63032, 63019, 63005, 62991, 62977, 62963, 62949, + 62935, 62921, 62907, 62893, 62879, 62865, 62850, 62836, + 62822, 62808, 62793, 62779, 62764, 62750, 62735, 62721, + 62706, 62692, 62677, 62662, 62648, 62633, 62618, 62603, + 62588, 62573, 62558, 62543, 62528, 62513, 62498, 62483, + 62468, 62453, 62437, 62422, 62407, 62391, 62376, 62360, + 62345, 62329, 62314, 62298, 62283, 62267, 62251, 62236, + 62220, 62204, 62188, 62172, 62156, 62141, 62125, 62108, + 62092, 62076, 62060, 62044, 62028, 62012, 61995, 61979, + 61963, 61946, 61930, 61913, 61897, 61880, 61864, 61847, + 61831, 61814, 61797, 61780, 61764, 61747, 61730, 61713, + 61696, 61679, 61662, 61645, 61628, 61611, 61594, 61577, + 61559, 61542, 61525, 61507, 61490, 61473, 61455, 61438, + 61420, 61403, 61385, 61367, 61350, 61332, 61314, 61297, + 61279, 61261, 61243, 61225, 61207, 61189, 61171, 61153, + 61135, 61117, 61099, 61081, 61062, 61044, 61026, 61007, + 60989, 60971, 60952, 60934, 60915, 60897, 60878, 60859, + 60841, 60822, 60803, 60785, 60766, 60747, 60728, 60709, + 60690, 60671, 60652, 60633, 60614, 60595, 60576, 60556, + 60537, 60518, 60499, 60479, 60460, 60441, 60421, 60402, + 60382, 60363, 60343, 60323, 60304, 60284, 60264, 60244, + 60225, 60205, 60185, 60165, 60145, 60125, 60105, 60085, + 60065, 60045, 60025, 60004, 59984, 59964, 59944, 59923, + 59903, 59883, 59862, 59842, 59821, 59801, 59780, 59759, + 59739, 59718, 59697, 59677, 59656, 59635, 59614, 59593, + 59572, 59551, 59530, 59509, 59488, 59467, 59446, 59425, + 59404, 59382, 59361, 59340, 59318, 59297, 59276, 59254, + 59233, 59211, 59190, 59168, 59146, 59125, 59103, 59081, + 59059, 59038, 59016, 58994, 58972, 58950, 58928, 58906, + 58884, 58862, 58840, 58818, 58795, 58773, 58751, 58729, + 58706, 58684, 58662, 58639, 58617, 58594, 58572, 58549, + 58527, 58504, 58481, 58459, 58436, 58413, 58390, 58367, + 58345, 58322, 58299, 58276, 58253, 58230, 58207, 58183, + 58160, 58137, 58114, 58091, 58067, 58044, 58021, 57997, + 57974, 57950, 57927, 57903, 57880, 57856, 57833, 57809, + 57785, 57762, 57738, 57714, 57690, 57666, 57642, 57618, + 57594, 57570, 57546, 57522, 57498, 57474, 57450, 57426, + 57402, 57377, 57353, 57329, 57304, 57280, 57255, 57231, + 57206, 57182, 57157, 57133, 57108, 57083, 57059, 57034, + 57009, 56984, 56959, 56935, 56910, 56885, 56860, 56835, + 56810, 56785, 56760, 56734, 56709, 56684, 56659, 56633, + 56608, 56583, 56557, 56532, 56507, 56481, 56456, 56430, + 56404, 56379, 56353, 56328, 56302, 56276, 56250, 56225, + 56199, 56173, 56147, 56121, 56095, 56069, 56043, 56017, + 55991, 55965, 55938, 55912, 55886, 55860, 55833, 55807, + 55781, 55754, 55728, 55701, 55675, 55648, 55622, 55595, + 55569, 55542, 55515, 55489, 55462, 55435, 55408, 55381, + 55354, 55327, 55300, 55274, 55246, 55219, 55192, 55165, + 55138, 55111, 55084, 55056, 55029, 55002, 54974, 54947, + 54920, 54892, 54865, 54837, 54810, 54782, 54755, 54727, + 54699, 54672, 54644, 54616, 54588, 54560, 54533, 54505, + 54477, 54449, 54421, 54393, 54365, 54337, 54308, 54280, + 54252, 54224, 54196, 54167, 54139, 54111, 54082, 54054, + 54026, 53997, 53969, 53940, 53911, 53883, 53854, 53826, + 53797, 53768, 53739, 53711, 53682, 53653, 53624, 53595, + 53566, 53537, 53508, 53479, 53450, 53421, 53392, 53363, + 53334, 53304, 53275, 53246, 53216, 53187, 53158, 53128, + 53099, 53069, 53040, 53010, 52981, 52951, 52922, 52892, + 52862, 52832, 52803, 52773, 52743, 52713, 52683, 52653, + 52624, 52594, 52564, 52534, 52503, 52473, 52443, 52413, + 52383, 52353, 52322, 52292, 52262, 52231, 52201, 52171, + 52140, 52110, 52079, 52049, 52018, 51988, 51957, 51926, + 51896, 51865, 51834, 51803, 51773, 51742, 51711, 51680, + 51649, 51618, 51587, 51556, 51525, 51494, 51463, 51432, + 51401, 51369, 51338, 51307, 51276, 51244, 51213, 51182, + 51150, 51119, 51087, 51056, 51024, 50993, 50961, 50929, + 50898, 50866, 50834, 50803, 50771, 50739, 50707, 50675, + 50644, 50612, 50580, 50548, 50516, 50484, 50452, 50420, + 50387, 50355, 50323, 50291, 50259, 50226, 50194, 50162, + 50129, 50097, 50065, 50032, 50000, 49967, 49935, 49902, + 49869, 49837, 49804, 49771, 49739, 49706, 49673, 49640, + 49608, 49575, 49542, 49509, 49476, 49443, 49410, 49377, + 49344, 49311, 49278, 49244, 49211, 49178, 49145, 49112, + 49078, 49045, 49012, 48978, 48945, 48911, 48878, 48844, + 48811, 48777, 48744, 48710, 48676, 48643, 48609, 48575, + 48542, 48508, 48474, 48440, 48406, 48372, 48338, 48304, + 48271, 48237, 48202, 48168, 48134, 48100, 48066, 48032, + 47998, 47963, 47929, 47895, 47860, 47826, 47792, 47757, + 47723, 47688, 47654, 47619, 47585, 47550, 47516, 47481, + 47446, 47412, 47377, 47342, 47308, 47273, 47238, 47203, + 47168, 47133, 47098, 47063, 47028, 46993, 46958, 46923, + 46888, 46853, 46818, 46783, 46747, 46712, 46677, 46642, + 46606, 46571, 46536, 46500, 46465, 46429, 46394, 46358, + 46323, 46287, 46252, 46216, 46180, 46145, 46109, 46073, + 46037, 46002, 45966, 45930, 45894, 45858, 45822, 45786, + 45750, 45714, 45678, 45642, 45606, 45570, 45534, 45498, + 45462, 45425, 45389, 45353, 45316, 45280, 45244, 45207, + 45171, 45135, 45098, 45062, 45025, 44989, 44952, 44915, + 44879, 44842, 44806, 44769, 44732, 44695, 44659, 44622, + 44585, 44548, 44511, 44474, 44437, 44400, 44363, 44326, + 44289, 44252, 44215, 44178, 44141, 44104, 44067, 44029, + 43992, 43955, 43918, 43880, 43843, 43806, 43768, 43731, + 43693, 43656, 43618, 43581, 43543, 43506, 43468, 43430, + 43393, 43355, 43317, 43280, 43242, 43204, 43166, 43128, + 43091, 43053, 43015, 42977, 42939, 42901, 42863, 42825, + 42787, 42749, 42711, 42672, 42634, 42596, 42558, 42520, + 42481, 42443, 42405, 42366, 42328, 42290, 42251, 42213, + 42174, 42136, 42097, 42059, 42020, 41982, 41943, 41904, + 41866, 41827, 41788, 41750, 41711, 41672, 41633, 41595, + 41556, 41517, 41478, 41439, 41400, 41361, 41322, 41283, + 41244, 41205, 41166, 41127, 41088, 41048, 41009, 40970, + 40931, 40891, 40852, 40813, 40773, 40734, 40695, 40655, + 40616, 40576, 40537, 40497, 40458, 40418, 40379, 40339, + 40300, 40260, 40220, 40180, 40141, 40101, 40061, 40021, + 39982, 39942, 39902, 39862, 39822, 39782, 39742, 39702, + 39662, 39622, 39582, 39542, 39502, 39462, 39422, 39382, + 39341, 39301, 39261, 39221, 39180, 39140, 39100, 39059, + 39019, 38979, 38938, 38898, 38857, 38817, 38776, 38736, + 38695, 38655, 38614, 38573, 38533, 38492, 38451, 38411, + 38370, 38329, 38288, 38248, 38207, 38166, 38125, 38084, + 38043, 38002, 37961, 37920, 37879, 37838, 37797, 37756, + 37715, 37674, 37633, 37592, 37551, 37509, 37468, 37427, + 37386, 37344, 37303, 37262, 37220, 37179, 37137, 37096, + 37055, 37013, 36972, 36930, 36889, 36847, 36805, 36764, + 36722, 36681, 36639, 36597, 36556, 36514, 36472, 36430, + 36388, 36347, 36305, 36263, 36221, 36179, 36137, 36095, + 36053, 36011, 35969, 35927, 35885, 35843, 35801, 35759, + 35717, 35675, 35633, 35590, 35548, 35506, 35464, 35421, + 35379, 35337, 35294, 35252, 35210, 35167, 35125, 35082, + 35040, 34997, 34955, 34912, 34870, 34827, 34785, 34742, + 34699, 34657, 34614, 34571, 34529, 34486, 34443, 34400, + 34358, 34315, 34272, 34229, 34186, 34143, 34100, 34057, + 34015, 33972, 33929, 33886, 33843, 33799, 33756, 33713, + 33670, 33627, 33584, 33541, 33498, 33454, 33411, 33368, + 33325, 33281, 33238, 33195, 33151, 33108, 33065, 33021, + 32978, 32934, 32891, 32847, 32804, 32760, 32717, 32673, + 32630, 32586, 32542, 32499, 32455, 32411, 32368, 32324, + 32280, 32236, 32193, 32149, 32105, 32061, 32017, 31974, + 31930, 31886, 31842, 31798, 31754, 31710, 31666, 31622, + 31578, 31534, 31490, 31446, 31402, 31357, 31313, 31269, + 31225, 31181, 31136, 31092, 31048, 31004, 30959, 30915, + 30871, 30826, 30782, 30738, 30693, 30649, 30604, 30560, + 30515, 30471, 30426, 30382, 30337, 30293, 30248, 30204, + 30159, 30114, 30070, 30025, 29980, 29936, 29891, 29846, + 29801, 29757, 29712, 29667, 29622, 29577, 29533, 29488, + 29443, 29398, 29353, 29308, 29263, 29218, 29173, 29128, + 29083, 29038, 28993, 28948, 28903, 28858, 28812, 28767, + 28722, 28677, 28632, 28586, 28541, 28496, 28451, 28405, + 28360, 28315, 28269, 28224, 28179, 28133, 28088, 28042, + 27997, 27952, 27906, 27861, 27815, 27770, 27724, 27678, + 27633, 27587, 27542, 27496, 27450, 27405, 27359, 27313, + 27268, 27222, 27176, 27131, 27085, 27039, 26993, 26947, + 26902, 26856, 26810, 26764, 26718, 26672, 26626, 26580, + 26534, 26488, 26442, 26396, 26350, 26304, 26258, 26212, + 26166, 26120, 26074, 26028, 25982, 25936, 25889, 25843, + 25797, 25751, 25705, 25658, 25612, 25566, 25520, 25473, + 25427, 25381, 25334, 25288, 25241, 25195, 25149, 25102, + 25056, 25009, 24963, 24916, 24870, 24823, 24777, 24730, + 24684, 24637, 24591, 24544, 24497, 24451, 24404, 24357, + 24311, 24264, 24217, 24171, 24124, 24077, 24030, 23984, + 23937, 23890, 23843, 23796, 23750, 23703, 23656, 23609, + 23562, 23515, 23468, 23421, 23374, 23327, 23280, 23233, + 23186, 23139, 23092, 23045, 22998, 22951, 22904, 22857, + 22810, 22763, 22716, 22668, 22621, 22574, 22527, 22480, + 22433, 22385, 22338, 22291, 22243, 22196, 22149, 22102, + 22054, 22007, 21960, 21912, 21865, 21817, 21770, 21723, + 21675, 21628, 21580, 21533, 21485, 21438, 21390, 21343, + 21295, 21248, 21200, 21153, 21105, 21057, 21010, 20962, + 20915, 20867, 20819, 20772, 20724, 20676, 20629, 20581, + 20533, 20485, 20438, 20390, 20342, 20294, 20246, 20199, + 20151, 20103, 20055, 20007, 19959, 19912, 19864, 19816, + 19768, 19720, 19672, 19624, 19576, 19528, 19480, 19432, + 19384, 19336, 19288, 19240, 19192, 19144, 19096, 19048, + 19000, 18951, 18903, 18855, 18807, 18759, 18711, 18663, + 18614, 18566, 18518, 18470, 18421, 18373, 18325, 18277, + 18228, 18180, 18132, 18084, 18035, 17987, 17939, 17890, + 17842, 17793, 17745, 17697, 17648, 17600, 17551, 17503, + 17455, 17406, 17358, 17309, 17261, 17212, 17164, 17115, + 17067, 17018, 16970, 16921, 16872, 16824, 16775, 16727, + 16678, 16629, 16581, 16532, 16484, 16435, 16386, 16338, + 16289, 16240, 16191, 16143, 16094, 16045, 15997, 15948, + 15899, 15850, 15802, 15753, 15704, 15655, 15606, 15557, + 15509, 15460, 15411, 15362, 15313, 15264, 15215, 15167, + 15118, 15069, 15020, 14971, 14922, 14873, 14824, 14775, + 14726, 14677, 14628, 14579, 14530, 14481, 14432, 14383, + 14334, 14285, 14236, 14187, 14138, 14089, 14040, 13990, + 13941, 13892, 13843, 13794, 13745, 13696, 13646, 13597, + 13548, 13499, 13450, 13401, 13351, 13302, 13253, 13204, + 13154, 13105, 13056, 13007, 12957, 12908, 12859, 12810, + 12760, 12711, 12662, 12612, 12563, 12514, 12464, 12415, + 12366, 12316, 12267, 12218, 12168, 12119, 12069, 12020, + 11970, 11921, 11872, 11822, 11773, 11723, 11674, 11624, + 11575, 11525, 11476, 11426, 11377, 11327, 11278, 11228, + 11179, 11129, 11080, 11030, 10981, 10931, 10882, 10832, + 10782, 10733, 10683, 10634, 10584, 10534, 10485, 10435, + 10386, 10336, 10286, 10237, 10187, 10137, 10088, 10038, + 9988, 9939, 9889, 9839, 9790, 9740, 9690, 9640, + 9591, 9541, 9491, 9442, 9392, 9342, 9292, 9243, + 9193, 9143, 9093, 9043, 8994, 8944, 8894, 8844, + 8794, 8745, 8695, 8645, 8595, 8545, 8496, 8446, + 8396, 8346, 8296, 8246, 8196, 8147, 8097, 8047, + 7997, 7947, 7897, 7847, 7797, 7747, 7697, 7648, + 7598, 7548, 7498, 7448, 7398, 7348, 7298, 7248, + 7198, 7148, 7098, 7048, 6998, 6948, 6898, 6848, + 6798, 6748, 6698, 6648, 6598, 6548, 6498, 6448, + 6398, 6348, 6298, 6248, 6198, 6148, 6098, 6048, + 5998, 5948, 5898, 5848, 5798, 5748, 5697, 5647, + 5597, 5547, 5497, 5447, 5397, 5347, 5297, 5247, + 5197, 5146, 5096, 5046, 4996, 4946, 4896, 4846, + 4796, 4745, 4695, 4645, 4595, 4545, 4495, 4445, + 4394, 4344, 4294, 4244, 4194, 4144, 4093, 4043, + 3993, 3943, 3893, 3843, 3792, 3742, 3692, 3642, + 3592, 3541, 3491, 3441, 3391, 3341, 3291, 3240, + 3190, 3140, 3090, 3039, 2989, 2939, 2889, 2839, + 2788, 2738, 2688, 2638, 2587, 2537, 2487, 2437, + 2387, 2336, 2286, 2236, 2186, 2135, 2085, 2035, + 1985, 1934, 1884, 1834, 1784, 1733, 1683, 1633, + 1583, 1532, 1482, 1432, 1382, 1331, 1281, 1231, + 1181, 1130, 1080, 1030, 980, 929, 879, 829, + 779, 728, 678, 628, 578, 527, 477, 427, + 376, 326, 276, 226, 175, 125, 75, 25, + -25, -75, -125, -175, -226, -276, -326, -376, + -427, -477, -527, -578, -628, -678, -728, -779, + -829, -879, -929, -980, -1030, -1080, -1130, -1181, + -1231, -1281, -1331, -1382, -1432, -1482, -1532, -1583, + -1633, -1683, -1733, -1784, -1834, -1884, -1934, -1985, + -2035, -2085, -2135, -2186, -2236, -2286, -2336, -2387, + -2437, -2487, -2537, -2588, -2638, -2688, -2738, -2788, + -2839, -2889, -2939, -2989, -3039, -3090, -3140, -3190, + -3240, -3291, -3341, -3391, -3441, -3491, -3541, -3592, + -3642, -3692, -3742, -3792, -3843, -3893, -3943, -3993, + -4043, -4093, -4144, -4194, -4244, -4294, -4344, -4394, + -4445, -4495, -4545, -4595, -4645, -4695, -4745, -4796, + -4846, -4896, -4946, -4996, -5046, -5096, -5146, -5197, + -5247, -5297, -5347, -5397, -5447, -5497, -5547, -5597, + -5647, -5697, -5748, -5798, -5848, -5898, -5948, -5998, + -6048, -6098, -6148, -6198, -6248, -6298, -6348, -6398, + -6448, -6498, -6548, -6598, -6648, -6698, -6748, -6798, + -6848, -6898, -6948, -6998, -7048, -7098, -7148, -7198, + -7248, -7298, -7348, -7398, -7448, -7498, -7548, -7598, + -7648, -7697, -7747, -7797, -7847, -7897, -7947, -7997, + -8047, -8097, -8147, -8196, -8246, -8296, -8346, -8396, + -8446, -8496, -8545, -8595, -8645, -8695, -8745, -8794, + -8844, -8894, -8944, -8994, -9043, -9093, -9143, -9193, + -9243, -9292, -9342, -9392, -9442, -9491, -9541, -9591, + -9640, -9690, -9740, -9790, -9839, -9889, -9939, -9988, + -10038, -10088, -10137, -10187, -10237, -10286, -10336, -10386, + -10435, -10485, -10534, -10584, -10634, -10683, -10733, -10782, + -10832, -10882, -10931, -10981, -11030, -11080, -11129, -11179, + -11228, -11278, -11327, -11377, -11426, -11476, -11525, -11575, + -11624, -11674, -11723, -11773, -11822, -11872, -11921, -11970, + -12020, -12069, -12119, -12168, -12218, -12267, -12316, -12366, + -12415, -12464, -12514, -12563, -12612, -12662, -12711, -12760, + -12810, -12859, -12908, -12957, -13007, -13056, -13105, -13154, + -13204, -13253, -13302, -13351, -13401, -13450, -13499, -13548, + -13597, -13647, -13696, -13745, -13794, -13843, -13892, -13941, + -13990, -14040, -14089, -14138, -14187, -14236, -14285, -14334, + -14383, -14432, -14481, -14530, -14579, -14628, -14677, -14726, + -14775, -14824, -14873, -14922, -14971, -15020, -15069, -15118, + -15167, -15215, -15264, -15313, -15362, -15411, -15460, -15509, + -15557, -15606, -15655, -15704, -15753, -15802, -15850, -15899, + -15948, -15997, -16045, -16094, -16143, -16191, -16240, -16289, + -16338, -16386, -16435, -16484, -16532, -16581, -16629, -16678, + -16727, -16775, -16824, -16872, -16921, -16970, -17018, -17067, + -17115, -17164, -17212, -17261, -17309, -17358, -17406, -17455, + -17503, -17551, -17600, -17648, -17697, -17745, -17793, -17842, + -17890, -17939, -17987, -18035, -18084, -18132, -18180, -18228, + -18277, -18325, -18373, -18421, -18470, -18518, -18566, -18614, + -18663, -18711, -18759, -18807, -18855, -18903, -18951, -19000, + -19048, -19096, -19144, -19192, -19240, -19288, -19336, -19384, + -19432, -19480, -19528, -19576, -19624, -19672, -19720, -19768, + -19816, -19864, -19912, -19959, -20007, -20055, -20103, -20151, + -20199, -20246, -20294, -20342, -20390, -20438, -20485, -20533, + -20581, -20629, -20676, -20724, -20772, -20819, -20867, -20915, + -20962, -21010, -21057, -21105, -21153, -21200, -21248, -21295, + -21343, -21390, -21438, -21485, -21533, -21580, -21628, -21675, + -21723, -21770, -21817, -21865, -21912, -21960, -22007, -22054, + -22102, -22149, -22196, -22243, -22291, -22338, -22385, -22433, + -22480, -22527, -22574, -22621, -22668, -22716, -22763, -22810, + -22857, -22904, -22951, -22998, -23045, -23092, -23139, -23186, + -23233, -23280, -23327, -23374, -23421, -23468, -23515, -23562, + -23609, -23656, -23703, -23750, -23796, -23843, -23890, -23937, + -23984, -24030, -24077, -24124, -24171, -24217, -24264, -24311, + -24357, -24404, -24451, -24497, -24544, -24591, -24637, -24684, + -24730, -24777, -24823, -24870, -24916, -24963, -25009, -25056, + -25102, -25149, -25195, -25241, -25288, -25334, -25381, -25427, + -25473, -25520, -25566, -25612, -25658, -25705, -25751, -25797, + -25843, -25889, -25936, -25982, -26028, -26074, -26120, -26166, + -26212, -26258, -26304, -26350, -26396, -26442, -26488, -26534, + -26580, -26626, -26672, -26718, -26764, -26810, -26856, -26902, + -26947, -26993, -27039, -27085, -27131, -27176, -27222, -27268, + -27313, -27359, -27405, -27450, -27496, -27542, -27587, -27633, + -27678, -27724, -27770, -27815, -27861, -27906, -27952, -27997, + -28042, -28088, -28133, -28179, -28224, -28269, -28315, -28360, + -28405, -28451, -28496, -28541, -28586, -28632, -28677, -28722, + -28767, -28812, -28858, -28903, -28948, -28993, -29038, -29083, + -29128, -29173, -29218, -29263, -29308, -29353, -29398, -29443, + -29488, -29533, -29577, -29622, -29667, -29712, -29757, -29801, + -29846, -29891, -29936, -29980, -30025, -30070, -30114, -30159, + -30204, -30248, -30293, -30337, -30382, -30426, -30471, -30515, + -30560, -30604, -30649, -30693, -30738, -30782, -30826, -30871, + -30915, -30959, -31004, -31048, -31092, -31136, -31181, -31225, + -31269, -31313, -31357, -31402, -31446, -31490, -31534, -31578, + -31622, -31666, -31710, -31754, -31798, -31842, -31886, -31930, + -31974, -32017, -32061, -32105, -32149, -32193, -32236, -32280, + -32324, -32368, -32411, -32455, -32499, -32542, -32586, -32630, + -32673, -32717, -32760, -32804, -32847, -32891, -32934, -32978, + -33021, -33065, -33108, -33151, -33195, -33238, -33281, -33325, + -33368, -33411, -33454, -33498, -33541, -33584, -33627, -33670, + -33713, -33756, -33799, -33843, -33886, -33929, -33972, -34015, + -34057, -34100, -34143, -34186, -34229, -34272, -34315, -34358, + -34400, -34443, -34486, -34529, -34571, -34614, -34657, -34699, + -34742, -34785, -34827, -34870, -34912, -34955, -34997, -35040, + -35082, -35125, -35167, -35210, -35252, -35294, -35337, -35379, + -35421, -35464, -35506, -35548, -35590, -35633, -35675, -35717, + -35759, -35801, -35843, -35885, -35927, -35969, -36011, -36053, + -36095, -36137, -36179, -36221, -36263, -36305, -36347, -36388, + -36430, -36472, -36514, -36555, -36597, -36639, -36681, -36722, + -36764, -36805, -36847, -36889, -36930, -36972, -37013, -37055, + -37096, -37137, -37179, -37220, -37262, -37303, -37344, -37386, + -37427, -37468, -37509, -37551, -37592, -37633, -37674, -37715, + -37756, -37797, -37838, -37879, -37920, -37961, -38002, -38043, + -38084, -38125, -38166, -38207, -38248, -38288, -38329, -38370, + -38411, -38451, -38492, -38533, -38573, -38614, -38655, -38695, + -38736, -38776, -38817, -38857, -38898, -38938, -38979, -39019, + -39059, -39100, -39140, -39180, -39221, -39261, -39301, -39341, + -39382, -39422, -39462, -39502, -39542, -39582, -39622, -39662, + -39702, -39742, -39782, -39822, -39862, -39902, -39942, -39982, + -40021, -40061, -40101, -40141, -40180, -40220, -40260, -40299, + -40339, -40379, -40418, -40458, -40497, -40537, -40576, -40616, + -40655, -40695, -40734, -40773, -40813, -40852, -40891, -40931, + -40970, -41009, -41048, -41087, -41127, -41166, -41205, -41244, + -41283, -41322, -41361, -41400, -41439, -41478, -41517, -41556, + -41595, -41633, -41672, -41711, -41750, -41788, -41827, -41866, + -41904, -41943, -41982, -42020, -42059, -42097, -42136, -42174, + -42213, -42251, -42290, -42328, -42366, -42405, -42443, -42481, + -42520, -42558, -42596, -42634, -42672, -42711, -42749, -42787, + -42825, -42863, -42901, -42939, -42977, -43015, -43053, -43091, + -43128, -43166, -43204, -43242, -43280, -43317, -43355, -43393, + -43430, -43468, -43506, -43543, -43581, -43618, -43656, -43693, + -43731, -43768, -43806, -43843, -43880, -43918, -43955, -43992, + -44029, -44067, -44104, -44141, -44178, -44215, -44252, -44289, + -44326, -44363, -44400, -44437, -44474, -44511, -44548, -44585, + -44622, -44659, -44695, -44732, -44769, -44806, -44842, -44879, + -44915, -44952, -44989, -45025, -45062, -45098, -45135, -45171, + -45207, -45244, -45280, -45316, -45353, -45389, -45425, -45462, + -45498, -45534, -45570, -45606, -45642, -45678, -45714, -45750, + -45786, -45822, -45858, -45894, -45930, -45966, -46002, -46037, + -46073, -46109, -46145, -46180, -46216, -46252, -46287, -46323, + -46358, -46394, -46429, -46465, -46500, -46536, -46571, -46606, + -46642, -46677, -46712, -46747, -46783, -46818, -46853, -46888, + -46923, -46958, -46993, -47028, -47063, -47098, -47133, -47168, + -47203, -47238, -47273, -47308, -47342, -47377, -47412, -47446, + -47481, -47516, -47550, -47585, -47619, -47654, -47688, -47723, + -47757, -47792, -47826, -47860, -47895, -47929, -47963, -47998, + -48032, -48066, -48100, -48134, -48168, -48202, -48236, -48271, + -48304, -48338, -48372, -48406, -48440, -48474, -48508, -48542, + -48575, -48609, -48643, -48676, -48710, -48744, -48777, -48811, + -48844, -48878, -48911, -48945, -48978, -49012, -49045, -49078, + -49112, -49145, -49178, -49211, -49244, -49278, -49311, -49344, + -49377, -49410, -49443, -49476, -49509, -49542, -49575, -49608, + -49640, -49673, -49706, -49739, -49771, -49804, -49837, -49869, + -49902, -49935, -49967, -50000, -50032, -50065, -50097, -50129, + -50162, -50194, -50226, -50259, -50291, -50323, -50355, -50387, + -50420, -50452, -50484, -50516, -50548, -50580, -50612, -50644, + -50675, -50707, -50739, -50771, -50803, -50834, -50866, -50898, + -50929, -50961, -50993, -51024, -51056, -51087, -51119, -51150, + -51182, -51213, -51244, -51276, -51307, -51338, -51369, -51401, + -51432, -51463, -51494, -51525, -51556, -51587, -51618, -51649, + -51680, -51711, -51742, -51773, -51803, -51834, -51865, -51896, + -51926, -51957, -51988, -52018, -52049, -52079, -52110, -52140, + -52171, -52201, -52231, -52262, -52292, -52322, -52353, -52383, + -52413, -52443, -52473, -52503, -52534, -52564, -52594, -52624, + -52653, -52683, -52713, -52743, -52773, -52803, -52832, -52862, + -52892, -52922, -52951, -52981, -53010, -53040, -53069, -53099, + -53128, -53158, -53187, -53216, -53246, -53275, -53304, -53334, + -53363, -53392, -53421, -53450, -53479, -53508, -53537, -53566, + -53595, -53624, -53653, -53682, -53711, -53739, -53768, -53797, + -53826, -53854, -53883, -53911, -53940, -53969, -53997, -54026, + -54054, -54082, -54111, -54139, -54167, -54196, -54224, -54252, + -54280, -54308, -54337, -54365, -54393, -54421, -54449, -54477, + -54505, -54533, -54560, -54588, -54616, -54644, -54672, -54699, + -54727, -54755, -54782, -54810, -54837, -54865, -54892, -54920, + -54947, -54974, -55002, -55029, -55056, -55084, -55111, -55138, + -55165, -55192, -55219, -55246, -55274, -55300, -55327, -55354, + -55381, -55408, -55435, -55462, -55489, -55515, -55542, -55569, + -55595, -55622, -55648, -55675, -55701, -55728, -55754, -55781, + -55807, -55833, -55860, -55886, -55912, -55938, -55965, -55991, + -56017, -56043, -56069, -56095, -56121, -56147, -56173, -56199, + -56225, -56250, -56276, -56302, -56328, -56353, -56379, -56404, + -56430, -56456, -56481, -56507, -56532, -56557, -56583, -56608, + -56633, -56659, -56684, -56709, -56734, -56760, -56785, -56810, + -56835, -56860, -56885, -56910, -56935, -56959, -56984, -57009, + -57034, -57059, -57083, -57108, -57133, -57157, -57182, -57206, + -57231, -57255, -57280, -57304, -57329, -57353, -57377, -57402, + -57426, -57450, -57474, -57498, -57522, -57546, -57570, -57594, + -57618, -57642, -57666, -57690, -57714, -57738, -57762, -57785, + -57809, -57833, -57856, -57880, -57903, -57927, -57950, -57974, + -57997, -58021, -58044, -58067, -58091, -58114, -58137, -58160, + -58183, -58207, -58230, -58253, -58276, -58299, -58322, -58345, + -58367, -58390, -58413, -58436, -58459, -58481, -58504, -58527, + -58549, -58572, -58594, -58617, -58639, -58662, -58684, -58706, + -58729, -58751, -58773, -58795, -58818, -58840, -58862, -58884, + -58906, -58928, -58950, -58972, -58994, -59016, -59038, -59059, + -59081, -59103, -59125, -59146, -59168, -59190, -59211, -59233, + -59254, -59276, -59297, -59318, -59340, -59361, -59382, -59404, + -59425, -59446, -59467, -59488, -59509, -59530, -59551, -59572, + -59593, -59614, -59635, -59656, -59677, -59697, -59718, -59739, + -59759, -59780, -59801, -59821, -59842, -59862, -59883, -59903, + -59923, -59944, -59964, -59984, -60004, -60025, -60045, -60065, + -60085, -60105, -60125, -60145, -60165, -60185, -60205, -60225, + -60244, -60264, -60284, -60304, -60323, -60343, -60363, -60382, + -60402, -60421, -60441, -60460, -60479, -60499, -60518, -60537, + -60556, -60576, -60595, -60614, -60633, -60652, -60671, -60690, + -60709, -60728, -60747, -60766, -60785, -60803, -60822, -60841, + -60859, -60878, -60897, -60915, -60934, -60952, -60971, -60989, + -61007, -61026, -61044, -61062, -61081, -61099, -61117, -61135, + -61153, -61171, -61189, -61207, -61225, -61243, -61261, -61279, + -61297, -61314, -61332, -61350, -61367, -61385, -61403, -61420, + -61438, -61455, -61473, -61490, -61507, -61525, -61542, -61559, + -61577, -61594, -61611, -61628, -61645, -61662, -61679, -61696, + -61713, -61730, -61747, -61764, -61780, -61797, -61814, -61831, + -61847, -61864, -61880, -61897, -61913, -61930, -61946, -61963, + -61979, -61995, -62012, -62028, -62044, -62060, -62076, -62092, + -62108, -62125, -62141, -62156, -62172, -62188, -62204, -62220, + -62236, -62251, -62267, -62283, -62298, -62314, -62329, -62345, + -62360, -62376, -62391, -62407, -62422, -62437, -62453, -62468, + -62483, -62498, -62513, -62528, -62543, -62558, -62573, -62588, + -62603, -62618, -62633, -62648, -62662, -62677, -62692, -62706, + -62721, -62735, -62750, -62764, -62779, -62793, -62808, -62822, + -62836, -62850, -62865, -62879, -62893, -62907, -62921, -62935, + -62949, -62963, -62977, -62991, -63005, -63019, -63032, -63046, + -63060, -63074, -63087, -63101, -63114, -63128, -63141, -63155, + -63168, -63182, -63195, -63208, -63221, -63235, -63248, -63261, + -63274, -63287, -63300, -63313, -63326, -63339, -63352, -63365, + -63378, -63390, -63403, -63416, -63429, -63441, -63454, -63466, + -63479, -63491, -63504, -63516, -63528, -63541, -63553, -63565, + -63578, -63590, -63602, -63614, -63626, -63638, -63650, -63662, + -63674, -63686, -63698, -63709, -63721, -63733, -63745, -63756, + -63768, -63779, -63791, -63803, -63814, -63825, -63837, -63848, + -63859, -63871, -63882, -63893, -63904, -63915, -63927, -63938, + -63949, -63960, -63971, -63981, -63992, -64003, -64014, -64025, + -64035, -64046, -64057, -64067, -64078, -64088, -64099, -64109, + -64120, -64130, -64140, -64151, -64161, -64171, -64181, -64192, + -64202, -64212, -64222, -64232, -64242, -64252, -64261, -64271, + -64281, -64291, -64301, -64310, -64320, -64330, -64339, -64349, + -64358, -64368, -64377, -64387, -64396, -64405, -64414, -64424, + -64433, -64442, -64451, -64460, -64469, -64478, -64487, -64496, + -64505, -64514, -64523, -64532, -64540, -64549, -64558, -64566, + -64575, -64584, -64592, -64601, -64609, -64617, -64626, -64634, + -64642, -64651, -64659, -64667, -64675, -64683, -64691, -64699, + -64707, -64715, -64723, -64731, -64739, -64747, -64754, -64762, + -64770, -64777, -64785, -64793, -64800, -64808, -64815, -64822, + -64830, -64837, -64844, -64852, -64859, -64866, -64873, -64880, + -64887, -64895, -64902, -64908, -64915, -64922, -64929, -64936, + -64943, -64949, -64956, -64963, -64969, -64976, -64982, -64989, + -64995, -65002, -65008, -65015, -65021, -65027, -65033, -65040, + -65046, -65052, -65058, -65064, -65070, -65076, -65082, -65088, + -65094, -65099, -65105, -65111, -65117, -65122, -65128, -65133, + -65139, -65144, -65150, -65155, -65161, -65166, -65171, -65177, + -65182, -65187, -65192, -65197, -65202, -65207, -65212, -65217, + -65222, -65227, -65232, -65237, -65242, -65246, -65251, -65256, + -65260, -65265, -65270, -65274, -65279, -65283, -65287, -65292, + -65296, -65300, -65305, -65309, -65313, -65317, -65321, -65325, + -65329, -65333, -65337, -65341, -65345, -65349, -65352, -65356, + -65360, -65363, -65367, -65371, -65374, -65378, -65381, -65385, + -65388, -65391, -65395, -65398, -65401, -65404, -65408, -65411, + -65414, -65417, -65420, -65423, -65426, -65429, -65431, -65434, + -65437, -65440, -65442, -65445, -65448, -65450, -65453, -65455, + -65458, -65460, -65463, -65465, -65467, -65470, -65472, -65474, + -65476, -65478, -65480, -65482, -65484, -65486, -65488, -65490, + -65492, -65494, -65496, -65497, -65499, -65501, -65502, -65504, + -65505, -65507, -65508, -65510, -65511, -65513, -65514, -65515, + -65516, -65518, -65519, -65520, -65521, -65522, -65523, -65524, + -65525, -65526, -65527, -65527, -65528, -65529, -65530, -65530, + -65531, -65531, -65532, -65532, -65533, -65533, -65534, -65534, + -65534, -65535, -65535, -65535, -65535, -65535, -65535, -65535, + -65535, -65535, -65535, -65535, -65535, -65535, -65535, -65534, + -65534, -65534, -65533, -65533, -65532, -65532, -65531, -65531, + -65530, -65530, -65529, -65528, -65527, -65527, -65526, -65525, + -65524, -65523, -65522, -65521, -65520, -65519, -65518, -65516, + -65515, -65514, -65513, -65511, -65510, -65508, -65507, -65505, + -65504, -65502, -65501, -65499, -65497, -65496, -65494, -65492, + -65490, -65488, -65486, -65484, -65482, -65480, -65478, -65476, + -65474, -65472, -65470, -65467, -65465, -65463, -65460, -65458, + -65455, -65453, -65450, -65448, -65445, -65442, -65440, -65437, + -65434, -65431, -65429, -65426, -65423, -65420, -65417, -65414, + -65411, -65408, -65404, -65401, -65398, -65395, -65391, -65388, + -65385, -65381, -65378, -65374, -65371, -65367, -65363, -65360, + -65356, -65352, -65349, -65345, -65341, -65337, -65333, -65329, + -65325, -65321, -65317, -65313, -65309, -65305, -65300, -65296, + -65292, -65287, -65283, -65279, -65274, -65270, -65265, -65260, + -65256, -65251, -65246, -65242, -65237, -65232, -65227, -65222, + -65217, -65212, -65207, -65202, -65197, -65192, -65187, -65182, + -65177, -65171, -65166, -65161, -65155, -65150, -65144, -65139, + -65133, -65128, -65122, -65117, -65111, -65105, -65099, -65094, + -65088, -65082, -65076, -65070, -65064, -65058, -65052, -65046, + -65040, -65033, -65027, -65021, -65015, -65008, -65002, -64995, + -64989, -64982, -64976, -64969, -64963, -64956, -64949, -64943, + -64936, -64929, -64922, -64915, -64908, -64902, -64895, -64887, + -64880, -64873, -64866, -64859, -64852, -64844, -64837, -64830, + -64822, -64815, -64808, -64800, -64793, -64785, -64777, -64770, + -64762, -64754, -64747, -64739, -64731, -64723, -64715, -64707, + -64699, -64691, -64683, -64675, -64667, -64659, -64651, -64642, + -64634, -64626, -64617, -64609, -64601, -64592, -64584, -64575, + -64566, -64558, -64549, -64540, -64532, -64523, -64514, -64505, + -64496, -64487, -64478, -64469, -64460, -64451, -64442, -64433, + -64424, -64414, -64405, -64396, -64387, -64377, -64368, -64358, + -64349, -64339, -64330, -64320, -64310, -64301, -64291, -64281, + -64271, -64261, -64252, -64242, -64232, -64222, -64212, -64202, + -64192, -64181, -64171, -64161, -64151, -64140, -64130, -64120, + -64109, -64099, -64088, -64078, -64067, -64057, -64046, -64035, + -64025, -64014, -64003, -63992, -63981, -63971, -63960, -63949, + -63938, -63927, -63915, -63904, -63893, -63882, -63871, -63859, + -63848, -63837, -63825, -63814, -63803, -63791, -63779, -63768, + -63756, -63745, -63733, -63721, -63709, -63698, -63686, -63674, + -63662, -63650, -63638, -63626, -63614, -63602, -63590, -63578, + -63565, -63553, -63541, -63528, -63516, -63504, -63491, -63479, + -63466, -63454, -63441, -63429, -63416, -63403, -63390, -63378, + -63365, -63352, -63339, -63326, -63313, -63300, -63287, -63274, + -63261, -63248, -63235, -63221, -63208, -63195, -63182, -63168, + -63155, -63141, -63128, -63114, -63101, -63087, -63074, -63060, + -63046, -63032, -63019, -63005, -62991, -62977, -62963, -62949, + -62935, -62921, -62907, -62893, -62879, -62865, -62850, -62836, + -62822, -62808, -62793, -62779, -62764, -62750, -62735, -62721, + -62706, -62692, -62677, -62662, -62648, -62633, -62618, -62603, + -62588, -62573, -62558, -62543, -62528, -62513, -62498, -62483, + -62468, -62453, -62437, -62422, -62407, -62391, -62376, -62360, + -62345, -62329, -62314, -62298, -62283, -62267, -62251, -62236, + -62220, -62204, -62188, -62172, -62156, -62141, -62125, -62108, + -62092, -62076, -62060, -62044, -62028, -62012, -61995, -61979, + -61963, -61946, -61930, -61913, -61897, -61880, -61864, -61847, + -61831, -61814, -61797, -61780, -61764, -61747, -61730, -61713, + -61696, -61679, -61662, -61645, -61628, -61611, -61594, -61577, + -61559, -61542, -61525, -61507, -61490, -61473, -61455, -61438, + -61420, -61403, -61385, -61367, -61350, -61332, -61314, -61297, + -61279, -61261, -61243, -61225, -61207, -61189, -61171, -61153, + -61135, -61117, -61099, -61081, -61062, -61044, -61026, -61007, + -60989, -60971, -60952, -60934, -60915, -60897, -60878, -60859, + -60841, -60822, -60803, -60785, -60766, -60747, -60728, -60709, + -60690, -60671, -60652, -60633, -60614, -60595, -60576, -60556, + -60537, -60518, -60499, -60479, -60460, -60441, -60421, -60402, + -60382, -60363, -60343, -60323, -60304, -60284, -60264, -60244, + -60225, -60205, -60185, -60165, -60145, -60125, -60105, -60085, + -60065, -60045, -60025, -60004, -59984, -59964, -59944, -59923, + -59903, -59883, -59862, -59842, -59821, -59801, -59780, -59759, + -59739, -59718, -59697, -59677, -59656, -59635, -59614, -59593, + -59572, -59551, -59530, -59509, -59488, -59467, -59446, -59425, + -59404, -59382, -59361, -59340, -59318, -59297, -59276, -59254, + -59233, -59211, -59189, -59168, -59146, -59125, -59103, -59081, + -59059, -59038, -59016, -58994, -58972, -58950, -58928, -58906, + -58884, -58862, -58840, -58818, -58795, -58773, -58751, -58729, + -58706, -58684, -58662, -58639, -58617, -58594, -58572, -58549, + -58527, -58504, -58481, -58459, -58436, -58413, -58390, -58367, + -58345, -58322, -58299, -58276, -58253, -58230, -58207, -58183, + -58160, -58137, -58114, -58091, -58067, -58044, -58021, -57997, + -57974, -57950, -57927, -57903, -57880, -57856, -57833, -57809, + -57785, -57762, -57738, -57714, -57690, -57666, -57642, -57618, + -57594, -57570, -57546, -57522, -57498, -57474, -57450, -57426, + -57402, -57377, -57353, -57329, -57304, -57280, -57255, -57231, + -57206, -57182, -57157, -57133, -57108, -57083, -57059, -57034, + -57009, -56984, -56959, -56935, -56910, -56885, -56860, -56835, + -56810, -56785, -56760, -56734, -56709, -56684, -56659, -56633, + -56608, -56583, -56557, -56532, -56507, -56481, -56456, -56430, + -56404, -56379, -56353, -56328, -56302, -56276, -56250, -56225, + -56199, -56173, -56147, -56121, -56095, -56069, -56043, -56017, + -55991, -55965, -55938, -55912, -55886, -55860, -55833, -55807, + -55781, -55754, -55728, -55701, -55675, -55648, -55622, -55595, + -55569, -55542, -55515, -55489, -55462, -55435, -55408, -55381, + -55354, -55327, -55300, -55274, -55246, -55219, -55192, -55165, + -55138, -55111, -55084, -55056, -55029, -55002, -54974, -54947, + -54920, -54892, -54865, -54837, -54810, -54782, -54755, -54727, + -54699, -54672, -54644, -54616, -54588, -54560, -54533, -54505, + -54477, -54449, -54421, -54393, -54365, -54337, -54308, -54280, + -54252, -54224, -54196, -54167, -54139, -54111, -54082, -54054, + -54026, -53997, -53969, -53940, -53911, -53883, -53854, -53826, + -53797, -53768, -53739, -53711, -53682, -53653, -53624, -53595, + -53566, -53537, -53508, -53479, -53450, -53421, -53392, -53363, + -53334, -53304, -53275, -53246, -53216, -53187, -53158, -53128, + -53099, -53069, -53040, -53010, -52981, -52951, -52922, -52892, + -52862, -52832, -52803, -52773, -52743, -52713, -52683, -52653, + -52624, -52594, -52564, -52534, -52503, -52473, -52443, -52413, + -52383, -52353, -52322, -52292, -52262, -52231, -52201, -52171, + -52140, -52110, -52079, -52049, -52018, -51988, -51957, -51926, + -51896, -51865, -51834, -51803, -51773, -51742, -51711, -51680, + -51649, -51618, -51587, -51556, -51525, -51494, -51463, -51432, + -51401, -51369, -51338, -51307, -51276, -51244, -51213, -51182, + -51150, -51119, -51087, -51056, -51024, -50993, -50961, -50929, + -50898, -50866, -50834, -50803, -50771, -50739, -50707, -50675, + -50644, -50612, -50580, -50548, -50516, -50484, -50452, -50420, + -50387, -50355, -50323, -50291, -50259, -50226, -50194, -50162, + -50129, -50097, -50065, -50032, -50000, -49967, -49935, -49902, + -49869, -49837, -49804, -49771, -49739, -49706, -49673, -49640, + -49608, -49575, -49542, -49509, -49476, -49443, -49410, -49377, + -49344, -49311, -49278, -49244, -49211, -49178, -49145, -49112, + -49078, -49045, -49012, -48978, -48945, -48911, -48878, -48844, + -48811, -48777, -48744, -48710, -48676, -48643, -48609, -48575, + -48542, -48508, -48474, -48440, -48406, -48372, -48338, -48305, + -48271, -48237, -48202, -48168, -48134, -48100, -48066, -48032, + -47998, -47963, -47929, -47895, -47860, -47826, -47792, -47757, + -47723, -47688, -47654, -47619, -47585, -47550, -47516, -47481, + -47446, -47412, -47377, -47342, -47307, -47273, -47238, -47203, + -47168, -47133, -47098, -47063, -47028, -46993, -46958, -46923, + -46888, -46853, -46818, -46783, -46747, -46712, -46677, -46642, + -46606, -46571, -46536, -46500, -46465, -46429, -46394, -46358, + -46323, -46287, -46251, -46216, -46180, -46145, -46109, -46073, + -46037, -46002, -45966, -45930, -45894, -45858, -45822, -45786, + -45750, -45714, -45678, -45642, -45606, -45570, -45534, -45498, + -45462, -45425, -45389, -45353, -45316, -45280, -45244, -45207, + -45171, -45135, -45098, -45062, -45025, -44989, -44952, -44915, + -44879, -44842, -44806, -44769, -44732, -44695, -44659, -44622, + -44585, -44548, -44511, -44474, -44437, -44400, -44363, -44326, + -44289, -44252, -44215, -44178, -44141, -44104, -44067, -44029, + -43992, -43955, -43918, -43880, -43843, -43806, -43768, -43731, + -43693, -43656, -43618, -43581, -43543, -43506, -43468, -43430, + -43393, -43355, -43317, -43280, -43242, -43204, -43166, -43128, + -43091, -43053, -43015, -42977, -42939, -42901, -42863, -42825, + -42787, -42749, -42711, -42672, -42634, -42596, -42558, -42520, + -42481, -42443, -42405, -42366, -42328, -42290, -42251, -42213, + -42174, -42136, -42097, -42059, -42020, -41982, -41943, -41904, + -41866, -41827, -41788, -41750, -41711, -41672, -41633, -41595, + -41556, -41517, -41478, -41439, -41400, -41361, -41322, -41283, + -41244, -41205, -41166, -41127, -41087, -41048, -41009, -40970, + -40931, -40891, -40852, -40813, -40773, -40734, -40695, -40655, + -40616, -40576, -40537, -40497, -40458, -40418, -40379, -40339, + -40299, -40260, -40220, -40180, -40141, -40101, -40061, -40021, + -39982, -39942, -39902, -39862, -39822, -39782, -39742, -39702, + -39662, -39622, -39582, -39542, -39502, -39462, -39422, -39382, + -39341, -39301, -39261, -39221, -39180, -39140, -39100, -39059, + -39019, -38979, -38938, -38898, -38857, -38817, -38776, -38736, + -38695, -38655, -38614, -38573, -38533, -38492, -38451, -38411, + -38370, -38329, -38288, -38248, -38207, -38166, -38125, -38084, + -38043, -38002, -37961, -37920, -37879, -37838, -37797, -37756, + -37715, -37674, -37633, -37592, -37550, -37509, -37468, -37427, + -37386, -37344, -37303, -37262, -37220, -37179, -37137, -37096, + -37055, -37013, -36972, -36930, -36889, -36847, -36805, -36764, + -36722, -36681, -36639, -36597, -36556, -36514, -36472, -36430, + -36388, -36347, -36305, -36263, -36221, -36179, -36137, -36095, + -36053, -36011, -35969, -35927, -35885, -35843, -35801, -35759, + -35717, -35675, -35633, -35590, -35548, -35506, -35464, -35421, + -35379, -35337, -35294, -35252, -35210, -35167, -35125, -35082, + -35040, -34997, -34955, -34912, -34870, -34827, -34785, -34742, + -34699, -34657, -34614, -34571, -34529, -34486, -34443, -34400, + -34358, -34315, -34272, -34229, -34186, -34143, -34100, -34057, + -34015, -33972, -33929, -33886, -33843, -33799, -33756, -33713, + -33670, -33627, -33584, -33541, -33498, -33454, -33411, -33368, + -33325, -33281, -33238, -33195, -33151, -33108, -33065, -33021, + -32978, -32934, -32891, -32847, -32804, -32760, -32717, -32673, + -32630, -32586, -32542, -32499, -32455, -32411, -32368, -32324, + -32280, -32236, -32193, -32149, -32105, -32061, -32017, -31974, + -31930, -31886, -31842, -31798, -31754, -31710, -31666, -31622, + -31578, -31534, -31490, -31446, -31402, -31357, -31313, -31269, + -31225, -31181, -31136, -31092, -31048, -31004, -30959, -30915, + -30871, -30826, -30782, -30738, -30693, -30649, -30604, -30560, + -30515, -30471, -30426, -30382, -30337, -30293, -30248, -30204, + -30159, -30114, -30070, -30025, -29980, -29936, -29891, -29846, + -29801, -29757, -29712, -29667, -29622, -29577, -29533, -29488, + -29443, -29398, -29353, -29308, -29263, -29218, -29173, -29128, + -29083, -29038, -28993, -28948, -28903, -28858, -28812, -28767, + -28722, -28677, -28632, -28586, -28541, -28496, -28451, -28405, + -28360, -28315, -28269, -28224, -28179, -28133, -28088, -28042, + -27997, -27952, -27906, -27861, -27815, -27770, -27724, -27678, + -27633, -27587, -27542, -27496, -27450, -27405, -27359, -27313, + -27268, -27222, -27176, -27131, -27085, -27039, -26993, -26947, + -26902, -26856, -26810, -26764, -26718, -26672, -26626, -26580, + -26534, -26488, -26442, -26396, -26350, -26304, -26258, -26212, + -26166, -26120, -26074, -26028, -25982, -25936, -25889, -25843, + -25797, -25751, -25705, -25658, -25612, -25566, -25520, -25473, + -25427, -25381, -25334, -25288, -25241, -25195, -25149, -25102, + -25056, -25009, -24963, -24916, -24870, -24823, -24777, -24730, + -24684, -24637, -24591, -24544, -24497, -24451, -24404, -24357, + -24311, -24264, -24217, -24171, -24124, -24077, -24030, -23984, + -23937, -23890, -23843, -23796, -23750, -23703, -23656, -23609, + -23562, -23515, -23468, -23421, -23374, -23327, -23280, -23233, + -23186, -23139, -23092, -23045, -22998, -22951, -22904, -22857, + -22810, -22763, -22716, -22668, -22621, -22574, -22527, -22480, + -22432, -22385, -22338, -22291, -22243, -22196, -22149, -22102, + -22054, -22007, -21960, -21912, -21865, -21817, -21770, -21723, + -21675, -21628, -21580, -21533, -21485, -21438, -21390, -21343, + -21295, -21248, -21200, -21153, -21105, -21057, -21010, -20962, + -20915, -20867, -20819, -20772, -20724, -20676, -20629, -20581, + -20533, -20485, -20438, -20390, -20342, -20294, -20246, -20199, + -20151, -20103, -20055, -20007, -19959, -19912, -19864, -19816, + -19768, -19720, -19672, -19624, -19576, -19528, -19480, -19432, + -19384, -19336, -19288, -19240, -19192, -19144, -19096, -19048, + -19000, -18951, -18903, -18855, -18807, -18759, -18711, -18663, + -18614, -18566, -18518, -18470, -18421, -18373, -18325, -18277, + -18228, -18180, -18132, -18084, -18035, -17987, -17939, -17890, + -17842, -17793, -17745, -17697, -17648, -17600, -17551, -17503, + -17455, -17406, -17358, -17309, -17261, -17212, -17164, -17115, + -17067, -17018, -16970, -16921, -16872, -16824, -16775, -16727, + -16678, -16629, -16581, -16532, -16484, -16435, -16386, -16338, + -16289, -16240, -16191, -16143, -16094, -16045, -15997, -15948, + -15899, -15850, -15802, -15753, -15704, -15655, -15606, -15557, + -15509, -15460, -15411, -15362, -15313, -15264, -15215, -15167, + -15118, -15069, -15020, -14971, -14922, -14873, -14824, -14775, + -14726, -14677, -14628, -14579, -14530, -14481, -14432, -14383, + -14334, -14285, -14236, -14187, -14138, -14089, -14040, -13990, + -13941, -13892, -13843, -13794, -13745, -13696, -13647, -13597, + -13548, -13499, -13450, -13401, -13351, -13302, -13253, -13204, + -13154, -13105, -13056, -13007, -12957, -12908, -12859, -12810, + -12760, -12711, -12662, -12612, -12563, -12514, -12464, -12415, + -12366, -12316, -12267, -12217, -12168, -12119, -12069, -12020, + -11970, -11921, -11872, -11822, -11773, -11723, -11674, -11624, + -11575, -11525, -11476, -11426, -11377, -11327, -11278, -11228, + -11179, -11129, -11080, -11030, -10981, -10931, -10882, -10832, + -10782, -10733, -10683, -10634, -10584, -10534, -10485, -10435, + -10386, -10336, -10286, -10237, -10187, -10137, -10088, -10038, + -9988, -9939, -9889, -9839, -9790, -9740, -9690, -9640, + -9591, -9541, -9491, -9442, -9392, -9342, -9292, -9243, + -9193, -9143, -9093, -9043, -8994, -8944, -8894, -8844, + -8794, -8745, -8695, -8645, -8595, -8545, -8496, -8446, + -8396, -8346, -8296, -8246, -8196, -8147, -8097, -8047, + -7997, -7947, -7897, -7847, -7797, -7747, -7697, -7648, + -7598, -7548, -7498, -7448, -7398, -7348, -7298, -7248, + -7198, -7148, -7098, -7048, -6998, -6948, -6898, -6848, + -6798, -6748, -6698, -6648, -6598, -6548, -6498, -6448, + -6398, -6348, -6298, -6248, -6198, -6148, -6098, -6048, + -5998, -5948, -5898, -5848, -5798, -5747, -5697, -5647, + -5597, -5547, -5497, -5447, -5397, -5347, -5297, -5247, + -5197, -5146, -5096, -5046, -4996, -4946, -4896, -4846, + -4796, -4745, -4695, -4645, -4595, -4545, -4495, -4445, + -4394, -4344, -4294, -4244, -4194, -4144, -4093, -4043, + -3993, -3943, -3893, -3843, -3792, -3742, -3692, -3642, + -3592, -3541, -3491, -3441, -3391, -3341, -3291, -3240, + -3190, -3140, -3090, -3039, -2989, -2939, -2889, -2839, + -2788, -2738, -2688, -2638, -2588, -2537, -2487, -2437, + -2387, -2336, -2286, -2236, -2186, -2135, -2085, -2035, + -1985, -1934, -1884, -1834, -1784, -1733, -1683, -1633, + -1583, -1532, -1482, -1432, -1382, -1331, -1281, -1231, + -1181, -1130, -1080, -1030, -980, -929, -879, -829, + -779, -728, -678, -628, -578, -527, -477, -427, + -376, -326, -276, -226, -175, -125, -75, -25 + }; + + /* MAES: Java can't use that much direct init data in a single class (finesine alone is well beyond that) + * so a first workaround is to generate the data procedurally. + * If this proves to cause accuracy problems, an external data file could be used. + * Another workaround is to define the int table in its own file and then import or "implement" it. + */ + // Re-use data, is just PI/2 phase shift. + public static final int[] finecosine = new int[FINEANGLES]; + + /* MAES: Java can't use that much direct init data in a single class so a first workaround + * s to generate the data procedurally. + * If this proves to cause accuracy problems, an external data file could be used. + * Another workaround is to define the int table in its own file and then import + * or "implement" it. + */ + static { + System.arraycopy(finesine, FINEANGLES / 4, finecosine, 0, FINEANGLES); + } + + private SineCosine() { + + } + +} \ No newline at end of file diff --git a/doom/src/data/Tables.java b/doom/src/data/Tables.java new file mode 100644 index 0000000..7fa372d --- /dev/null +++ b/doom/src/data/Tables.java @@ -0,0 +1,474 @@ +package data; + +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: Tables.java,v 1.22 2011/05/06 09:21:59 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: Tables.java,v $ +// Revision 1.22 2011/05/06 09:21:59 velktron +// Cleaned up and reorganized common renderer code. +// +// Revision 1.21 2011/02/11 00:11:13 velktron +// A MUCH needed update to v1.3. +// +// Revision 1.20 2010/12/20 17:15:08 velktron +// Made the renderer more OO -> TextureManager and other changes as well. +// +// Revision 1.19 2010/12/10 17:38:56 velktron +// pspritescale fixed, weapon actions won't crash (but not work either). +// +// Revision 1.18 2010/11/25 20:12:44 velktron +// Fixed blockmap bug and viewangletox overflow bug. +// +// Revision 1.17 2010/11/22 01:17:16 velktron +// Fixed blockmap (for the most part), some actions implemented and functional, ambient animation/lighting functional. +// +// Revision 1.16 2010/11/17 23:55:06 velktron +// Kind of playable/controllable. +// +// Revision 1.15 2010/11/15 17:15:54 velktron +// Fixed masked columns rendering, introduced unrolled span and column functions from Boom (thanks, Lee Killough :-) +// +// Revision 1.14 2010/11/14 20:00:21 velktron +// Bleeding floor bug fixed! +// +// Revision 1.13 2010/11/12 13:37:25 velktron +// Rationalized the LUT system - now it's 100% procedurally generated. +// +// Revision 1.12 2010/11/03 16:48:04 velktron +// "Bling" view angles fixed (perhaps related to the "bleeding line bug"?) +// +// Revision 1.11 2010/10/14 18:37:14 velktron +// Rendering kinda works. Wow. +// +// Revision 1.10 2010/10/08 16:55:50 velktron +// Duh +// +// Revision 1.9 2010/10/01 16:47:51 velktron +// Fixed tab interception. +// +// Revision 1.8 2010/09/27 15:07:44 velktron +// meh +// +// Revision 1.7 2010/09/27 02:27:29 velktron +// BEASTLY update +// +// Revision 1.6 2010/09/22 16:40:02 velktron +// MASSIVE changes in the status passing model. +// DoomMain and DoomGame unified. +// Doomstat merged into DoomMain (now status and game functions are one). +// +// Most of DoomMain implemented. Possible to attempt a "classic type" start but will stop when reading sprites. +// +// Revision 1.5 2010/09/21 15:53:37 velktron +// Split the Map ...somewhat... +// +// Revision 1.4 2010/09/16 00:16:27 velktron +// Velvet FM 96.8 +// +// Revision 1.3 2010/09/15 16:17:38 velktron +// Arithmetic +// +// Revision 1.2 2010/09/09 16:09:09 velktron +// Yer more enhancements to the display system... +// +// Revision 1.1 2010/07/05 16:18:40 velktron +// YOU DON'T WANNA KNOW +// +// Revision 1.1 2010/06/30 08:58:51 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// Lookup tables. +// Do not try to look them up :-). +// In the order of appearance: +// +// int finetangent[4096] - Tangens LUT. +// Should work with BAM fairly well (12 of 16bit, +// effectively, by shifting). +// +// int finesine[10240] - Sine lookup. +// Guess what, serves as cosine, too. +// Remarkable thing is, how to use BAMs with this? +// +// int tantoangle[2049] - ArcTan LUT, +// maps tan(angle) to angle fast. Gotta search. +// +// +//----------------------------------------------------------------------------- +public final class Tables { + + public static final String rcsid = "$Id:"; + + public static final double PI = 3.141592657; + + /** Normally set to 12, and this sets the value of other constants too. + * Howevever changing it will also distort the view, resulting in a + * nightmare-like vision. There are some practical minimum and + * maximums as well. + * + * + */ + public static final int BITSPRECISION = 12; + public static final int FINEANGLES = 2 << BITSPRECISION; + public static final int FINETANS = FINEANGLES / 2; // 4096 for normal precision. + public static final int QUARTERMARK = 2 << BITSPRECISION - 2; + public static final int FINEMASK = (FINEANGLES - 1); + /** Mod long angle_t's with this value to cut off rollover */ + public static final long ANGLEMODULE = 0x100000000L; + + /** AND with this to remove unwanted sign extensions */ + public static final long BITS32 = 0x00000000FFFFFFFFL; + + /** Sign elimination */ + public static final int BITS31 = 0x7FFFFFFF; + + // Maes: we have to procedurally generate finesine/finecosine, else we run into a Java static limit. + // Either that, or I split the files. Guess what I did. + // public static int PRECISION = 10240 ; + /** 0x100000000 to 0x2000 */ + public static final int ANGLETOFINESHIFT = 31 - BITSPRECISION; + + /* Binary Angle Measurement. + * Some maths: their definition means that a range of 2pi is actually + * mapped to 2^32 values!!! But the lookup tables are only 8K (2^13) + * long (for sine/cosine), which means that we're 19 bits too precise + * -> ergo, >>ANGLETOFINESHIFT must be applied. + * + * Also, the original angle_t type was "unsigned int", so we should be + * using longs here. However, as BAM is used only after shifting, so + * using ints doesn't cause a problem for LUT access. + * + * However, some problems may arise with comparisons and ordinary arithmetic: + * e.g. ANG270 is supposed to be larger not only than ANG180, but also from + * ANG45, which does not hold true if those constants were stored as ints. + * + * As a rule of thumb, whenever you need to store JUST a BAM index, then + * ints are ok (actually, you'll have to cast down to int anyway). + * + * Whenever you need to add or compare angles directly however, you need + * longs. Furthermore, you must account for possible rollovers by modding + * with 0x100000000 or else long ints will store angles exceeding 360 degrees! + * Under no circumstances the value actually stored in the "long angles" should + * exceed 0xFFFFFFFF. + * + * An example: comparing any two long angles directly is OK, provided they were + * constructed correctly. + * + * Adding, subtracting, multiplying etc. with two or more angles however requires + * rollover compensation (e.g. result=(a+b+c) is wrong, result=(a+b+c)%0xFFFFFFFF + * is correct and will produce an angle you can "trust". + * + * + */ + /** Doom angle constants. */ + public static final long ANG45 = 0x20000000L, + ANG90 = 0x40000000L, + ANG180 = 0x80000000L, + ANG270 = 0xc0000000L; + + public static final int SLOPERANGE = (2 << (BITSPRECISION - 2)); // Normally 2048. + public static final int SLOPEBITS = BITSPRECISION - 1; + public static final int DBITS = FRACBITS - SLOPEBITS; + +// typedef unsigned angle_t; + // Effective size is 2049; + // The +1 size is to handle the case when x==y + // without additional checking. + //extern angle_t tantoangle[SLOPERANGE+1]; + /** + * Original size was 10240, but includes 5PI/4 periods. We can get away with + * ints on this one because of the small range. MAES: WTF? -64 ~ 64K + * range... so 17-bit accuracy? heh. + */ + public static final int[] finesine = new int[FINEANGLES + QUARTERMARK]; + public static final int[] finecosine = new int[FINEANGLES]; + + /** Any denominator smaller than 512 will result in + * maximum slope (45 degrees, or an index into tantoangle of 2048) + * The original method used unsigned args. So if this returns NEGATIVES + * in any way, it means you fucked up. Another "consistency" for Doom. + * Even though it was called upon fixed_t signed numbers. + * + */ + public static final int SlopeDiv(long num, long den) { + int ans; + + if (den < 512) { + return SLOPERANGE; + } + + ans = (int) ((num << 3) / (den >>> 8)); + + return ans <= SLOPERANGE ? ans : SLOPERANGE; + } + + /** Finetangent table. It only has 4096 values corresponding roughly + * to -90/+90 angles, with values between are -/+ 2607 for "infinity". + * + * Since in vanilla accesses to the table can overflow way beyond 4096 + * indexes, the access index must be clipped to 4K tops via an accessor, + * or, in order to simulate some aspects of vanilla overflowing, replicate + * 4K of finesine's values AFTER the 4K index. This removes the need + * for access checking, at the cost of some extra memory. It also allows + * a small degree of "vanilla like" compatibility. + * + * + */ + public final static int[] finetangent = new int[2 * FINETANS]; + +// MAES: original range 2049 +// This obviously +// Range goes from 0x00000000 to 0x20000000, so in theory plain ints should be enough... + /** This maps a value 0-2048 to a BAM unsigned integer angle, ranging from 0x0 to 0x2000000: + * + * In practice, this means there are only tangent values for angles up to 45 degrees. + * + * These values are valid BAM measurements in the first quadrant + * + * + */ + public static final int[] tantoangle = new int[SLOPERANGE + 1]; + + /** Use this to get a value from the finesine table. It will be automatically shifte, + * Equivalent to finesine[angle>>>ANGLETOFINESHIFT] + * + * @param angle in BAM units + * @return + */ + public static final int finesine(int angle) { + return finesine[angle >>> ANGLETOFINESHIFT]; + } + + /** Use this to get a value from the finesine table using a long argument. + * It will automatically shift, apply rollover module and cast. + * + * Equivalent to finesine[(int) ((angle>>ANGLETOFINESHIFT)%ANGLEMODULE)]; + * + * @param angle in BAM units + * @return + */ + public static final int finesine(long angle) { + return finesine[(int) ((angle & BITS32) >>> ANGLETOFINESHIFT)]; + } + + /** Use this to get a value from the finecosine table. It will be automatically shifted, + * Equivalent to finecosine[angle>>>ANGLETOFINESHIFT] + * @param angle in BAM units + * @return + */ + public static final int finecosine(int angle) { + return finecosine[angle >>> ANGLETOFINESHIFT]; + } + + /** Use this to get a value from the finecosine table. + * It will automatically shift, apply rollover module and cast. + * + * Equivalent to finecosine[(int) ((angle&BITS32)>>>ANGLETOFINESHIFT)] + * @param angle in BAM units + * @return + */ + public static final int finecosine(long angle) { + return finecosine[(int) ((angle & BITS32) >>> ANGLETOFINESHIFT)]; + } + + /** Compare BAM angles in 32-bit format + * "Greater or Equal" bam0>bam1 + * */ + public static final boolean GE(int bam0, int bam1) { + // Handle easy case. + if (bam0 == bam1) { + return true; + } + + // bam0 is greater than 180 degrees. + if (bam0 < 0 && bam1 >= 0) { + return true; + } + // bam1 is greater than 180 degrees. + if (bam0 >= 0 && bam1 < 0) { + return false; + } + + // Both "greater than 180", No other way to compare. + bam0 &= BITS31; + bam1 &= BITS31; + return bam0 > bam1; + } + + public static final boolean GT(int bam0, int bam1) { + // bam0 is greater than 180 degrees. + if (bam0 < 0 && bam1 >= 0) { + return true; + } + // bam1 is greater than 180 degrees. + if (bam0 >= 0 && bam1 < 0) { + return false; + } + + // Both "greater than 180", No other way to compare. + bam0 &= BITS31; + bam1 &= BITS31; + return bam0 > bam1; + } + + public static final int BAMDiv(int bam0, int bam1) { + // bam0 is greater than 180 degrees. + if (bam0 >= 0) { + return bam0 / bam1; + } + // bam0 is greater than 180 degrees. + // We have to make is so that ANG270 0xC0000000 becomes ANG135, aka 60000000 + if (bam1 >= 0) { + return (int) ((long) (0x0FFFFFFFFL & bam0) / bam1); + } + + return (int) ((long) (0x0FFFFFFFFL & bam0) / (0x0FFFFFFFFL & bam1)); + } + + /** Converts a long angle to a BAM LUT-ready angle (13 bits, between 0-8191). + * Cuts away rollover. + * + * @param angle + * @return + */ + public static final int toBAMIndex(long angle) { + return (int) ((angle & BITS32) >>> ANGLETOFINESHIFT); + } + + /** Converts a long angle to a TAN BAM LUT-ready angle (12 bits, between 0-4195). + * Cuts away rollover. + * + * @param angle + * @return + */ + public static final int toFineTanIndex(long angle) { + return (int) ((angle & BITS31) >>> ANGLETOFINESHIFT); + } + + /** Converts an 32-bit int angle to a BAM LUT-ready angle (13 bits, between 0-8192). + * + * @param angle + * @return + */ + public static final int toBAMIndex(int angle) { + return angle >>> ANGLETOFINESHIFT; + } + + /** Add two long angles and correct for overflow */ + public static final long addAngles(long a, long b) { + return ((a + b) & BITS32); + } + + /** MAES: I brought this function "back from the dead" since + * Java has some pretty low static limits for statically defined LUTs. + * In order to keep the codebase clutter and static allocation to a minimum, + * I decided to procedurally generate the tables during runtime, + * using the original functions. + * + * The code has been thoroughly checked in both Sun's JDK and GCC and was + * found to, indeed, produce the same values found in the finesine/finecosine + * and finetangent tables, at least on Intel. + * + * The "tantoangle" table is also generated procedurally, but since there + * was no "dead code" to build upon, it was recreated through reverse + * engineering and also found to be 100% faithful to the original data. + * + * + */ + public static void InitTables() { + int i; + float a; + float fv; + int t; + + // viewangle tangent table + for (i = 0; i < FINEANGLES / 2; i++) { + a = (float) ((i - FINEANGLES / 4 + 0.5) * PI * 2) / FINEANGLES; + fv = (float) (FRACUNIT * Math.tan(a)); + t = (int) fv; + finetangent[i] = t; + } + + // finesine table + for (i = 0; i < FINEANGLES + QUARTERMARK; i++) { + // OPTIMIZE: mirror... + a = (float) ((i + 0.5) * PI * 2) / FINEANGLES; + t = (int) (FRACUNIT * Math.sin(a)); + finesine[i] = t; + if (i >= QUARTERMARK) { + finecosine[i - QUARTERMARK] = t; + } + } + + // HACK: replicate part of finesine after finetangent, to + // simulate overflow behavior and remove need for capping + // indexes + // viewangle tangent table + for (i = FINEANGLES / 2; i < FINEANGLES; i++) { + finetangent[i] = finesine[i - FINEANGLES / 2]; + } + + /* tantoangle table + * There was actually no dead code for that one, so this is a close recreation. + * Since there are 2049 values, and the maximum angle considered is 536870912 (0x20000000) + * which is 45 degrees in BAM, we have to fill in the atan for values up to 45 degrees. + * Since the argument is a slope ranging from 0...2048, we have 2049 equally spaced (?) + * values, with 2048 being being the unitary slope (0x10000 in fixed_t). That value is only + * accessed in special cases (overflow) so we only need to consider 0..2047 aka 11 bits. + * So: we take "minislopes" 0-2048, we blow them up to a full fixed_t unit with <<5. + * We make this into a float (?), then use trigonometric ATAN, and then go to BAM. + * + * Any questions? + * + */ + /* This is the recreated code + for (i=0 ; i 0, then it is in use) + public int usefulness; + + // lump number of sfx + public int lumpnum; + + public sfxinfo_t(String name, boolean singularity, int priority, + sfxinfo_t link, int pitch, int volume, byte[] data, + int usefulness, int lumpnum) { + this.name = name; + this.singularity = singularity; + this.priority = priority; + this.link = link; + this.pitch = pitch; + this.volume = volume; + this.data = data; + this.usefulness = usefulness; + this.lumpnum = lumpnum; + } + + /** MAES: Call this constructor if you don't want a cross-linked sound. + * + * @param name + * @param singularity + * @param priority + * @param pitch + * @param volume + * @param usefulness + */ + public sfxinfo_t(String name, boolean singularity, int priority, + int pitch, int volume, int usefulness) { + this.name = name; + this.singularity = singularity; + this.priority = priority; + this.linked = false; + this.pitch = pitch; + this.volume = volume; + this.usefulness = usefulness; + } + + public sfxinfo_t(String name, boolean singularity, int priority, boolean linked, + int pitch, int volume, int usefulness) { + this.name = name; + this.singularity = singularity; + this.priority = priority; + this.linked = linked; + this.pitch = pitch; + this.volume = volume; + this.usefulness = usefulness; + } + + public int identify(sfxinfo_t[] array) { + for (int i = 0; i < array.length; i++) { + if (array[i] == this) { + return i; + } + } + // Duh + return 0; + } + +}; \ No newline at end of file diff --git a/doom/src/data/sounds.java b/doom/src/data/sounds.java new file mode 100644 index 0000000..5ba12c0 --- /dev/null +++ b/doom/src/data/sounds.java @@ -0,0 +1,442 @@ +package data; +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: sounds.java,v 1.1 2010/06/30 08:58:51 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: sounds.java,v $ +// Revision 1.1 2010/06/30 08:58:51 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// Created by a sound utility. +// Kept as a sample, DOOM2 sounds. +// +//----------------------------------------------------------------------------- + +public class sounds { + +// +// Information about all the music +// + public static musicinfo_t[] S_music + = { + new musicinfo_t(null), + new musicinfo_t("e1m1", 0), + new musicinfo_t("e1m2", 0), + new musicinfo_t("e1m3", 0), + new musicinfo_t("e1m4", 0), + new musicinfo_t("e1m5", 0), + new musicinfo_t("e1m6", 0), + new musicinfo_t("e1m7", 0), + new musicinfo_t("e1m8", 0), + new musicinfo_t("e1m9", 0), + new musicinfo_t("e2m1", 0), + new musicinfo_t("e2m2", 0), + new musicinfo_t("e2m3", 0), + new musicinfo_t("e2m4", 0), + new musicinfo_t("e2m5", 0), + new musicinfo_t("e2m6", 0), + new musicinfo_t("e2m7", 0), + new musicinfo_t("e2m8", 0), + new musicinfo_t("e2m9", 0), + new musicinfo_t("e3m1", 0), + new musicinfo_t("e3m2", 0), + new musicinfo_t("e3m3", 0), + new musicinfo_t("e3m4", 0), + new musicinfo_t("e3m5", 0), + new musicinfo_t("e3m6", 0), + new musicinfo_t("e3m7", 0), + new musicinfo_t("e3m8", 0), + new musicinfo_t("e3m9", 0), + new musicinfo_t("inter", 0), + new musicinfo_t("intro", 0), + new musicinfo_t("bunny", 0), + new musicinfo_t("victor", 0), + new musicinfo_t("introa", 0), + new musicinfo_t("runnin", 0), + new musicinfo_t("stalks", 0), + new musicinfo_t("countd", 0), + new musicinfo_t("betwee", 0), + new musicinfo_t("doom", 0), + new musicinfo_t("the_da", 0), + new musicinfo_t("shawn", 0), + new musicinfo_t("ddtblu", 0), + new musicinfo_t("in_cit", 0), + new musicinfo_t("dead", 0), + new musicinfo_t("stlks2", 0), + new musicinfo_t("theda2", 0), + new musicinfo_t("doom2", 0), + new musicinfo_t("ddtbl2", 0), + new musicinfo_t("runni2", 0), + new musicinfo_t("dead2", 0), + new musicinfo_t("stlks3", 0), + new musicinfo_t("romero", 0), + new musicinfo_t("shawn2", 0), + new musicinfo_t("messag", 0), + new musicinfo_t("count2", 0), + new musicinfo_t("ddtbl3", 0), + new musicinfo_t("ampie", 0), + new musicinfo_t("theda3", 0), + new musicinfo_t("adrian", 0), + new musicinfo_t("messg2", 0), + new musicinfo_t("romer2", 0), + new musicinfo_t("tense", 0), + new musicinfo_t("shawn3", 0), + new musicinfo_t("openin", 0), + new musicinfo_t("evil", 0), + new musicinfo_t("ultima", 0), + new musicinfo_t("read_m", 0), + new musicinfo_t("dm2ttl", 0), + new musicinfo_t("dm2int", 0) + }; + +// +// Information about all the sfx +// + public static sfxinfo_t nullSfxLink; + + public static sfxinfo_t[] S_sfx + = { + // S_sfx[0] needs to be a dummy for odd reasons. + new sfxinfo_t("none", false, 0, -1, -1, 0), + new sfxinfo_t("pistol", false, 64, -1, -1, 0), + new sfxinfo_t("shotgn", false, 64, -1, -1, 0), + new sfxinfo_t("sgcock", false, 64, -1, -1, 0), + new sfxinfo_t("dshtgn", false, 64, -1, -1, 0), + new sfxinfo_t("dbopn", false, 64, -1, -1, 0), + new sfxinfo_t("dbcls", false, 64, -1, -1, 0), + new sfxinfo_t("dbload", false, 64, -1, -1, 0), + new sfxinfo_t("plasma", false, 64, -1, -1, 0), + new sfxinfo_t("bfg", false, 64, -1, -1, 0), + new sfxinfo_t("sawup", false, 64, -1, -1, 0), + new sfxinfo_t("sawidl", false, 118, -1, -1, 0), + new sfxinfo_t("sawful", false, 64, -1, -1, 0), + new sfxinfo_t("sawhit", false, 64, -1, -1, 0), + new sfxinfo_t("rlaunc", false, 64, -1, -1, 0), + new sfxinfo_t("rxplod", false, 70, -1, -1, 0), + new sfxinfo_t("firsht", false, 70, -1, -1, 0), + new sfxinfo_t("firxpl", false, 70, -1, -1, 0), + new sfxinfo_t("pstart", false, 100, -1, -1, 0), + new sfxinfo_t("pstop", false, 100, -1, -1, 0), + new sfxinfo_t("doropn", false, 100, -1, -1, 0), + new sfxinfo_t("dorcls", false, 100, -1, -1, 0), + new sfxinfo_t("stnmov", false, 119, -1, -1, 0), + new sfxinfo_t("swtchn", false, 78, -1, -1, 0), + new sfxinfo_t("swtchx", false, 78, -1, -1, 0), + new sfxinfo_t("plpain", false, 96, -1, -1, 0), + new sfxinfo_t("dmpain", false, 96, -1, -1, 0), + new sfxinfo_t("popain", false, 96, -1, -1, 0), + new sfxinfo_t("vipain", false, 96, -1, -1, 0), + new sfxinfo_t("mnpain", false, 96, -1, -1, 0), + new sfxinfo_t("pepain", false, 96, -1, -1, 0), + new sfxinfo_t("slop", false, 78, -1, -1, 0), + new sfxinfo_t("itemup", true, 78, -1, -1, 0), + new sfxinfo_t("wpnup", true, 78, -1, -1, 0), + new sfxinfo_t("oof", false, 96, -1, -1, 0), + new sfxinfo_t("telept", false, 32, -1, -1, 0), + new sfxinfo_t("posit1", true, 98, -1, -1, 0), + new sfxinfo_t("posit2", true, 98, -1, -1, 0), + new sfxinfo_t("posit3", true, 98, -1, -1, 0), + new sfxinfo_t("bgsit1", true, 98, -1, -1, 0), + new sfxinfo_t("bgsit2", true, 98, -1, -1, 0), + new sfxinfo_t("sgtsit", true, 98, -1, -1, 0), + new sfxinfo_t("cacsit", true, 98, -1, -1, 0), + new sfxinfo_t("brssit", true, 94, -1, -1, 0), + new sfxinfo_t("cybsit", true, 92, -1, -1, 0), + new sfxinfo_t("spisit", true, 90, -1, -1, 0), + new sfxinfo_t("bspsit", true, 90, -1, -1, 0), + new sfxinfo_t("kntsit", true, 90, -1, -1, 0), + new sfxinfo_t("vilsit", true, 90, -1, -1, 0), + new sfxinfo_t("mansit", true, 90, -1, -1, 0), + new sfxinfo_t("pesit", true, 90, -1, -1, 0), + new sfxinfo_t("sklatk", false, 70, -1, -1, 0), + new sfxinfo_t("sgtatk", false, 70, -1, -1, 0), + new sfxinfo_t("skepch", false, 70, -1, -1, 0), + new sfxinfo_t("vilatk", false, 70, -1, -1, 0), + new sfxinfo_t("claw", false, 70, -1, -1, 0), + new sfxinfo_t("skeswg", false, 70, -1, -1, 0), + new sfxinfo_t("pldeth", false, 32, -1, -1, 0), + new sfxinfo_t("pdiehi", false, 32, -1, -1, 0), + new sfxinfo_t("podth1", false, 70, -1, -1, 0), + new sfxinfo_t("podth2", false, 70, -1, -1, 0), + new sfxinfo_t("podth3", false, 70, -1, -1, 0), + new sfxinfo_t("bgdth1", false, 70, -1, -1, 0), + new sfxinfo_t("bgdth2", false, 70, -1, -1, 0), + new sfxinfo_t("sgtdth", false, 70, -1, -1, 0), + new sfxinfo_t("cacdth", false, 70, -1, -1, 0), + new sfxinfo_t("skldth", false, 70, -1, -1, 0), + new sfxinfo_t("brsdth", false, 32, -1, -1, 0), + new sfxinfo_t("cybdth", false, 32, -1, -1, 0), + new sfxinfo_t("spidth", false, 32, -1, -1, 0), + new sfxinfo_t("bspdth", false, 32, -1, -1, 0), + new sfxinfo_t("vildth", false, 32, -1, -1, 0), + new sfxinfo_t("kntdth", false, 32, -1, -1, 0), + new sfxinfo_t("pedth", false, 32, -1, -1, 0), + new sfxinfo_t("skedth", false, 32, -1, -1, 0), + new sfxinfo_t("posact", true, 120, -1, -1, 0), + new sfxinfo_t("bgact", true, 120, -1, -1, 0), + new sfxinfo_t("dmact", true, 120, -1, -1, 0), + new sfxinfo_t("bspact", true, 100, -1, -1, 0), + new sfxinfo_t("bspwlk", true, 100, -1, -1, 0), + new sfxinfo_t("vilact", true, 100, -1, -1, 0), + new sfxinfo_t("noway", false, 78, -1, -1, 0), + new sfxinfo_t("barexp", false, 60, -1, -1, 0), + new sfxinfo_t("punch", false, 64, -1, -1, 0), + new sfxinfo_t("hoof", false, 70, -1, -1, 0), + new sfxinfo_t("metal", false, 70, -1, -1, 0), + // MAES: here C referenced a field before it was defined. + // We'll make do by defining a new "linked" boolean field, and + // handling special cases in a separate initializer. + new sfxinfo_t("chgun", false, 64, true, 150, 0, 0), + new sfxinfo_t("tink", false, 60, -1, -1, 0), + new sfxinfo_t("bdopn", false, 100, -1, -1, 0), + new sfxinfo_t("bdcls", false, 100, -1, -1, 0), + new sfxinfo_t("itmbk", false, 100, -1, -1, 0), + new sfxinfo_t("flame", false, 32, -1, -1, 0), + new sfxinfo_t("flamst", false, 32, -1, -1, 0), + new sfxinfo_t("getpow", false, 60, -1, -1, 0), + new sfxinfo_t("bospit", false, 70, -1, -1, 0), + new sfxinfo_t("boscub", false, 70, -1, -1, 0), + new sfxinfo_t("bossit", false, 70, -1, -1, 0), + new sfxinfo_t("bospn", false, 70, -1, -1, 0), + new sfxinfo_t("bosdth", false, 70, -1, -1, 0), + new sfxinfo_t("manatk", false, 70, -1, -1, 0), + new sfxinfo_t("mandth", false, 70, -1, -1, 0), + new sfxinfo_t("sssit", false, 70, -1, -1, 0), + new sfxinfo_t("ssdth", false, 70, -1, -1, 0), + new sfxinfo_t("keenpn", false, 70, -1, -1, 0), + new sfxinfo_t("keendt", false, 70, -1, -1, 0), + new sfxinfo_t("skeact", false, 70, -1, -1, 0), + new sfxinfo_t("skesit", false, 70, -1, -1, 0), + new sfxinfo_t("skeatk", false, 70, -1, -1, 0), + new sfxinfo_t("radio", false, 60, -1, -1, 0) + }; + + /** MAES: This method is here to handle exceptions in definitions of static sfx. + * Only the chaingun sound needs to be cross-linked to the pistol sound, but in + * Java you can't do that until the array is actually created. So remember to run + * this explicitly, and add any further rules you want! + * + */ + public static void init() { + for (int i = 0; i < S_sfx.length; i++) { + if (S_sfx[i].linked) { + // MAES: Rule for chgun + if (S_sfx[i].name.compareToIgnoreCase("chgun") == 0) { + S_sfx[i].link = S_sfx[sfxenum_t.sfx_pistol.ordinal()]; + } + } + } + + } + + public static enum musicenum_t { + mus_None, + mus_e1m1, + mus_e1m2, + mus_e1m3, + mus_e1m4, + mus_e1m5, + mus_e1m6, + mus_e1m7, + mus_e1m8, + mus_e1m9, + mus_e2m1, + mus_e2m2, + mus_e2m3, + mus_e2m4, + mus_e2m5, + mus_e2m6, + mus_e2m7, + mus_e2m8, + mus_e2m9, + mus_e3m1, + mus_e3m2, + mus_e3m3, + mus_e3m4, + mus_e3m5, + mus_e3m6, + mus_e3m7, + mus_e3m8, + mus_e3m9, + mus_inter, + mus_intro, + mus_bunny, + mus_victor, + mus_introa, + mus_runnin, + mus_stalks, + mus_countd, + mus_betwee, + mus_doom, + mus_the_da, + mus_shawn, + mus_ddtblu, + mus_in_cit, + mus_dead, + mus_stlks2, + mus_theda2, + mus_doom2, + mus_ddtbl2, + mus_runni2, + mus_dead2, + mus_stlks3, + mus_romero, + mus_shawn2, + mus_messag, + mus_count2, + mus_ddtbl3, + mus_ampie, + mus_theda3, + mus_adrian, + mus_messg2, + mus_romer2, + mus_tense, + mus_shawn3, + mus_openin, + mus_evil, + mus_ultima, + mus_read_m, + mus_dm2ttl, + mus_dm2int, + NUMMUSIC + }; + + public static enum sfxenum_t { + sfx_None, + sfx_pistol, + sfx_shotgn, + sfx_sgcock, + sfx_dshtgn, + sfx_dbopn, + sfx_dbcls, + sfx_dbload, + sfx_plasma, + sfx_bfg, + sfx_sawup, + sfx_sawidl, + sfx_sawful, + sfx_sawhit, + sfx_rlaunc, + sfx_rxplod, + sfx_firsht, + sfx_firxpl, + sfx_pstart, + sfx_pstop, + sfx_doropn, + sfx_dorcls, + sfx_stnmov, + sfx_swtchn, + sfx_swtchx, + sfx_plpain, + sfx_dmpain, + sfx_popain, + sfx_vipain, + sfx_mnpain, + sfx_pepain, + sfx_slop, + sfx_itemup, + sfx_wpnup, + sfx_oof, + sfx_telept, + sfx_posit1, + sfx_posit2, + sfx_posit3, + sfx_bgsit1, + sfx_bgsit2, + sfx_sgtsit, + sfx_cacsit, + sfx_brssit, + sfx_cybsit, + sfx_spisit, + sfx_bspsit, + sfx_kntsit, + sfx_vilsit, + sfx_mansit, + sfx_pesit, + sfx_sklatk, + sfx_sgtatk, + sfx_skepch, + sfx_vilatk, + sfx_claw, + sfx_skeswg, + sfx_pldeth, + sfx_pdiehi, + sfx_podth1, + sfx_podth2, + sfx_podth3, + sfx_bgdth1, + sfx_bgdth2, + sfx_sgtdth, + sfx_cacdth, + sfx_skldth, + sfx_brsdth, + sfx_cybdth, + sfx_spidth, + sfx_bspdth, + sfx_vildth, + sfx_kntdth, + sfx_pedth, + sfx_skedth, + sfx_posact, + sfx_bgact, + sfx_dmact, + sfx_bspact, + sfx_bspwlk, + sfx_vilact, + sfx_noway, + sfx_barexp, + sfx_punch, + sfx_hoof, + sfx_metal, + sfx_chgun, + sfx_tink, + sfx_bdopn, + sfx_bdcls, + sfx_itmbk, + sfx_flame, + sfx_flamst, + sfx_getpow, + sfx_bospit, + sfx_boscub, + sfx_bossit, + sfx_bospn, + sfx_bosdth, + sfx_manatk, + sfx_mandth, + sfx_sssit, + sfx_ssdth, + sfx_keenpn, + sfx_keendt, + sfx_skeact, + sfx_skesit, + sfx_skeatk, + sfx_radio, + NUMSFX + }; + +} \ No newline at end of file diff --git a/doom/src/data/spritenum_t.java b/doom/src/data/spritenum_t.java new file mode 100644 index 0000000..110ce29 --- /dev/null +++ b/doom/src/data/spritenum_t.java @@ -0,0 +1,144 @@ +package data; + +/** This is actually used as a data type */ +public enum spritenum_t { + SPR_TROO, + SPR_SHTG, + SPR_PUNG, + SPR_PISG, + SPR_PISF, + SPR_SHTF, + SPR_SHT2, + SPR_CHGG, + SPR_CHGF, + SPR_MISG, + SPR_MISF, + SPR_SAWG, + SPR_PLSG, + SPR_PLSF, + SPR_BFGG, + SPR_BFGF, + SPR_BLUD, + SPR_PUFF, + SPR_BAL1, + SPR_BAL2, + SPR_PLSS, + SPR_PLSE, + SPR_MISL, + SPR_BFS1, + SPR_BFE1, + SPR_BFE2, + SPR_TFOG, + SPR_IFOG, + SPR_PLAY, + SPR_POSS, + SPR_SPOS, + SPR_VILE, + SPR_FIRE, + SPR_FATB, + SPR_FBXP, + SPR_SKEL, + SPR_MANF, + SPR_FATT, + SPR_CPOS, + SPR_SARG, + SPR_HEAD, + SPR_BAL7, + SPR_BOSS, + SPR_BOS2, + SPR_SKUL, + SPR_SPID, + SPR_BSPI, + SPR_APLS, + SPR_APBX, + SPR_CYBR, + SPR_PAIN, + SPR_SSWV, + SPR_KEEN, + SPR_BBRN, + SPR_BOSF, + SPR_ARM1, + SPR_ARM2, + SPR_BAR1, + SPR_BEXP, + SPR_FCAN, + SPR_BON1, + SPR_BON2, + SPR_BKEY, + SPR_RKEY, + SPR_YKEY, + SPR_BSKU, + SPR_RSKU, + SPR_YSKU, + SPR_STIM, + SPR_MEDI, + SPR_SOUL, + SPR_PINV, + SPR_PSTR, + SPR_PINS, + SPR_MEGA, + SPR_SUIT, + SPR_PMAP, + SPR_PVIS, + SPR_CLIP, + SPR_AMMO, + SPR_ROCK, + SPR_BROK, + SPR_CELL, + SPR_CELP, + SPR_SHEL, + SPR_SBOX, + SPR_BPAK, + SPR_BFUG, + SPR_MGUN, + SPR_CSAW, + SPR_LAUN, + SPR_PLAS, + SPR_SHOT, + SPR_SGN2, + SPR_COLU, + SPR_SMT2, + SPR_GOR1, + SPR_POL2, + SPR_POL5, + SPR_POL4, + SPR_POL3, + SPR_POL1, + SPR_POL6, + SPR_GOR2, + SPR_GOR3, + SPR_GOR4, + SPR_GOR5, + SPR_SMIT, + SPR_COL1, + SPR_COL2, + SPR_COL3, + SPR_COL4, + SPR_CAND, + SPR_CBRA, + SPR_COL6, + SPR_TRE1, + SPR_TRE2, + SPR_ELEC, + SPR_CEYE, + SPR_FSKU, + SPR_COL5, + SPR_TBLU, + SPR_TGRN, + SPR_TRED, + SPR_SMBT, + SPR_SMGT, + SPR_SMRT, + SPR_HDB1, + SPR_HDB2, + SPR_HDB3, + SPR_HDB4, + SPR_HDB5, + SPR_HDB6, + SPR_POB1, + SPR_POB2, + SPR_BRS1, + SPR_TLMP, + SPR_TLP2, + NUMSPRITES +}; \ No newline at end of file diff --git a/doom/src/data/state_t.java b/doom/src/data/state_t.java new file mode 100644 index 0000000..0c5dad7 --- /dev/null +++ b/doom/src/data/state_t.java @@ -0,0 +1,80 @@ +package data; + +import static data.Defines.TIC_MUL; +import defines.statenum_t; +import p.ActiveStates; +import static p.ActiveStates.NOP; + +public class state_t { + + public state_t() { + + } + + public state_t(spritenum_t sprite, int frame, int tics, ActiveStates action, statenum_t nextstate, int misc1, int misc2) { + this.sprite = sprite; + this.frame = frame; + this.tics = tics * TIC_MUL; + this.action = action == null ? NOP : action; + this.nextstate = nextstate; + this.misc1 = misc1; + this.misc2 = misc2; + } + + public spritenum_t sprite; + /** + * The frame should indicate which one of the frames available in the + * available spritenum should be used. This can also be flagged with + * 0x8000 indicating bright sprites. + */ + + public int frame; + public int tics; + //TODO: proper implementation of (*action) + // MAES: was actionp_t... which is typedeffed to ActionFunction anyway, + // and this is the only place it's invoked explicitly. + /** + * OK...this is the most infamous part of Doom to implement in Java. + * We can't have proper "function pointers" in java without either losing a LOT + * of speed (through reflection) or cluttering syntax and heap significantly + * (callback objects, which also need to be aware of context). + * Therefore, I decided to implement an "action dispatcher". + * This a + * + */ + public ActiveStates action; + + public statenum_t nextstate; + public int misc1, misc2; + + /** + * relative index in state array. Needed sometimes. + */ + public int id; + + @Override + public String toString() { + sb.setLength(0); + sb.append(this.getClass().getName()); + sb.append(" sprite "); + sb.append(this.sprite.name()); + sb.append(" frame "); + sb.append(this.frame); + + return sb.toString(); + + } + + protected static StringBuilder sb = new StringBuilder(); + + /*@Override + public void read(DoomFile f) throws IOException { + this.sprite = spritenum_t.values()[f.readLEInt()]; + this.frame = f.readLEInt(); + this.tics = f.readLong(); + this.action = ActionFunction.values()[f.readInt()]; + this.nextstate = statenum_t.values()[f.readInt()]; + this.misc1 = f.readInt(); + this.misc2 = f.readInt(); + } */ +} \ No newline at end of file diff --git a/doom/src/defines/DoomVersion.java b/doom/src/defines/DoomVersion.java new file mode 100644 index 0000000..10a77b1 --- /dev/null +++ b/doom/src/defines/DoomVersion.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package defines; + +import doom.DoomMain; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import utils.C2JUtils; +import static utils.C2JUtils.testReadAccess; + +public enum DoomVersion { + DOOM2F_WAD("doom2f.wad"), + DOOM2_WAD("doom2.wad"), + PLUTONIA_WAD("plutonia.wad"), + TNT_WAD("tnt.wad"), + XBLA_WAD("xbla.wad"), + DOOMU_WAD("doomu.wad"), + UDOOM_WAD("udoom.wad"), + DOOM_WAD("doom.wad"), + DOOM1_WAD("doom1.wad"), + FREEDM_WAD("freedm.wad"), + FREEDOOM1_WAD("freedoom1.wad"), + FREEDOOM2_WAD("freedoom2.wad"); + + public final String wadFileName; + + private DoomVersion(String wadFileName) { + this.wadFileName = wadFileName; + } + + private static final Logger LOGGER = Loggers.getLogger(DoomVersion.class.getName()); + + /** + * Try all versions in given doomwaddir + * + * @return full path to the wad of success + */ + public static String tryAllWads(final DoomMain DOOM, final String doomwaddir) { + for (DoomVersion v : values()) { + final String vFullPath = doomwaddir + '/' + v.wadFileName; + if (testReadAccess(vFullPath)) { + DOOM.setGameMode(GameMode.forVersion(v)); + if (v == DOOM2F_WAD) { + // C'est ridicule! + // Let's handle languages in config files, okay? + DOOM.language = Language_t.french; + //System.out.println("French version\n"); + } + + return vFullPath; + } + } + + return null; + } + + /** + * Try only one IWAD. + * + * @param iwad + * @param doomwaddir + * @return game mode + */ + public static GameMode tryOnlyOne(String iwad, String doomwaddir) { + try { + // Is it a known and valid version? + final DoomVersion v = DoomVersion.valueOf(iwad.trim().toUpperCase().replace('.', '_')); + final GameMode tmp = GameMode.forVersion(v); + + // Can we read it? + if (tmp != null && C2JUtils.testReadAccess(doomwaddir + iwad)) { + return tmp; // Yes, so communicate the gamemode back. + } + + } catch (IllegalArgumentException ex) { + LOGGER.log(Level.WARNING, iwad, ex); + } + + // It's either invalid or we can't read it. + // Fuck that. + return null; + } +} \ No newline at end of file diff --git a/doom/src/defines/GameMission_t.java b/doom/src/defines/GameMission_t.java new file mode 100644 index 0000000..b345e92 --- /dev/null +++ b/doom/src/defines/GameMission_t.java @@ -0,0 +1,12 @@ +package defines; + +/** Mission packs - might be useful for TC stuff? + * + */ +public enum GameMission_t { + doom, // DOOM 1 + doom2, // DOOM 2 + pack_tnt, // TNT mission pack + pack_plut, // Plutonia pack + none +} \ No newline at end of file diff --git a/doom/src/defines/GameMode.java b/doom/src/defines/GameMode.java new file mode 100644 index 0000000..36a20b5 --- /dev/null +++ b/doom/src/defines/GameMode.java @@ -0,0 +1,75 @@ +package defines; + +import static defines.DoomVersion.DOOM1_WAD; +import static defines.DoomVersion.DOOM2F_WAD; +import static defines.DoomVersion.DOOM2_WAD; +import static defines.DoomVersion.DOOMU_WAD; +import static defines.DoomVersion.DOOM_WAD; +import static defines.DoomVersion.FREEDM_WAD; +import static defines.DoomVersion.FREEDOOM1_WAD; +import static defines.DoomVersion.FREEDOOM2_WAD; +import static defines.DoomVersion.PLUTONIA_WAD; +import static defines.DoomVersion.TNT_WAD; +import static defines.DoomVersion.UDOOM_WAD; +import static defines.DoomVersion.XBLA_WAD; +import doom.CommandVariable; + +/** + * Game mode handling - identify IWAD version to handle IWAD dependend animations etc. + */ +public enum GameMode { + shareware("data_se", DOOM1_WAD, CommandVariable.SHDEV), // DOOM 1 shareware, E1, M9 + registered("data_se", DOOM_WAD, CommandVariable.REGDEV), // DOOM 1 registered, E3, M27 + commercial("cdata", DOOM2_WAD, CommandVariable.COMDEV), // DOOM 2 retail, E1 M34 + // DOOM 2 german edition not handled + retail("data_se", DOOMU_WAD, CommandVariable.REGDEV), // DOOM 1 retail, E4, M36 + pack_tnt("cdata", TNT_WAD, CommandVariable.COMDEV), // TNT mission pack + pack_plut("cdata", PLUTONIA_WAD, CommandVariable.COMDEV), // Plutonia pack + pack_xbla("cdata", XBLA_WAD, CommandVariable.COMDEV), // XBLA Doom. How you got hold of it, I don't care :-p + freedm("cdata", FREEDM_WAD, CommandVariable.FRDMDEV), // FreeDM + freedoom1("data_se", FREEDOOM1_WAD, CommandVariable.FR1DEV), // Freedoom phase 1 + freedoom2("cdata", FREEDOOM2_WAD, CommandVariable.FR2DEV), // Freedoom phase 2 + indetermined("data_se", null, null); // Well, no IWAD found. + + public final String devDir; + public final DoomVersion version; + public final CommandVariable devVar; + + public static GameMode forVersion(DoomVersion v) { + switch (v) { + case DOOM1_WAD: + return shareware; + case DOOM2F_WAD: + case DOOM2_WAD: + return commercial; + case DOOMU_WAD: + case UDOOM_WAD: + return retail; + case DOOM_WAD: + return registered; + case FREEDM_WAD: + return freedm; + case FREEDOOM1_WAD: + return freedoom1; + case FREEDOOM2_WAD: + return freedoom2; + case PLUTONIA_WAD: + return pack_plut; + case TNT_WAD: + return pack_tnt; + case XBLA_WAD: + return pack_xbla; + } + return null; + } + + private GameMode(String devDir, DoomVersion version, CommandVariable devVar) { + this.devDir = devDir; + this.version = version; + this.devVar = devVar; + } + + public boolean hasTexture2() { + return !(this == GameMode.shareware || this == GameMode.freedoom2 || this == GameMode.commercial); + } +}; \ No newline at end of file diff --git a/doom/src/defines/Language_t.java b/doom/src/defines/Language_t.java new file mode 100644 index 0000000..8f013b6 --- /dev/null +++ b/doom/src/defines/Language_t.java @@ -0,0 +1,9 @@ +package defines; + +/** Identify language to use, software localization. */ +public enum Language_t { + english, + french, + german, + unknown +} \ No newline at end of file diff --git a/doom/src/defines/ammotype_t.java b/doom/src/defines/ammotype_t.java new file mode 100644 index 0000000..b2a98a3 --- /dev/null +++ b/doom/src/defines/ammotype_t.java @@ -0,0 +1,12 @@ +package defines; + +//Ammunition types defined. +public enum ammotype_t { + am_clip, // Pistol / chaingun ammo. + am_shell, // Shotgun / double barreled shotgun. + am_cell, // Plasma rifle, BFG. + am_misl, // Missile launcher. + NUMAMMO, + am_noammo // Unlimited for chainsaw / fist. + +} \ No newline at end of file diff --git a/doom/src/defines/card_t.java b/doom/src/defines/card_t.java new file mode 100644 index 0000000..b708945 --- /dev/null +++ b/doom/src/defines/card_t.java @@ -0,0 +1,15 @@ +package defines; + +/** + * Key cards. + */ +public enum card_t { + it_bluecard, + it_yellowcard, + it_redcard, + it_blueskull, + it_yellowskull, + it_redskull, + NUMCARDS + +} \ No newline at end of file diff --git a/doom/src/defines/gamestate_t.java b/doom/src/defines/gamestate_t.java new file mode 100644 index 0000000..55f1c19 --- /dev/null +++ b/doom/src/defines/gamestate_t.java @@ -0,0 +1,12 @@ +package defines; + +/** The current state of the game: whether we are + playing, gazing at the intermission screen, + the game final animation, or a demo. */ +public enum gamestate_t { + GS_LEVEL, + GS_INTERMISSION, + GS_FINALE, + GS_DEMOSCREEN, + GS_MINUS_ONE // hack used for the "-1" state +} \ No newline at end of file diff --git a/doom/src/defines/skill_t.java b/doom/src/defines/skill_t.java new file mode 100644 index 0000000..a40fc4d --- /dev/null +++ b/doom/src/defines/skill_t.java @@ -0,0 +1,9 @@ +package defines; + +public enum skill_t { + sk_baby, + sk_easy, + sk_medium, + sk_hard, + sk_nightmare +} \ No newline at end of file diff --git a/doom/src/defines/slopetype_t.java b/doom/src/defines/slopetype_t.java new file mode 100644 index 0000000..3542e42 --- /dev/null +++ b/doom/src/defines/slopetype_t.java @@ -0,0 +1,10 @@ +package defines; + +/** Move clipping aid for LineDefs. */ +public enum slopetype_t { + ST_HORIZONTAL, + ST_VERTICAL, + ST_POSITIVE, + ST_NEGATIVE + +} \ No newline at end of file diff --git a/doom/src/defines/statenum_t.java b/doom/src/defines/statenum_t.java new file mode 100644 index 0000000..e380875 --- /dev/null +++ b/doom/src/defines/statenum_t.java @@ -0,0 +1,972 @@ +package defines; + +public enum statenum_t { + S_NULL, + S_LIGHTDONE, + S_PUNCH, + S_PUNCHDOWN, + S_PUNCHUP, + S_PUNCH1, + S_PUNCH2, + S_PUNCH3, + S_PUNCH4, + S_PUNCH5, + S_PISTOL, + S_PISTOLDOWN, + S_PISTOLUP, + S_PISTOL1, + S_PISTOL2, + S_PISTOL3, + S_PISTOL4, + S_PISTOLFLASH, + S_SGUN, + S_SGUNDOWN, + S_SGUNUP, + S_SGUN1, + S_SGUN2, + S_SGUN3, + S_SGUN4, + S_SGUN5, + S_SGUN6, + S_SGUN7, + S_SGUN8, + S_SGUN9, + S_SGUNFLASH1, + S_SGUNFLASH2, + S_DSGUN, + S_DSGUNDOWN, + S_DSGUNUP, + S_DSGUN1, + S_DSGUN2, + S_DSGUN3, + S_DSGUN4, + S_DSGUN5, + S_DSGUN6, + S_DSGUN7, + S_DSGUN8, + S_DSGUN9, + S_DSGUN10, + S_DSNR1, + S_DSNR2, + S_DSGUNFLASH1, + S_DSGUNFLASH2, + S_CHAIN, + S_CHAINDOWN, + S_CHAINUP, + S_CHAIN1, + S_CHAIN2, + S_CHAIN3, + S_CHAINFLASH1, + S_CHAINFLASH2, + S_MISSILE, + S_MISSILEDOWN, + S_MISSILEUP, + S_MISSILE1, + S_MISSILE2, + S_MISSILE3, + S_MISSILEFLASH1, + S_MISSILEFLASH2, + S_MISSILEFLASH3, + S_MISSILEFLASH4, + S_SAW, + S_SAWB, + S_SAWDOWN, + S_SAWUP, + S_SAW1, + S_SAW2, + S_SAW3, + S_PLASMA, + S_PLASMADOWN, + S_PLASMAUP, + S_PLASMA1, + S_PLASMA2, + S_PLASMAFLASH1, + S_PLASMAFLASH2, + S_BFG, + S_BFGDOWN, + S_BFGUP, + S_BFG1, + S_BFG2, + S_BFG3, + S_BFG4, + S_BFGFLASH1, + S_BFGFLASH2, + S_BLOOD1, + S_BLOOD2, + S_BLOOD3, + S_PUFF1, + S_PUFF2, + S_PUFF3, + S_PUFF4, + S_TBALL1, + S_TBALL2, + S_TBALLX1, + S_TBALLX2, + S_TBALLX3, + S_RBALL1, + S_RBALL2, + S_RBALLX1, + S_RBALLX2, + S_RBALLX3, + S_PLASBALL, + S_PLASBALL2, + S_PLASEXP, + S_PLASEXP2, + S_PLASEXP3, + S_PLASEXP4, + S_PLASEXP5, + S_ROCKET, + S_BFGSHOT, + S_BFGSHOT2, + S_BFGLAND, + S_BFGLAND2, + S_BFGLAND3, + S_BFGLAND4, + S_BFGLAND5, + S_BFGLAND6, + S_BFGEXP, + S_BFGEXP2, + S_BFGEXP3, + S_BFGEXP4, + S_EXPLODE1, + S_EXPLODE2, + S_EXPLODE3, + S_TFOG, + S_TFOG01, + S_TFOG02, + S_TFOG2, + S_TFOG3, + S_TFOG4, + S_TFOG5, + S_TFOG6, + S_TFOG7, + S_TFOG8, + S_TFOG9, + S_TFOG10, + S_IFOG, + S_IFOG01, + S_IFOG02, + S_IFOG2, + S_IFOG3, + S_IFOG4, + S_IFOG5, + S_PLAY, + S_PLAY_RUN1, + S_PLAY_RUN2, + S_PLAY_RUN3, + S_PLAY_RUN4, + S_PLAY_ATK1, + S_PLAY_ATK2, + S_PLAY_PAIN, + S_PLAY_PAIN2, + S_PLAY_DIE1, + S_PLAY_DIE2, + S_PLAY_DIE3, + S_PLAY_DIE4, + S_PLAY_DIE5, + S_PLAY_DIE6, + S_PLAY_DIE7, + S_PLAY_XDIE1, + S_PLAY_XDIE2, + S_PLAY_XDIE3, + S_PLAY_XDIE4, + S_PLAY_XDIE5, + S_PLAY_XDIE6, + S_PLAY_XDIE7, + S_PLAY_XDIE8, + S_PLAY_XDIE9, + S_POSS_STND, + S_POSS_STND2, + S_POSS_RUN1, + S_POSS_RUN2, + S_POSS_RUN3, + S_POSS_RUN4, + S_POSS_RUN5, + S_POSS_RUN6, + S_POSS_RUN7, + S_POSS_RUN8, + S_POSS_ATK1, + S_POSS_ATK2, + S_POSS_ATK3, + S_POSS_PAIN, + S_POSS_PAIN2, + S_POSS_DIE1, + S_POSS_DIE2, + S_POSS_DIE3, + S_POSS_DIE4, + S_POSS_DIE5, + S_POSS_XDIE1, + S_POSS_XDIE2, + S_POSS_XDIE3, + S_POSS_XDIE4, + S_POSS_XDIE5, + S_POSS_XDIE6, + S_POSS_XDIE7, + S_POSS_XDIE8, + S_POSS_XDIE9, + S_POSS_RAISE1, + S_POSS_RAISE2, + S_POSS_RAISE3, + S_POSS_RAISE4, + S_SPOS_STND, + S_SPOS_STND2, + S_SPOS_RUN1, + S_SPOS_RUN2, + S_SPOS_RUN3, + S_SPOS_RUN4, + S_SPOS_RUN5, + S_SPOS_RUN6, + S_SPOS_RUN7, + S_SPOS_RUN8, + S_SPOS_ATK1, + S_SPOS_ATK2, + S_SPOS_ATK3, + S_SPOS_PAIN, + S_SPOS_PAIN2, + S_SPOS_DIE1, + S_SPOS_DIE2, + S_SPOS_DIE3, + S_SPOS_DIE4, + S_SPOS_DIE5, + S_SPOS_XDIE1, + S_SPOS_XDIE2, + S_SPOS_XDIE3, + S_SPOS_XDIE4, + S_SPOS_XDIE5, + S_SPOS_XDIE6, + S_SPOS_XDIE7, + S_SPOS_XDIE8, + S_SPOS_XDIE9, + S_SPOS_RAISE1, + S_SPOS_RAISE2, + S_SPOS_RAISE3, + S_SPOS_RAISE4, + S_SPOS_RAISE5, + S_VILE_STND, + S_VILE_STND2, + S_VILE_RUN1, + S_VILE_RUN2, + S_VILE_RUN3, + S_VILE_RUN4, + S_VILE_RUN5, + S_VILE_RUN6, + S_VILE_RUN7, + S_VILE_RUN8, + S_VILE_RUN9, + S_VILE_RUN10, + S_VILE_RUN11, + S_VILE_RUN12, + S_VILE_ATK1, + S_VILE_ATK2, + S_VILE_ATK3, + S_VILE_ATK4, + S_VILE_ATK5, + S_VILE_ATK6, + S_VILE_ATK7, + S_VILE_ATK8, + S_VILE_ATK9, + S_VILE_ATK10, + S_VILE_ATK11, + S_VILE_HEAL1, + S_VILE_HEAL2, + S_VILE_HEAL3, + S_VILE_PAIN, + S_VILE_PAIN2, + S_VILE_DIE1, + S_VILE_DIE2, + S_VILE_DIE3, + S_VILE_DIE4, + S_VILE_DIE5, + S_VILE_DIE6, + S_VILE_DIE7, + S_VILE_DIE8, + S_VILE_DIE9, + S_VILE_DIE10, + S_FIRE1, + S_FIRE2, + S_FIRE3, + S_FIRE4, + S_FIRE5, + S_FIRE6, + S_FIRE7, + S_FIRE8, + S_FIRE9, + S_FIRE10, + S_FIRE11, + S_FIRE12, + S_FIRE13, + S_FIRE14, + S_FIRE15, + S_FIRE16, + S_FIRE17, + S_FIRE18, + S_FIRE19, + S_FIRE20, + S_FIRE21, + S_FIRE22, + S_FIRE23, + S_FIRE24, + S_FIRE25, + S_FIRE26, + S_FIRE27, + S_FIRE28, + S_FIRE29, + S_FIRE30, + S_SMOKE1, + S_SMOKE2, + S_SMOKE3, + S_SMOKE4, + S_SMOKE5, + S_TRACER, + S_TRACER2, + S_TRACEEXP1, + S_TRACEEXP2, + S_TRACEEXP3, + S_SKEL_STND, + S_SKEL_STND2, + S_SKEL_RUN1, + S_SKEL_RUN2, + S_SKEL_RUN3, + S_SKEL_RUN4, + S_SKEL_RUN5, + S_SKEL_RUN6, + S_SKEL_RUN7, + S_SKEL_RUN8, + S_SKEL_RUN9, + S_SKEL_RUN10, + S_SKEL_RUN11, + S_SKEL_RUN12, + S_SKEL_FIST1, + S_SKEL_FIST2, + S_SKEL_FIST3, + S_SKEL_FIST4, + S_SKEL_MISS1, + S_SKEL_MISS2, + S_SKEL_MISS3, + S_SKEL_MISS4, + S_SKEL_PAIN, + S_SKEL_PAIN2, + S_SKEL_DIE1, + S_SKEL_DIE2, + S_SKEL_DIE3, + S_SKEL_DIE4, + S_SKEL_DIE5, + S_SKEL_DIE6, + S_SKEL_RAISE1, + S_SKEL_RAISE2, + S_SKEL_RAISE3, + S_SKEL_RAISE4, + S_SKEL_RAISE5, + S_SKEL_RAISE6, + S_FATSHOT1, + S_FATSHOT2, + S_FATSHOTX1, + S_FATSHOTX2, + S_FATSHOTX3, + S_FATT_STND, + S_FATT_STND2, + S_FATT_RUN1, + S_FATT_RUN2, + S_FATT_RUN3, + S_FATT_RUN4, + S_FATT_RUN5, + S_FATT_RUN6, + S_FATT_RUN7, + S_FATT_RUN8, + S_FATT_RUN9, + S_FATT_RUN10, + S_FATT_RUN11, + S_FATT_RUN12, + S_FATT_ATK1, + S_FATT_ATK2, + S_FATT_ATK3, + S_FATT_ATK4, + S_FATT_ATK5, + S_FATT_ATK6, + S_FATT_ATK7, + S_FATT_ATK8, + S_FATT_ATK9, + S_FATT_ATK10, + S_FATT_PAIN, + S_FATT_PAIN2, + S_FATT_DIE1, + S_FATT_DIE2, + S_FATT_DIE3, + S_FATT_DIE4, + S_FATT_DIE5, + S_FATT_DIE6, + S_FATT_DIE7, + S_FATT_DIE8, + S_FATT_DIE9, + S_FATT_DIE10, + S_FATT_RAISE1, + S_FATT_RAISE2, + S_FATT_RAISE3, + S_FATT_RAISE4, + S_FATT_RAISE5, + S_FATT_RAISE6, + S_FATT_RAISE7, + S_FATT_RAISE8, + S_CPOS_STND, + S_CPOS_STND2, + S_CPOS_RUN1, + S_CPOS_RUN2, + S_CPOS_RUN3, + S_CPOS_RUN4, + S_CPOS_RUN5, + S_CPOS_RUN6, + S_CPOS_RUN7, + S_CPOS_RUN8, + S_CPOS_ATK1, + S_CPOS_ATK2, + S_CPOS_ATK3, + S_CPOS_ATK4, + S_CPOS_PAIN, + S_CPOS_PAIN2, + S_CPOS_DIE1, + S_CPOS_DIE2, + S_CPOS_DIE3, + S_CPOS_DIE4, + S_CPOS_DIE5, + S_CPOS_DIE6, + S_CPOS_DIE7, + S_CPOS_XDIE1, + S_CPOS_XDIE2, + S_CPOS_XDIE3, + S_CPOS_XDIE4, + S_CPOS_XDIE5, + S_CPOS_XDIE6, + S_CPOS_RAISE1, + S_CPOS_RAISE2, + S_CPOS_RAISE3, + S_CPOS_RAISE4, + S_CPOS_RAISE5, + S_CPOS_RAISE6, + S_CPOS_RAISE7, + S_TROO_STND, + S_TROO_STND2, + S_TROO_RUN1, + S_TROO_RUN2, + S_TROO_RUN3, + S_TROO_RUN4, + S_TROO_RUN5, + S_TROO_RUN6, + S_TROO_RUN7, + S_TROO_RUN8, + S_TROO_ATK1, + S_TROO_ATK2, + S_TROO_ATK3, + S_TROO_PAIN, + S_TROO_PAIN2, + S_TROO_DIE1, + S_TROO_DIE2, + S_TROO_DIE3, + S_TROO_DIE4, + S_TROO_DIE5, + S_TROO_XDIE1, + S_TROO_XDIE2, + S_TROO_XDIE3, + S_TROO_XDIE4, + S_TROO_XDIE5, + S_TROO_XDIE6, + S_TROO_XDIE7, + S_TROO_XDIE8, + S_TROO_RAISE1, + S_TROO_RAISE2, + S_TROO_RAISE3, + S_TROO_RAISE4, + S_TROO_RAISE5, + S_SARG_STND, + S_SARG_STND2, + S_SARG_RUN1, + S_SARG_RUN2, + S_SARG_RUN3, + S_SARG_RUN4, + S_SARG_RUN5, + S_SARG_RUN6, + S_SARG_RUN7, + S_SARG_RUN8, + S_SARG_ATK1, + S_SARG_ATK2, + S_SARG_ATK3, + S_SARG_PAIN, + S_SARG_PAIN2, + S_SARG_DIE1, + S_SARG_DIE2, + S_SARG_DIE3, + S_SARG_DIE4, + S_SARG_DIE5, + S_SARG_DIE6, + S_SARG_RAISE1, + S_SARG_RAISE2, + S_SARG_RAISE3, + S_SARG_RAISE4, + S_SARG_RAISE5, + S_SARG_RAISE6, + S_HEAD_STND, + S_HEAD_RUN1, + S_HEAD_ATK1, + S_HEAD_ATK2, + S_HEAD_ATK3, + S_HEAD_PAIN, + S_HEAD_PAIN2, + S_HEAD_PAIN3, + S_HEAD_DIE1, + S_HEAD_DIE2, + S_HEAD_DIE3, + S_HEAD_DIE4, + S_HEAD_DIE5, + S_HEAD_DIE6, + S_HEAD_RAISE1, + S_HEAD_RAISE2, + S_HEAD_RAISE3, + S_HEAD_RAISE4, + S_HEAD_RAISE5, + S_HEAD_RAISE6, + S_BRBALL1, + S_BRBALL2, + S_BRBALLX1, + S_BRBALLX2, + S_BRBALLX3, + S_BOSS_STND, + S_BOSS_STND2, + S_BOSS_RUN1, + S_BOSS_RUN2, + S_BOSS_RUN3, + S_BOSS_RUN4, + S_BOSS_RUN5, + S_BOSS_RUN6, + S_BOSS_RUN7, + S_BOSS_RUN8, + S_BOSS_ATK1, + S_BOSS_ATK2, + S_BOSS_ATK3, + S_BOSS_PAIN, + S_BOSS_PAIN2, + S_BOSS_DIE1, + S_BOSS_DIE2, + S_BOSS_DIE3, + S_BOSS_DIE4, + S_BOSS_DIE5, + S_BOSS_DIE6, + S_BOSS_DIE7, + S_BOSS_RAISE1, + S_BOSS_RAISE2, + S_BOSS_RAISE3, + S_BOSS_RAISE4, + S_BOSS_RAISE5, + S_BOSS_RAISE6, + S_BOSS_RAISE7, + S_BOS2_STND, + S_BOS2_STND2, + S_BOS2_RUN1, + S_BOS2_RUN2, + S_BOS2_RUN3, + S_BOS2_RUN4, + S_BOS2_RUN5, + S_BOS2_RUN6, + S_BOS2_RUN7, + S_BOS2_RUN8, + S_BOS2_ATK1, + S_BOS2_ATK2, + S_BOS2_ATK3, + S_BOS2_PAIN, + S_BOS2_PAIN2, + S_BOS2_DIE1, + S_BOS2_DIE2, + S_BOS2_DIE3, + S_BOS2_DIE4, + S_BOS2_DIE5, + S_BOS2_DIE6, + S_BOS2_DIE7, + S_BOS2_RAISE1, + S_BOS2_RAISE2, + S_BOS2_RAISE3, + S_BOS2_RAISE4, + S_BOS2_RAISE5, + S_BOS2_RAISE6, + S_BOS2_RAISE7, + S_SKULL_STND, + S_SKULL_STND2, + S_SKULL_RUN1, + S_SKULL_RUN2, + S_SKULL_ATK1, + S_SKULL_ATK2, + S_SKULL_ATK3, + S_SKULL_ATK4, + S_SKULL_PAIN, + S_SKULL_PAIN2, + S_SKULL_DIE1, + S_SKULL_DIE2, + S_SKULL_DIE3, + S_SKULL_DIE4, + S_SKULL_DIE5, + S_SKULL_DIE6, + S_SPID_STND, + S_SPID_STND2, + S_SPID_RUN1, + S_SPID_RUN2, + S_SPID_RUN3, + S_SPID_RUN4, + S_SPID_RUN5, + S_SPID_RUN6, + S_SPID_RUN7, + S_SPID_RUN8, + S_SPID_RUN9, + S_SPID_RUN10, + S_SPID_RUN11, + S_SPID_RUN12, + S_SPID_ATK1, + S_SPID_ATK2, + S_SPID_ATK3, + S_SPID_ATK4, + S_SPID_PAIN, + S_SPID_PAIN2, + S_SPID_DIE1, + S_SPID_DIE2, + S_SPID_DIE3, + S_SPID_DIE4, + S_SPID_DIE5, + S_SPID_DIE6, + S_SPID_DIE7, + S_SPID_DIE8, + S_SPID_DIE9, + S_SPID_DIE10, + S_SPID_DIE11, + S_BSPI_STND, + S_BSPI_STND2, + S_BSPI_SIGHT, + S_BSPI_RUN1, + S_BSPI_RUN2, + S_BSPI_RUN3, + S_BSPI_RUN4, + S_BSPI_RUN5, + S_BSPI_RUN6, + S_BSPI_RUN7, + S_BSPI_RUN8, + S_BSPI_RUN9, + S_BSPI_RUN10, + S_BSPI_RUN11, + S_BSPI_RUN12, + S_BSPI_ATK1, + S_BSPI_ATK2, + S_BSPI_ATK3, + S_BSPI_ATK4, + S_BSPI_PAIN, + S_BSPI_PAIN2, + S_BSPI_DIE1, + S_BSPI_DIE2, + S_BSPI_DIE3, + S_BSPI_DIE4, + S_BSPI_DIE5, + S_BSPI_DIE6, + S_BSPI_DIE7, + S_BSPI_RAISE1, + S_BSPI_RAISE2, + S_BSPI_RAISE3, + S_BSPI_RAISE4, + S_BSPI_RAISE5, + S_BSPI_RAISE6, + S_BSPI_RAISE7, + S_ARACH_PLAZ, + S_ARACH_PLAZ2, + S_ARACH_PLEX, + S_ARACH_PLEX2, + S_ARACH_PLEX3, + S_ARACH_PLEX4, + S_ARACH_PLEX5, + S_CYBER_STND, + S_CYBER_STND2, + S_CYBER_RUN1, + S_CYBER_RUN2, + S_CYBER_RUN3, + S_CYBER_RUN4, + S_CYBER_RUN5, + S_CYBER_RUN6, + S_CYBER_RUN7, + S_CYBER_RUN8, + S_CYBER_ATK1, + S_CYBER_ATK2, + S_CYBER_ATK3, + S_CYBER_ATK4, + S_CYBER_ATK5, + S_CYBER_ATK6, + S_CYBER_PAIN, + S_CYBER_DIE1, + S_CYBER_DIE2, + S_CYBER_DIE3, + S_CYBER_DIE4, + S_CYBER_DIE5, + S_CYBER_DIE6, + S_CYBER_DIE7, + S_CYBER_DIE8, + S_CYBER_DIE9, + S_CYBER_DIE10, + S_PAIN_STND, + S_PAIN_RUN1, + S_PAIN_RUN2, + S_PAIN_RUN3, + S_PAIN_RUN4, + S_PAIN_RUN5, + S_PAIN_RUN6, + S_PAIN_ATK1, + S_PAIN_ATK2, + S_PAIN_ATK3, + S_PAIN_ATK4, + S_PAIN_PAIN, + S_PAIN_PAIN2, + S_PAIN_DIE1, + S_PAIN_DIE2, + S_PAIN_DIE3, + S_PAIN_DIE4, + S_PAIN_DIE5, + S_PAIN_DIE6, + S_PAIN_RAISE1, + S_PAIN_RAISE2, + S_PAIN_RAISE3, + S_PAIN_RAISE4, + S_PAIN_RAISE5, + S_PAIN_RAISE6, + S_SSWV_STND, + S_SSWV_STND2, + S_SSWV_RUN1, + S_SSWV_RUN2, + S_SSWV_RUN3, + S_SSWV_RUN4, + S_SSWV_RUN5, + S_SSWV_RUN6, + S_SSWV_RUN7, + S_SSWV_RUN8, + S_SSWV_ATK1, + S_SSWV_ATK2, + S_SSWV_ATK3, + S_SSWV_ATK4, + S_SSWV_ATK5, + S_SSWV_ATK6, + S_SSWV_PAIN, + S_SSWV_PAIN2, + S_SSWV_DIE1, + S_SSWV_DIE2, + S_SSWV_DIE3, + S_SSWV_DIE4, + S_SSWV_DIE5, + S_SSWV_XDIE1, + S_SSWV_XDIE2, + S_SSWV_XDIE3, + S_SSWV_XDIE4, + S_SSWV_XDIE5, + S_SSWV_XDIE6, + S_SSWV_XDIE7, + S_SSWV_XDIE8, + S_SSWV_XDIE9, + S_SSWV_RAISE1, + S_SSWV_RAISE2, + S_SSWV_RAISE3, + S_SSWV_RAISE4, + S_SSWV_RAISE5, + S_KEENSTND, + S_COMMKEEN, + S_COMMKEEN2, + S_COMMKEEN3, + S_COMMKEEN4, + S_COMMKEEN5, + S_COMMKEEN6, + S_COMMKEEN7, + S_COMMKEEN8, + S_COMMKEEN9, + S_COMMKEEN10, + S_COMMKEEN11, + S_COMMKEEN12, + S_KEENPAIN, + S_KEENPAIN2, + S_BRAIN, + S_BRAIN_PAIN, + S_BRAIN_DIE1, + S_BRAIN_DIE2, + S_BRAIN_DIE3, + S_BRAIN_DIE4, + S_BRAINEYE, + S_BRAINEYESEE, + S_BRAINEYE1, + S_SPAWN1, + S_SPAWN2, + S_SPAWN3, + S_SPAWN4, + S_SPAWNFIRE1, + S_SPAWNFIRE2, + S_SPAWNFIRE3, + S_SPAWNFIRE4, + S_SPAWNFIRE5, + S_SPAWNFIRE6, + S_SPAWNFIRE7, + S_SPAWNFIRE8, + S_BRAINEXPLODE1, + S_BRAINEXPLODE2, + S_BRAINEXPLODE3, + S_ARM1, + S_ARM1A, + S_ARM2, + S_ARM2A, + S_BAR1, + S_BAR2, + S_BEXP, + S_BEXP2, + S_BEXP3, + S_BEXP4, + S_BEXP5, + S_BBAR1, + S_BBAR2, + S_BBAR3, + S_BON1, + S_BON1A, + S_BON1B, + S_BON1C, + S_BON1D, + S_BON1E, + S_BON2, + S_BON2A, + S_BON2B, + S_BON2C, + S_BON2D, + S_BON2E, + S_BKEY, + S_BKEY2, + S_RKEY, + S_RKEY2, + S_YKEY, + S_YKEY2, + S_BSKULL, + S_BSKULL2, + S_RSKULL, + S_RSKULL2, + S_YSKULL, + S_YSKULL2, + S_STIM, + S_MEDI, + S_SOUL, + S_SOUL2, + S_SOUL3, + S_SOUL4, + S_SOUL5, + S_SOUL6, + S_PINV, + S_PINV2, + S_PINV3, + S_PINV4, + S_PSTR, + S_PINS, + S_PINS2, + S_PINS3, + S_PINS4, + S_MEGA, + S_MEGA2, + S_MEGA3, + S_MEGA4, + S_SUIT, + S_PMAP, + S_PMAP2, + S_PMAP3, + S_PMAP4, + S_PMAP5, + S_PMAP6, + S_PVIS, + S_PVIS2, + S_CLIP, + S_AMMO, + S_ROCK, + S_BROK, + S_CELL, + S_CELP, + S_SHEL, + S_SBOX, + S_BPAK, + S_BFUG, + S_MGUN, + S_CSAW, + S_LAUN, + S_PLAS, + S_SHOT, + S_SHOT2, + S_COLU, + S_STALAG, + S_BLOODYTWITCH, + S_BLOODYTWITCH2, + S_BLOODYTWITCH3, + S_BLOODYTWITCH4, + S_DEADTORSO, + S_DEADBOTTOM, + S_HEADSONSTICK, + S_GIBS, + S_HEADONASTICK, + S_HEADCANDLES, + S_HEADCANDLES2, + S_DEADSTICK, + S_LIVESTICK, + S_LIVESTICK2, + S_MEAT2, + S_MEAT3, + S_MEAT4, + S_MEAT5, + S_STALAGTITE, + S_TALLGRNCOL, + S_SHRTGRNCOL, + S_TALLREDCOL, + S_SHRTREDCOL, + S_CANDLESTIK, + S_CANDELABRA, + S_SKULLCOL, + S_TORCHTREE, + S_BIGTREE, + S_TECHPILLAR, + S_EVILEYE, + S_EVILEYE2, + S_EVILEYE3, + S_EVILEYE4, + S_FLOATSKULL, + S_FLOATSKULL2, + S_FLOATSKULL3, + S_HEARTCOL, + S_HEARTCOL2, + S_BLUETORCH, + S_BLUETORCH2, + S_BLUETORCH3, + S_BLUETORCH4, + S_GREENTORCH, + S_GREENTORCH2, + S_GREENTORCH3, + S_GREENTORCH4, + S_REDTORCH, + S_REDTORCH2, + S_REDTORCH3, + S_REDTORCH4, + S_BTORCHSHRT, + S_BTORCHSHRT2, + S_BTORCHSHRT3, + S_BTORCHSHRT4, + S_GTORCHSHRT, + S_GTORCHSHRT2, + S_GTORCHSHRT3, + S_GTORCHSHRT4, + S_RTORCHSHRT, + S_RTORCHSHRT2, + S_RTORCHSHRT3, + S_RTORCHSHRT4, + S_HANGNOGUTS, + S_HANGBNOBRAIN, + S_HANGTLOOKDN, + S_HANGTSKULL, + S_HANGTLOOKUP, + S_HANGTNOBRAIN, + S_COLONGIBS, + S_SMALLPOOL, + S_BRAINSTEM, + S_TECHLAMP, + S_TECHLAMP2, + S_TECHLAMP3, + S_TECHLAMP4, + S_TECH2LAMP, + S_TECH2LAMP2, + S_TECH2LAMP3, + S_TECH2LAMP4, + NUMSTATES +} \ No newline at end of file diff --git a/doom/src/demo/IDemoTicCmd.java b/doom/src/demo/IDemoTicCmd.java new file mode 100644 index 0000000..4369678 --- /dev/null +++ b/doom/src/demo/IDemoTicCmd.java @@ -0,0 +1,28 @@ +package demo; + +import doom.ticcmd_t; +import w.IWritableDoomObject; + +/** Demo Tic Commands can be read/written to disk/buffers, + * and are not necessarily equal to the in-game ticcmd_t. + * Thus, it's necessary for them to implement some + * adaptor method (both ways). + * + * @author admin + * + */ +public interface IDemoTicCmd extends IWritableDoomObject { + + /** Decode this IDemoTicCmd into a standard ticcmd_t. + * + * @param source + */ + public void decode(ticcmd_t dest); + + /** Encode this IDemoTicCmd from a standard ticcmd_t. + * + * @param dest + */ + public void encode(ticcmd_t source); + +} \ No newline at end of file diff --git a/doom/src/demo/IDoomDemo.java b/doom/src/demo/IDoomDemo.java new file mode 100644 index 0000000..033ba1a --- /dev/null +++ b/doom/src/demo/IDoomDemo.java @@ -0,0 +1,68 @@ +package demo; + +import defines.skill_t; +import w.IWritableDoomObject; + +public interface IDoomDemo extends IWritableDoomObject { + + /** Vanilla end demo marker, to append at the end of recorded demos */ + public static final int DEMOMARKER = 0x80; + + /** Get next demo command, in its raw format. Use + * its own adapters if you need it converted to a + * standard ticcmd_t. + * + * @return + */ + IDemoTicCmd getNextTic(); + + /** Record a demo command in the IDoomDemo's native format. + * Use the IDemoTicCmd's objects adaptors to convert it. + * + * @param tic + */ + void putTic(IDemoTicCmd tic); + + int getVersion(); + + void setVersion(int version); + + skill_t getSkill(); + + void setSkill(skill_t skill); + + int getEpisode(); + + void setEpisode(int episode); + + int getMap(); + + void setMap(int map); + + boolean isDeathmatch(); + + void setDeathmatch(boolean deathmatch); + + boolean isRespawnparm(); + + void setRespawnparm(boolean respawnparm); + + boolean isFastparm(); + + void setFastparm(boolean fastparm); + + boolean isNomonsters(); + + void setNomonsters(boolean nomonsters); + + int getConsoleplayer(); + + void setConsoleplayer(int consoleplayer); + + boolean[] getPlayeringame(); + + void setPlayeringame(boolean[] playeringame); + + void resetDemo(); + +} \ No newline at end of file diff --git a/doom/src/demo/VanillaDoomDemo.java b/doom/src/demo/VanillaDoomDemo.java new file mode 100644 index 0000000..747c617 --- /dev/null +++ b/doom/src/demo/VanillaDoomDemo.java @@ -0,0 +1,238 @@ +package demo; + +import static data.Limits.MAXPLAYERS; +import defines.skill_t; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import static utils.GenericCopy.malloc; +import w.CacheableDoomObject; +import w.DoomBuffer; +import w.DoomIO; + +public class VanillaDoomDemo implements IDoomDemo, CacheableDoomObject { + + // This stuff is in the demo header, in the order it appears + // However everything is byte-sized when read from disk or to memory. + public int version; + public skill_t skill; + public int episode; + public int map; + public boolean deathmatch; + public boolean respawnparm; + public boolean fastparm; + public boolean nomonsters; + public int consoleplayer; + public boolean[] playeringame; // normally MAXPLAYERS (4) for vanilla. + + protected int p_demo; + + // After that, demos contain a sequence of ticcmd_t's to build dynamically at + // load time or when recording. This abstraction allows arbitrary demo sizes + // and easy per-step handling, and even changes/extensions. Just make sure + // that ticcmd_t's are serializable! + // Also, the format used in demo lumps is NOT the same as in datagrams/network + // (e.g. there is no consistency) and their handling is modified. + VanillaTiccmd[] commands; + List demorecorder; + + public VanillaDoomDemo() { + this.demorecorder = new ArrayList<>(); + } + + @Override + public void unpack(ByteBuffer b) { + // Just the Header info for vanilla should be 13 bytes. + // 1 byte at the end is the end-demo marker + // So valid vanilla demos should have sizes that + // fit the formula 14+4n, since each vanilla + // demo ticcmd_t is 4 bytes. + int lens = (b.limit() - 13) / 4; + boolean vanilla = (b.limit() == (14 + 4 * lens)); + + // Minimum valid vanilla demo should be 14 bytes...in theory. + if (b.limit() < 14) { + // Use skill==null as an indicator that loading didn't go well. + skill = null; + return; + } + + version = b.get(); + + try { + skill = skill_t.values()[b.get()]; + } catch (Exception e) { + skill = null; + } + + episode = b.get(); + map = b.get(); + deathmatch = b.get() != 0; + respawnparm = b.get() != 0; + fastparm = b.get() != 0; + nomonsters = b.get() != 0; + consoleplayer = b.get(); + + playeringame = new boolean[MAXPLAYERS]; + + for (int i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = b.get() != 0; + } + + this.commands = malloc(VanillaTiccmd::new, VanillaTiccmd[]::new, lens); + + try { + DoomBuffer.readObjectArray(b, this.commands, lens); + } catch (IOException e) { + skill = null; + } + } + + @Override + public IDemoTicCmd getNextTic() { + if ((commands != null) && (p_demo < commands.length)) { + + return commands[p_demo++]; + } else { + return null; + } + } + + @Override + public void putTic(IDemoTicCmd tic) { + demorecorder.add(tic); + + } + + @Override + public int getVersion() { + return version; + } + + @Override + public void setVersion(int version) { + this.version = version; + } + + @Override + public skill_t getSkill() { + return skill; + } + + @Override + public void setSkill(skill_t skill) { + this.skill = skill; + } + + @Override + public int getEpisode() { + return episode; + } + + @Override + public void setEpisode(int episode) { + this.episode = episode; + } + + @Override + public int getMap() { + return map; + } + + @Override + public void setMap(int map) { + this.map = map; + } + + @Override + public boolean isDeathmatch() { + return deathmatch; + } + + @Override + public void setDeathmatch(boolean deathmatch) { + this.deathmatch = deathmatch; + } + + @Override + public boolean isRespawnparm() { + return respawnparm; + } + + @Override + public void setRespawnparm(boolean respawnparm) { + this.respawnparm = respawnparm; + } + + @Override + public boolean isFastparm() { + return fastparm; + } + + @Override + public void setFastparm(boolean fastparm) { + this.fastparm = fastparm; + } + + @Override + public boolean isNomonsters() { + return nomonsters; + } + + @Override + public void setNomonsters(boolean nomonsters) { + this.nomonsters = nomonsters; + } + + @Override + public int getConsoleplayer() { + return consoleplayer; + } + + @Override + public void setConsoleplayer(int consoleplayer) { + this.consoleplayer = consoleplayer; + } + + @Override + public boolean[] getPlayeringame() { + return playeringame; + } + + @Override + public void setPlayeringame(boolean[] playeringame) { + this.playeringame = playeringame; + } + + @Override + public void write(DataOutputStream f) + throws IOException { + + f.writeByte(version); + f.writeByte(skill.ordinal()); + f.writeByte(episode); + f.writeByte(map); + f.writeBoolean(deathmatch); + f.writeBoolean(respawnparm); + f.writeBoolean(fastparm); + f.writeBoolean(nomonsters); + f.writeByte(consoleplayer); + DoomIO.writeBoolean(f, this.playeringame, MAXPLAYERS); + for (IDemoTicCmd i : demorecorder) { + i.write(f); + } + f.writeByte(DEMOMARKER); + + // TODO Auto-generated method stub + } + + @Override + public void resetDemo() { + this.p_demo = 0; + + } + + /////////////////////// VARIOUS BORING GETTERS ///////////////////// +} \ No newline at end of file diff --git a/doom/src/demo/VanillaTiccmd.java b/doom/src/demo/VanillaTiccmd.java new file mode 100644 index 0000000..151dba1 --- /dev/null +++ b/doom/src/demo/VanillaTiccmd.java @@ -0,0 +1,116 @@ +package demo; + +import doom.ticcmd_t; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import utils.C2JUtils; +import w.CacheableDoomObject; +import w.IWritableDoomObject; + +/** A more lightweight version of ticcmd_t, which contains + * too much crap and redundant data. In order to keep demo + * loading and recording easier, this class contains only the + * necessary stuff to read/write from/to disk during VANILLA + * demos. It can be converted from/to ticcmd_t, if needed. + * + * @author admin + * + */ +public class VanillaTiccmd implements CacheableDoomObject, IDemoTicCmd, IWritableDoomObject { + + /** *2048 for move */ + public byte forwardmove; + + /** *2048 for move */ + public byte sidemove; + + /** <<16 for angle delta */ + public byte angleturn; + public byte buttons; + + /** Special note: the only occasion where we'd ever be interested + * in reading ticcmd_t's from a lump is when playing back demos. + * Therefore, we use this specialized reading method which does NOT, + * I repeat, DOES NOT set all fields and some are read differently. + * NOT 1:1 intercangeable with the Datagram methods! + * + */ + @Override + public void unpack(ByteBuffer f) + throws IOException { + + // MAES: the original ID code for reference. + // demo_p++ is a pointer inside a raw byte buffer. + //cmd->forwardmove = ((signed char)*demo_p++); + //cmd->sidemove = ((signed char)*demo_p++); + //cmd->angleturn = ((unsigned char)*demo_p++)<<8; + //cmd->buttons = (unsigned char)*demo_p++; + forwardmove = f.get(); + sidemove = f.get(); + // Even if they use the "unsigned char" syntax, angleturn is signed. + angleturn = f.get(); + buttons = f.get(); + + } + + /** Ditto, we only pack some of the fields. + * + * @param f + * @throws IOException + */ + public void pack(ByteBuffer f) + throws IOException { + + f.put(forwardmove); + f.put(sidemove); + f.put(angleturn); + f.put(buttons); + } + + private static StringBuilder sb = new StringBuilder(); + + public String toString() { + sb.setLength(0); + sb.append(" forwardmove "); + sb.append(this.forwardmove); + sb.append(" sidemove "); + sb.append(this.sidemove); + sb.append(" angleturn "); + sb.append(this.angleturn); + sb.append(" buttons "); + sb.append(Integer.toHexString(this.buttons)); + return sb.toString(); + } + + @Override + public void decode(ticcmd_t dest) { + // Decode + dest.forwardmove = this.forwardmove; + dest.sidemove = this.sidemove; + dest.angleturn = (short) (this.angleturn << 8); + dest.buttons = (char) (C2JUtils.toUnsignedByte(this.buttons)); + } + + @Override + public void encode(ticcmd_t source) { + // Note: we can get away with a simple copy because + // Demoticcmds have already been "decoded". + this.forwardmove = source.forwardmove; + this.sidemove = source.sidemove; + // OUCH!!! NASTY PRECISION REDUCTION... but it's the + // One True Vanilla way. + this.angleturn = (byte) (source.angleturn >>> 8); + this.buttons = (byte) (source.buttons & 0x00FF); + } + + @Override + public void write(DataOutputStream f) + throws IOException { + f.writeByte(forwardmove); + f.writeByte(sidemove); + f.writeByte(angleturn); + f.writeByte(buttons); + } + +} \ No newline at end of file diff --git a/doom/src/doom/CVarManager.java b/doom/src/doom/CVarManager.java new file mode 100644 index 0000000..059db0a --- /dev/null +++ b/doom/src/doom/CVarManager.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import utils.ResourceIO; + +/** + * New, object-oriented Console Variable Manager + * Usage: + * 1. Define CVars in CommandVariable Enum + * 2. In program entry main function, create any ICommandLineManager and pass an instance to create CVarManager + * 3. Use methods bool, present, get and with to check or get CVars + * + * @author Good Sign + */ +public class CVarManager { + + private static final Logger LOGGER = Loggers.getLogger(CVarManager.class.getName()); + + private final EnumMap cVarMap = new EnumMap<>(CommandVariable.class); + + public CVarManager(final List commandList) { + LOGGER.log(Level.INFO, + String.format("%d command-line variable(s).", processAllArgs(commandList))); + } + + /** + * Checks that CVar of switch-type is passed as Command Line Argument + * @param cv + * @return boolean + */ + public boolean bool(final CommandVariable cv) { + return cv.getType() == CommandVariable.Type.SWITCH && cVarMap.containsKey(cv); + } + + /** + * Checks that CVar of any type is passed as Command Line Argument with proper value(s) + * @param cv + * @return boolean + */ + public boolean present(final CommandVariable cv) { + return cVarMap.get(cv) != null; + } + + /** + * Checks that CVar of any type is passed as Command Line Argument + * @param cv + * @return boolean + */ + public boolean specified(final CommandVariable cv) { + return cVarMap.containsKey(cv); + } + + /** + * Gets an Optional with or without a value of CVar argument at position + * @param cv + * @return Optional + */ + public Optional get(final CommandVariable cv, final Class itemType, final int position) { + if (cv.arguments[position] == itemType) { + if (!cVarMap.containsKey(cv)) { + return Optional.empty(); + } + + @SuppressWarnings("unchecked") + final T ret = (T) cVarMap.get(cv)[position]; + return Optional.ofNullable(ret); + } + + throw new IllegalArgumentException("CVar argument at position " + position + " is not of class " + itemType.getName()); + } + + /** + * Tries to apply a CVar argument at position to the consuming function + * The magic is that you declare a lambda function or reference some method + * and the type of object will be automatically picked from what you hinted + * + * i.e. (String s) -> System.out.println(s) will try to get string, + * (Object o) -> map.put(key, o) or o -> list.add(o.hashCode()) will try to get objects + * and you dont have to specify class + * + * The drawback is the ClassCastException will be thrown if the value is neither + * what you expected, nor a subclass of it + * + * @param cv + * @param position + * @param action + * @return false if CVar is not passed as Command Line Argument or the consuming action is incompatible + */ + public boolean with(final CommandVariable cv, final int position, final Consumer action) { + try { + @SuppressWarnings("unchecked") + final Object[] mapped = cVarMap.get(cv); + if (mapped == null) { + return false; + } + + @SuppressWarnings("unchecked") + final T item = (T) mapped[position]; + action.accept(item); + return true; + } catch (ClassCastException ex) { + return false; + } + } + + /** + * Tries to replace the CVar argument if already present or add it along with CVar + * @param cv + * @param value + * @param position + * @return false if invalid position or value class + */ + public boolean override(final CommandVariable cv, final T value, final int position) { + if (position < 0 || position >= cv.arguments.length) { + return false; + } + + if (!cv.arguments[position].isInstance(value)) { + return false; + } + + cVarMap.compute(cv, (key, array) -> { + if (array == null) { + array = new Object[cv.arguments.length]; + } + + array[position] = value; + return array; + }); + + return true; + } + + private void readResponseFile(final String filename) { + final ResponseReader r = new ResponseReader(); + if (new ResourceIO(filename).readLines(r)) { + LOGGER.log(Level.INFO, String.format("Found response file %s, read %d command line variables", filename, r.cVarCount)); + } else { + LOGGER.log(Level.WARNING, String.format("No such response file %s!", filename)); + System.exit(1); + } + } + + private int processAllArgs(final List commandList) { + int cVarCount = 0, position = 0; + + for (final int limit = commandList.size(); + limit > position; + position = processCVar(commandList, position), ++position, ++cVarCount) { + } + + return cVarCount; + } + + private int processCVar(final List commandList, int position) { + final String arg = commandList.get(position); + + if (!isCommandArgument(arg)) { + return position; + } + + final char cVarPrefix = arg.charAt(0); + final String cVarName = arg.substring(1); + + if (cVarPrefix == '@') { + readResponseFile(cVarName); + return position; + } else try { + final CommandVariable cVar = CommandVariable.valueOf(cVarName.toUpperCase()); + if (cVar.prefix == cVarPrefix) { + switch (cVar.getType()) { + case PARAMETER: + cVarMap.put(cVar, null); + case VARARG: + return processCVarSubArgs(commandList, position, cVar); + case SWITCH: + default: + cVarMap.put(cVar, null); + return position; + } + } + } catch (IllegalArgumentException ex) { + } // ignore + return position; + } + + private int processCVarSubArgs(final List commandList, int position, final CommandVariable cVar) { + final Object[] cVarMappings = new Object[cVar.arguments.length]; + for (int j = 0; j < cVar.arguments.length; ++j) { + if (cVar.arguments[j].isArray()) { + final Class elementClass = cVar.arguments[j].getComponentType(); + final Object[] mapping = processVarArg(elementClass, commandList, position + 1); + cVarMappings[j] = mapping; + position += mapping.length; + if (mapping.length == 0) { + break; + } + } else if ((cVarMappings[j] = processValue(cVar.arguments[j], commandList, position + 1)) == null) { + break; + } else { + ++position; + } + } + cVarMap.put(cVar, cVarMappings); + return position; + } + + private Object processValue(final Class elementClass, final List commandList, int position) { + if (position < commandList.size()) { + final String arg = commandList.get(position); + if (!isCommandArgument(arg)) { + return formatArgValue(elementClass, arg); + } + } + return null; + } + + private Object[] processVarArg(final Class elementClass, final List commandList, int position) { + final List list = new ArrayList<>(); + for (Object value; (value = processValue(elementClass, commandList, position)) != null; ++position) { + list.add(value); + } + // as String[] instanceof Object[], upcast + return list.toArray((Object[]) Array.newInstance(elementClass, list.size())); + } + + private Object formatArgValue(final Class format, final String arg) { + if (format == Integer.class) { + try { + return Integer.parseInt(arg); + } catch (NumberFormatException ex) { + LOGGER.log(Level.WARNING, "formatArgValue failure", ex); + return null; + } + } else if (format == String.class) { + return arg; + } + try { + return format.getDeclaredConstructor(String.class).newInstance(arg); + } catch (NoSuchMethodException + | SecurityException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException ex) { + LOGGER.log(Level.SEVERE, "formatArgValue failure", ex); + return null; + } + } + + private boolean isCommandArgument(final String arg) { + if (arg.length() < CommandVariable.MIN_CVAR_LENGTH) { + return false; + } + + switch (arg.charAt(0)) { + case '-': + case '+': + case '@': + return true; + } + + return false; + } + + private class ResponseReader implements Consumer { + + int cVarCount = 0; + + @Override + public void accept(final String line) { + cVarCount += processAllArgs(Arrays.asList(line.split(" "))); + } + } +} \ No newline at end of file diff --git a/doom/src/doom/CommandVariable.java b/doom/src/doom/CommandVariable.java new file mode 100644 index 0000000..f88dba0 --- /dev/null +++ b/doom/src/doom/CommandVariable.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +/** + * A new way to define Command Line Arguments for the Engine + * + * @author Good Sign + */ +public enum CommandVariable { + DISP(String.class), GEOM(String[].class), CONFIG(String[].class), TRANMAP(String.class), + PLAYDEMO(String.class), FASTDEMO(String.class), TIMEDEMO(String.class), RECORD(String.class), STATCOPY(String.class), + TURBO(Integer.class), SKILL(Integer.class), EPISODE(Integer.class), TIMER(Integer.class), PORT(Integer.class), + MULTIPLY(Integer.class), WIDTH(Integer.class), HEIGHT(Integer.class), + PARALLELRENDERER(Integer.class, Integer.class, Integer.class), + PARALLELRENDERER2(Integer.class, Integer.class, Integer.class), + LOADGAME(Character.class), DUP(Character.class), + NET(Character.class, String[].class), + WART(Integer.class, Integer.class), + WARP(WarpFormat.class), + MAP('+', MapFormat.class), + FILE(String[].class), + IWAD(String.class), + NOVERT(ForbidFormat.class), + NOVOLATILEIMAGE(ForbidFormat.class), + AWTFRAME, + DEBUGFILE, + SHDEV, + REGDEV, + FRDMDEV, + FR1DEV, + FR2DEV, + COMDEV, + NOMONSTERS, + RESPAWN, + FAST, + DEVPARM, + ALTDEATH, + DEATHMATCH, + MILLIS, + FASTTIC, + CDROM, + AVG, + NODRAW, + NOBLIT, + NOPLAYPAL, + NOCOLORMAP, + SERIALRENDERER, + EXTRATIC, + NOMUSIC, + NOSOUND, + NOSFX, + AUDIOLINES, + SPEAKERSOUND, + CLIPSOUND, + CLASSICSOUND, + INDEXED, + HICOLOR, + TRUECOLOR, + ALPHATRUECOLOR, + BLOCKMAP, + SHOWFPS, + JAVARANDOM, + GREYPAL; + + public final char prefix; + public final Class[] arguments; + public final static int MIN_CVAR_LENGTH = 4; + + CommandVariable(final char prefix, final Class... arguments) { + this.prefix = prefix; + this.arguments = arguments; + } + + CommandVariable(final Class... arguments) { + this('-', arguments); + } + + public Type getType() { + return arguments.length > 0 + ? (arguments[arguments.length - 1].isArray() + ? Type.VARARG + : Type.PARAMETER) + : Type.SWITCH; + } + + public enum Type { + PARAMETER, VARARG, SWITCH; + } + + public interface WarpMetric { + + int getEpisode(); + + int getMap(); + } + + public static class ForbidFormat { + + public static ForbidFormat FORBID = new ForbidFormat("disable"); + public static ForbidFormat ALLOW = new ForbidFormat(null); + private final boolean isForbidden; + + public ForbidFormat(final String forbidString) { + this.isForbidden = "disable".equals(forbidString); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + (this.isForbidden ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ForbidFormat other = (ForbidFormat) obj; + return this.isForbidden == other.isForbidden; + } + } + + public static class WarpFormat { + + final int warpInt; + + public WarpFormat(final int warpInt) { + this.warpInt = warpInt; + } + + public WarpFormat(final String warpString) { + int tryParse; + try { + tryParse = Integer.parseInt(warpString); + } catch (NumberFormatException e) { + // swallow exception. No warp. + tryParse = 0; + } + this.warpInt = tryParse; + } + + public WarpMetric getMetric(final boolean commercial) { + return new Metric(commercial); + } + + private class Metric implements WarpMetric { + + final int episode; + final int map; + + Metric(final boolean commercial) { + if (commercial) { + episode = 1; + map = WarpFormat.this.warpInt; + } else { + final int evalInt = WarpFormat.this.warpInt > 99 + ? WarpFormat.this.warpInt % 100 + : WarpFormat.this.warpInt; + + episode = evalInt / 10; + map = evalInt % 10; + } + } + + @Override + public int getEpisode() { + return episode; + } + + @Override + public int getMap() { + return map; + } + } + } + + public static class MapFormat { + + final String mapString; + + public MapFormat(final String mapString) { + this.mapString = mapString.toLowerCase(); + } + + protected int parseAsMapXX() { + if (mapString.length() != 5 || mapString.lastIndexOf("map") != 0) { + return -1; // Meh. + } + + final int map; + try { + map = Integer.parseInt(mapString.substring(3)); + } catch (NumberFormatException e) { + return -1; // eww + } + + return map; + } + + protected int parseAsExMx() { + if (mapString.length() != 4 || mapString.charAt(0) != 'e' || mapString.charAt(2) != 'm') { + return -1; // Nah. + } + + final char episode = mapString.charAt(1); + final char mission = mapString.charAt(3); + + if (episode < '0' || episode > '9' || mission < '0' || mission > '9') { + return -1; + } + + return (episode - '0') * 10 + (mission - '0'); + } + + public WarpMetric getMetric(final boolean commercial) { + final int parse = commercial + ? parseAsMapXX() + : parseAsExMx(); + + return new WarpFormat(Math.max(parse, 0)).getMetric(commercial); + } + } +} \ No newline at end of file diff --git a/doom/src/doom/ConfigBase.java b/doom/src/doom/ConfigBase.java new file mode 100644 index 0000000..baddbbf --- /dev/null +++ b/doom/src/doom/ConfigBase.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +import data.dstrings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import m.Settings; +import mochadoom.Engine; +import utils.OSValidator; +import utils.ResourceIO; + +/** + * Manages loading different config files from different places + * (the part about different places is still unfinished) + * + * @author Good Sign + */ +public enum ConfigBase { + WINDOWS("default.cfg", "USERPROFILE"), + UNIX(".doomrc", "HOME"); + + /** + * Early detection of the system and setting this is important to define global config Files + */ + public static final ConfigBase CURRENT = OSValidator.isMac() || OSValidator.isUnix() ? UNIX : WINDOWS; + + /** + * Reference these in Settings.java to set which file they will go on by default + */ + public static final Files FILE_DOOM = new Files(CURRENT.defaultConfigName, Enum::compareTo), + FILE_MOCHADOOM = new Files("mochadoom.cfg"); + + public final String defaultConfigName; + public final String env; + + ConfigBase(final String fileName, final String env) { + this.defaultConfigName = fileName; + this.env = env; + } + + public static class Files { + + private static String folder; + + public final Comparator comparator; + public final String fileName; + // flags that configuration is provided by the -config argument + public final boolean alternate; + + public boolean changed = true; + private String[] paths; + + public Files(String fileName) { + this(fileName, false); + } + + public Files(String fileName, boolean alternate) { + this(fileName, Comparator.comparing(Enum::name, String::compareTo), alternate); + } + + public Files(String fileName, Comparator comparator) { + this(fileName, comparator, false); + } + + public Files(String fileName, Comparator comparator, boolean alternate) { + this.fileName = fileName; + this.comparator = comparator; + this.alternate = alternate; + } + + public Optional firstValidPathIO() { + return Arrays.stream(getPaths()) + .map(ResourceIO::new) + .filter(ResourceIO::exists) + .findFirst(); + } + + public ResourceIO workDirIO() { + return new ResourceIO(getFolder() + fileName); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + Objects.hashCode(this.fileName); + hash = 53 * hash + (this.alternate ? 1 : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Files other = (Files) obj; + if (this.alternate != other.alternate) { + return false; + } + return Objects.equals(this.fileName, other.fileName); + } + + /** + * Get file / paths combinations + * + * @return a one or more path to the file + */ + private String[] getPaths() { + if (paths != null) { + return paths; + } + + String getPath = null; + + try { // get it if have rights to do, otherwise ignore and use only current folder + getPath = System.getenv(CURRENT.env); + } catch (SecurityException ex) { + } + + if (getPath == null || "".equals(getPath)) { + return new String[]{folder}; + } + + getPath += System.getProperty("file.separator"); + return paths = new String[]{ + /** + * Uncomment the next line and it will load default.cfg and mochadoom.cfg from user home dir + * I find it undesirable - it can load some unrelated file and even write it at exit + * - Good Sign 2017/04/19 + */ + //getPath + folder + fileName, + getFolder() + fileName + }; + } + + private static String getFolder() { + return folder != null ? folder : (folder + = Engine.getCVM().bool(CommandVariable.SHDEV) + || Engine.getCVM().bool(CommandVariable.REGDEV) + || Engine.getCVM().bool(CommandVariable.FR1DEV) + || Engine.getCVM().bool(CommandVariable.FRDMDEV) + || Engine.getCVM().bool(CommandVariable.FR2DEV) + || Engine.getCVM().bool(CommandVariable.COMDEV) + ? dstrings.DEVDATA + System.getProperty("file.separator") + : ""); + } + } + + /** + * To be able to look for config in several places + * Still unfinished + */ + public static List getFiles() { + final List ret = new ArrayList<>(); + + /** + * If user supplied -config argument, it will only use the values from these files instead of defaults + */ + if (!Engine.getCVM() + .with(CommandVariable.CONFIG, 0, (String[] fileNames) + -> Arrays.stream(fileNames).map(fileName -> new Files(fileName, true)).forEach(ret::add)) /** + * If there is no such argument, load default.cfg (or .doomrc) and mochadoom.cfg + */ + ) { + ret.add(FILE_DOOM); + ret.add(FILE_MOCHADOOM); + } + + return ret; + } +} \ No newline at end of file diff --git a/doom/src/doom/ConfigManager.java b/doom/src/doom/ConfigManager.java new file mode 100644 index 0000000..669b902 --- /dev/null +++ b/doom/src/doom/ConfigManager.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +import doom.ConfigBase.Files; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import m.Settings; +import static m.Settings.SETTINGS_MAP; +import mochadoom.Loggers; +import utils.ParseString; +import utils.QuoteType; +import utils.ResourceIO; + +/** + * Loads and saves game cfg files + * + * @author Good Sign + */ +public class ConfigManager { + + private static final Logger LOGGER = Loggers.getLogger(ConfigManager.class.getName()); + + private static final Pattern SPLITTER = Pattern.compile("[ \t\n\r\f]+"); + + private final List configFiles = ConfigBase.getFiles(); + private final EnumMap configMap = new EnumMap<>(Settings.class); + + public enum UpdateStatus { + UNCHANGED, UPDATED, INVALID; + } + + public ConfigManager() { + LoadDefaults(); + } + + public UpdateStatus update(final Settings setting, final String value) { + if (setting.valueType == String.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == Character.class + || setting.valueType == Long.class + || setting.valueType == Integer.class + || setting.valueType == Boolean.class) { + final Object parse = ParseString.parseString(value); + if (setting.valueType.isInstance(parse)) { + return setting.hasChange(!Objects.equals(configMap.put(setting, parse), parse)); + } + } else if (setting.valueType.getSuperclass() == Enum.class) { + // Enum search by name + @SuppressWarnings({"unchecked", "rawtypes"}) + final Object enumerated = Enum.valueOf((Class) setting.valueType, value); + return setting.hasChange(!Objects.equals(configMap.put(setting, enumerated), enumerated)); + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final Object value) { + if (setting.valueType == String.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value.toString()), value.toString())); + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final int value) { + if (setting.valueType == Integer.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == String.class) { + final String valStr = Integer.toString(value); + return setting.hasChange(!Objects.equals(configMap.put(setting, valStr), valStr)); + } else if (setting.valueType.getSuperclass() == Enum.class) { + final Object[] enumValues = setting.valueType.getEnumConstants(); + if (value >= 0 && value < enumValues.length) { + return setting.hasChange(!Objects.equals(configMap.put(setting, enumValues[value]), enumValues[value])); + } + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final long value) { + if (setting.valueType == Long.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == String.class) { + final String valStr = Long.toString(value); + return setting.hasChange(!Objects.equals(configMap.put(setting, valStr), valStr)); + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final double value) { + if (setting.valueType == Double.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == String.class) { + final String valStr = Double.toString(value); + return setting.hasChange(!Objects.equals(configMap.put(setting, valStr), valStr)); + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final char value) { + if (setting.valueType == Character.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == String.class) { + final String valStr = Character.toString(value); + return setting.hasChange(!Objects.equals(configMap.put(setting, valStr), valStr)); + } + + return UpdateStatus.INVALID; + } + + public UpdateStatus update(final Settings setting, final boolean value) { + if (setting.valueType == Boolean.class) { + return setting.hasChange(!Objects.equals(configMap.put(setting, value), value)); + } else if (setting.valueType == String.class) { + final String valStr = Boolean.toString(value); + return setting.hasChange(!Objects.equals(configMap.put(setting, valStr), valStr)); + } + + return UpdateStatus.INVALID; + } + + private String export(final Settings setting) { + return setting.quoteType().map(qt -> { + return new StringBuilder() + .append(setting.name()) + .append("\t\t") + .append(qt.quoteChar) + .append(configMap.get(setting)) + .append(qt.quoteChar) + .toString(); + }).orElseGet(() -> { + return new StringBuilder() + .append(setting.name()) + .append("\t\t") + .append(configMap.get(setting)) + .toString(); + }); + } + + public boolean equals(final Settings setting, final Object obj) { + return obj.equals(configMap.get(setting)); + } + + @SuppressWarnings("unchecked") + public T getValue(final Settings setting, final Class valueType) { + if (setting.valueType == valueType) { + return (T) configMap.get(setting); + } else if (valueType == String.class) { + return (T) configMap.get(setting).toString(); + } else if (setting.valueType == String.class) { + if (valueType == Character.class + || valueType == Long.class + || valueType == Integer.class + || valueType == Boolean.class) { + final Object parse = ParseString.parseString(configMap.get(setting).toString()); + if (valueType.isInstance(parse)) { + return (T) parse; + } + } + } else if (valueType == Integer.class && setting.valueType.getSuperclass() == Enum.class) { + return (T) ((Integer) ((Enum) configMap.get(setting)).ordinal()); + } + + throw new IllegalArgumentException("Unsupported cast: " + setting.valueType + " to " + valueType); + } + + public void SaveDefaults() { + SETTINGS_MAP.forEach((file, settings) -> { + // skip writing settings which are not part of the loaded config files, + // this helps to not overwrite default.cfg with empty content in case we're using the -config argument + if (!this.configFiles.contains(file)) { + return; + } + + // do not write unless there is changes + if (!file.changed) { + return; + } + + // choose existing config file or create one in current working directory + final ResourceIO rio = file.firstValidPathIO().orElseGet(file::workDirIO); + final Iterator it = settings.stream().sorted(file.comparator).iterator(); + if (rio.writeLines(() -> { + if (it.hasNext()) { + return export(it.next()); + } + + return null; + }, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { + // we wrote successfully - so it will not try to write it again, unless something really change + file.changed = false; + } + }); + } + + /** + * Handles variables and settings from default.cfg and other config files + * They can be load even earlier then other systems + */ + private void LoadDefaults() { + Arrays.stream(Settings.values()) + .forEach(setting -> { + configMap.put(setting, setting.defaultValue); + }); + + LOGGER.log(Level.INFO, "M_LoadDefaults: Load system defaults."); + this.configFiles.forEach(file -> { + final Optional maybeRIO = file.firstValidPathIO(); + + /** + * Each file successfully read marked as not changed, and as changed - those who don't exist + * + */ + file.changed = !(maybeRIO.isPresent() && readFoundConfig(file, maybeRIO.get())); + }); + + // create files who don't exist (it will skip those with changed = false - all who exists) + SaveDefaults(); + } + + private boolean readFoundConfig(Files file, ResourceIO rio) { + LOGGER.log(Level.INFO, String.format("M_LoadDefaults: Using config %s.", rio.getFileame())); + if (rio.readLines(line -> { + final String[] split = SPLITTER.split(line, 2); + if (split.length < 2) { + return; + } + + final String name = split[0]; + try { + final Settings setting = Settings.valueOf(name); + final String value = setting.quoteType() + .filter(qt -> qt == QuoteType.DOUBLE) + .map(qt -> qt.unQuote(split[1])) + .orElse(split[1]); + + if (update(setting, value) == UpdateStatus.INVALID) { + LOGGER.log(Level.WARNING, String.format("WARNING: invalid config value for: %s in %s", name, rio.getFileame())); + } else { + setting.rebase(file); + } + } catch (IllegalArgumentException ex) { + } + })) { + return true; // successfully read a file + } + + // Something went bad, but this won't destroy successfully read values, though. + LOGGER.log(Level.SEVERE, String.format("Can't read the settings file %s", rio.getFileame())); + return false; + } + +} \ No newline at end of file diff --git a/doom/src/doom/DoomContext.java b/doom/src/doom/DoomContext.java new file mode 100644 index 0000000..a8e0b61 --- /dev/null +++ b/doom/src/doom/DoomContext.java @@ -0,0 +1,36 @@ +package doom; + +/** Since a lot of stuff requires shared/global access to + * the WadLoader, the Renderer, the Video system etc. and + * we're trying to depart from the global/static mentality, + * a common sharing is required. Ideally, this would be a perfect + * example of where multiple inheritance could be adopted, since most + * stuff needs to share this status anyway. The next best thing is + * to have local references of any used fields in the classes that use them. + * + * About generics: T refers to the type of the graphics resources, and is + * currently byte[], as all graphics resources are 8-bit indexed. There are + * no plans that this will change anytime soon. Some classes should allow + * different types in theory, but it would be too complex and pointless to + * make everything fully compliant at the moment. + * + * V refers to the type of DISPLAY, and can be 8-bit (byte[]), 16-bit (short[] + * for HiColor and lesser modes such as ARGB4444, etc.), and, in the future, + * int[] (truecolor). + * + * The general approach is sharing as much code as possible between different + * implementations (e.g. rendering code), and only specialize methods/classes when + * the abstraction of generics isn't enough (typically, when you have to assign + * directly to primitive arrays or deal with primitive method signatures). + * + * Classes that have specialized code for indexed and hicolor modes should be top-level + * classes in their package, and contain two nested, static, extending classes called + * Indexed and HiColor e.g. new MyClass.Indexed() and new MyClass.HiColor(), while any common + * code should reside in MyClass. + * + * @author velktron + * + */ +public final class DoomContext { + +} \ No newline at end of file diff --git a/doom/src/doom/DoomMain.java b/doom/src/doom/DoomMain.java new file mode 100644 index 0000000..23974d5 --- /dev/null +++ b/doom/src/doom/DoomMain.java @@ -0,0 +1,4011 @@ +package doom; + +import automap.IAutoMap; +import static data.Defines.BACKUPTICS; +import static data.Defines.BTS_PAUSE; +import static data.Defines.BTS_SAVEGAME; +import static data.Defines.BTS_SAVEMASK; +import static data.Defines.BTS_SAVESHIFT; +import static data.Defines.BT_ATTACK; +import static data.Defines.BT_CHANGE; +import static data.Defines.BT_SPECIAL; +import static data.Defines.BT_SPECIALMASK; +import static data.Defines.BT_USE; +import static data.Defines.BT_WEAPONSHIFT; +import static data.Defines.JAVARANDOM_MASK; +import static data.Defines.NORMALUNIX; +import static data.Defines.NUMWEAPONS; +import static data.Defines.PST_DEAD; +import static data.Defines.PST_LIVE; +import static data.Defines.PST_REBORN; +import static data.Defines.PU_CACHE; +import static data.Defines.PU_STATIC; +import static data.Defines.SKYFLATNAME; +import static data.Defines.TICRATE; +import static data.Defines.TOCENTER; +import static data.Defines.VERSION; +import static data.Limits.MAXEVENTS; +import static data.Limits.MAXINT; +import static data.Limits.MAXNETNODES; +import static data.Limits.MAXPLAYERS; +import data.Tables; +import static data.Tables.ANG45; +import static data.Tables.ANGLETOFINESHIFT; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.dstrings; +import static data.dstrings.DEVMAPS; +import static data.dstrings.SAVEGAMENAME; +import static data.info.mobjinfo; +import static data.info.states; +import data.mapthing_t; +import data.mobjtype_t; +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import defines.DoomVersion; +import defines.GameMission_t; +import defines.GameMode; +import defines.Language_t; +import defines.gamestate_t; +import static defines.gamestate_t.GS_DEMOSCREEN; +import static defines.gamestate_t.GS_FINALE; +import static defines.gamestate_t.GS_INTERMISSION; +import static defines.gamestate_t.GS_LEVEL; +import static defines.gamestate_t.GS_MINUS_ONE; +import defines.skill_t; +import defines.statenum_t; +import demo.IDemoTicCmd; +import demo.VanillaDoomDemo; +import demo.VanillaTiccmd; +import static doom.NetConsts.CMD_GET; +import static doom.NetConsts.CMD_SEND; +import static doom.NetConsts.DOOMCOM_ID; +import static doom.NetConsts.NCMD_CHECKSUM; +import static doom.NetConsts.NCMD_EXIT; +import static doom.NetConsts.NCMD_KILL; +import static doom.NetConsts.NCMD_RETRANSMIT; +import static doom.NetConsts.NCMD_SETUP; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.D_Main; +import static doom.SourceCode.D_Main.D_DoomLoop; +import static doom.SourceCode.D_Main.D_ProcessEvents; +import doom.SourceCode.G_Game; +import static doom.SourceCode.G_Game.G_BeginRecording; +import static doom.SourceCode.G_Game.G_BuildTiccmd; +import static doom.SourceCode.G_Game.G_CheckSpot; +import static doom.SourceCode.G_Game.G_DeathMatchSpawnPlayer; +import static doom.SourceCode.G_Game.G_DoCompleted; +import static doom.SourceCode.G_Game.G_DoLoadGame; +import static doom.SourceCode.G_Game.G_DoLoadLevel; +import static doom.SourceCode.G_Game.G_DoNewGame; +import static doom.SourceCode.G_Game.G_DoPlayDemo; +import static doom.SourceCode.G_Game.G_DoReborn; +import static doom.SourceCode.G_Game.G_DoSaveGame; +import static doom.SourceCode.G_Game.G_InitNew; +import static doom.SourceCode.G_Game.G_Responder; +import static doom.SourceCode.G_Game.G_Ticker; +import static doom.englsh.D_CDROM; +import static doom.englsh.D_DEVSTR; +import static doom.englsh.GGSAVED; +import static doom.englsh.SCREENSHOT; +import static doom.evtype_t.ev_joystick; +import static doom.evtype_t.ev_keydown; +import static doom.evtype_t.ev_keyup; +import static doom.evtype_t.ev_mouse; +import static doom.gameaction_t.ga_completed; +import static doom.gameaction_t.ga_failure; +import static doom.gameaction_t.ga_loadgame; +import static doom.gameaction_t.ga_loadlevel; +import static doom.gameaction_t.ga_newgame; +import static doom.gameaction_t.ga_nothing; +import static doom.gameaction_t.ga_playdemo; +import static doom.gameaction_t.ga_savegame; +import static doom.gameaction_t.ga_screenshot; +import static doom.gameaction_t.ga_victory; +import static doom.gameaction_t.ga_worlddone; +import f.EndLevel; +import f.Finale; +import f.Wiper; +import g.Signals; +import static g.Signals.ScanCode.SC_CAPSLK; +import static g.Signals.ScanCode.SC_DOWN; +import static g.Signals.ScanCode.SC_ESCAPE; +import static g.Signals.ScanCode.SC_F12; +import static g.Signals.ScanCode.SC_LALT; +import static g.Signals.ScanCode.SC_LCTRL; +import static g.Signals.ScanCode.SC_LEFT; +import static g.Signals.ScanCode.SC_LSHIFT; +import static g.Signals.ScanCode.SC_NUMKEY2; +import static g.Signals.ScanCode.SC_NUMKEY4; +import static g.Signals.ScanCode.SC_NUMKEY6; +import static g.Signals.ScanCode.SC_NUMKEY8; +import static g.Signals.ScanCode.SC_PAUSE; +import static g.Signals.ScanCode.SC_RALT; +import static g.Signals.ScanCode.SC_RCTRL; +import static g.Signals.ScanCode.SC_RIGHT; +import static g.Signals.ScanCode.SC_RSHIFT; +import static g.Signals.ScanCode.SC_SEMICOLON; +import static g.Signals.ScanCode.SC_UP; +import hu.HU; +import i.DiskDrawer; +import i.DoomSystem; +import i.IDiskDrawer; +import i.IDoomSystem; +import i.Strings; +import java.awt.Rectangle; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import m.DelegateRandom; +import m.IDoomMenu; +import m.Menu; +import m.MenuMisc; +import m.Settings; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.MAPFRACUNIT; +import mochadoom.Engine; +import mochadoom.Loggers; +import n.DoomSystemNetworking; +import n.DummyNetworkDriver; +import p.AbstractLevelLoader; +import p.ActionFunctions; +import p.BoomLevelLoader; +import p.mobj_t; +import rr.ISpriteManager; +import rr.SceneRenderer; +import rr.SpriteManager; +import rr.TextureManager; +import rr.ViewVars; +import rr.patch_t; +import rr.subsector_t; +import s.IDoomSound; +import s.IMusic; +import s.ISoundDriver; +import savegame.IDoomSaveGame; +import savegame.IDoomSaveGameHeader; +import savegame.VanillaDSG; +import savegame.VanillaDSGHeader; +import st.AbstractStatusBar; +import st.StatusBar; +import timing.ITicker; +import timing.MilliTicker; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.flags; +import static utils.C2JUtils.memcpy; +import static utils.C2JUtils.memset; +import v.DoomGraphicSystem; +import v.renderers.BppMode; +import static v.renderers.DoomScreen.FG; +import v.renderers.RendererFactory; +import v.scale.VideoScale; +import v.scale.VisualSettings; +import w.IWadLoader; +import w.WadLoader; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomMain.java,v 1.109 2012/11/06 16:04:58 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// DOOM main program (D_DoomMain) and game loop (D_DoomLoop), +// plus functions to determine game mode (shareware, registered), +// parse command line parameters, configure game parameters (turbo), +// and call the startup functions. +// +// In Mocha Doom, this was unified with d_game and doomstat.c +// +//----------------------------------------------------------------------------- +@SuppressWarnings({ + "UseOfSystemOutOrSystemErr", + "MalformedFormatString", + "CallToPrintStackTrace", + "override", + "StringBufferMayBeStringBuilder" +}) +public class DoomMain extends DoomStatus implements IDoomGameNetworking, IDoomGame, IDoom { + + private static final Logger LOGGER = Loggers.getLogger(DoomMain.class.getName()); + + public static final String RCSID = "$Id: DoomMain.java,v 1.109 2012/11/06 16:04:58 velktron Exp $"; + + // + // EVENT HANDLING + // + // Events are asynchronous inputs generally generated by the game user. + // Events can be discarded if no responder claims them + // + public final event_t[] events = new event_t[MAXEVENTS]; + public int eventhead; + public int eventtail; + + /** + * D_PostEvent + * Called by the I/O functions when input is detected + */ + public void PostEvent(event_t ev) { + /** + * Do not pollute DOOM's internal event queue - clear keys there + * - Good Sign 2017/04/24 + */ + if (ev == event_t.CANCEL_KEYS) { + // PAINFULLY and FORCEFULLY clear the buttons. + memset(gamekeydown, false, gamekeydown.length); + keysCleared = true; + return; // Nothing more to do here. + } + + events[eventhead] = ev; + eventhead = (++eventhead) & (MAXEVENTS - 1); + } + + /** + * D_ProcessEvents + * Send all the events of the given timestamp down the responder chain + */ + @D_Main.C(D_ProcessEvents) + public void ProcessEvents() { + // IF STORE DEMO, DO NOT ACCEPT INPUT + if ((isCommercial())) { + W_CheckNumForName: + { + if ((wadLoader.CheckNumForName("MAP01") < 0)) { + return; + } + } + } + + for (; eventtail != eventhead; eventtail = (++eventtail) & (MAXEVENTS - 1)) { + final event_t ev = events[eventtail]; + ev.withMouse(event_t.mouseevent_t::processedNotify); + + M_Responder: + { + if (menu.Responder(ev)) { + continue; // menu ate the event + } + } + + G_Responder: + { + Responder(ev); + } + } + } + + // "static" to Display, don't move. + private boolean viewactivestate = false; + private boolean menuactivestate = false; + private boolean inhelpscreensstate = false; + private boolean fullscreen = false; + private gamestate_t oldgamestate = GS_MINUS_ONE; + private int borderdrawcount; + + /** + * D_Display + * draw current display, possibly wiping it from the previous + * @throws IOException + */ + public void Display() throws IOException { + int nowtime; + int tics; + int wipestart; + int y; + boolean done; + boolean wipe; + boolean redrawsbar; + + // for comparative timing / profiling + if (nodrawers) { + return; + } + redrawsbar = false; + + // change the view size if needed + if (sceneRenderer.getSetSizeNeeded()) { + sceneRenderer.ExecuteSetViewSize(); + // force background redraw + oldgamestate = GS_MINUS_ONE; + borderdrawcount = 3; + } + + // save the current screen if about to wipe + wipe = (gamestate != wipegamestate); + if (wipe) { + wiper.StartScreen(0, 0, vs.getScreenWidth(), vs.getScreenHeight()); + } + + if (gamestate == GS_LEVEL && eval(gametic)) { + headsUp.Erase(); + } + + // do buffered drawing + switch (gamestate) { + case GS_LEVEL: + if (!eval(gametic)) { + break; + } + + if (automapactive) { + autoMap.Drawer(); + } + + if (wipe + || (!sceneRenderer.isFullHeight() && fullscreen) + || (inhelpscreensstate && !inhelpscreens) + || (diskDrawer.justDoneReading())) { + redrawsbar = true; // just put away the help screen + } + statusBar.Drawer(sceneRenderer.isFullHeight(), redrawsbar); + fullscreen = sceneRenderer.isFullHeight(); + break; + case GS_INTERMISSION: + endLevel.Drawer(); + break; + case GS_FINALE: + finale.Drawer(); + break; + case GS_DEMOSCREEN: + PageDrawer(); + break; + default: + break; + } + + // draw the view directly + if (gamestate == GS_LEVEL && !automapactive && eval(gametic)) { + if (flashing_hom) { + graphicSystem.FillRect(FG, new Rectangle(view.getViewWindowX(), view.getViewWindowY(), + view.getScaledViewWidth(), view.getScaledViewHeight()), gametic % 256); + } + sceneRenderer.RenderPlayerView(players[displayplayer]); + } + + // Automap was active, update only HU. + if (gamestate == GS_LEVEL && eval(gametic)) { + headsUp.Drawer(); + } + + // clean up border stuff + if (gamestate != oldgamestate && gamestate != GS_LEVEL) { + graphicSystem.setPalette(0); + } + + // see if the border needs to be initially drawn + if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL) { + // view was not active + viewactivestate = false; + // draw the pattern into the back screen + sceneRenderer.FillBackScreen(); + } + + // see if the border needs to be updated to the screen + if (gamestate == GS_LEVEL && !automapactive && !sceneRenderer.isFullScreen()) { + if (menuactive || menuactivestate || !viewactivestate) { + borderdrawcount = 3; + } + + if (eval(borderdrawcount)) { + // erase old menu stuff + sceneRenderer.DrawViewBorder(); + borderdrawcount--; + } + } + + menuactivestate = menuactive; + viewactivestate = viewactive; + inhelpscreensstate = inhelpscreens; + oldgamestate = wipegamestate = gamestate; + + // draw pause pic + if (paused) { + if (automapactive) { + y = 4 * graphicSystem.getScalingY(); + } else { + y = view.getViewWindowY() + 4 * graphicSystem.getScalingY(); + } + + final patch_t pause = wadLoader.CachePatchName("M_PAUSE", PU_CACHE); + graphicSystem.DrawPatchCenteredScaled(FG, pause, vs, y, DoomGraphicSystem.V_NOSCALESTART); + } + + // menus go directly to the screen + menu.Drawer(); // menu is drawn even on top of everything + NetUpdate(); // send out any new accumulation + + // Disk access goes after everything. + diskDrawer.Drawer(); + + // normal update + if (!wipe) { + //System.out.print("Tick "+gametic+"\t"); + //System.out.print(players[0]); + Engine.updateFrame(); // page flip or blit buffer + return; + } + + // wipe update. At this point, AT LEAST one frame of the game must have been + // rendered for this to work. 22/5/2011: Fixed a vexing bug with the wiper. + // Jesus Christ with a Super Shotgun! + wiper.EndScreen(0, 0, vs.getScreenWidth(), vs.getScreenHeight()); + + wipestart = ticker.GetTime() - 1; + + do { + do { + nowtime = ticker.GetTime(); + tics = nowtime - wipestart; + } while (tics == 0); // Wait until a single tic has passed. + wipestart = nowtime; + Wiper.Wipe wipeType = CM.equals(Settings.scale_melt, Boolean.TRUE) + ? Wiper.Wipe.ScaledMelt : Wiper.Wipe.Melt; + + done = wiper.ScreenWipe(wipeType, 0, 0, vs.getScreenWidth(), vs.getScreenHeight(), tics); + soundDriver.UpdateSound(); + soundDriver.SubmitSound(); // update sounds after one wipe tic. + menu.Drawer(); // menu is drawn even on top of wipes + Engine.updateFrame(); // page flip or blit buffer + } while (!done); + } + + /** + * To be able to debug vanilla incompatibilitites, the DoomLoop + * and all that is called by it that relates to the Loop itself, + * the ticks, game object modifications, mode changes and so on, + * ***MUST*** be preceded by a label, containing original + * underscored naming of the method in Doom Source Code. + * + * Remember the label blocks will retain their name even in case + * of *automated refactoring*, thus if you rename some method + * and update it throughout the whole codebase, the named label + * will still be named the same underscored original method name + * + * Do it the most verbose way you can - preserving both, or all + * brackets of all blocks containing and contained in the label, + * and the brackets of the label itself, with one exception: + * + * If there is no more function to do the task was given to the + * function in original Doom Source Code, the label stull ***MUST*** + * be present, just type a semicolon to end it without actions. + * The syntax is short and displays clearly that nothing is done. + * - Good Sign 2017/04/26 + * + * D_DoomLoop() + * Not a globally visible function, + * just included for source reference, + * called by D_DoomMain, never exits. + * Manages timing and IO, + * calls all ?_Responder, ?_Ticker, and ?_Drawer, + * calls I_GetTime, I_StartFrame, and I_StartTic + * @throws IOException + */ + @D_Main.C(D_DoomLoop) + public void DoomLoop() throws IOException { + if (demorecording) { + G_BeginRecording: + { + BeginRecording(); + } + } + + M_CheckParm: + { + if (cVarManager.bool(CommandVariable.DEBUGFILE)) { + String filename = "debug" + consoleplayer + ".txt"; + LOGGER.log(Level.INFO, String.format("Debug output to: %s", filename)); + try { + debugfile = new OutputStreamWriter(new FileOutputStream(filename)); + } catch (FileNotFoundException e) { + LOGGER.log(Level.SEVERE, "Couldn't open debugfile.", e); + } + } + } + + I_InitGraphics: + { + view = sceneRenderer.getView(); + } + + while (true) { + // frame syncronous IO operations + I_StartFrame: + ; + + // process one or more tics + if (singletics) { + I_StartTic: + ; + D_ProcessEvents: + { + ProcessEvents(); + } + G_BuildTiccmd: + { + BuildTiccmd(netcmds[consoleplayer][maketic % BACKUPTICS]); + } + if (advancedemo) { + D_DoAdvanceDemo: + { + DoAdvanceDemo(); + } + } + M_Ticker: + { + menu.Ticker(); + } + G_Ticker: + { + Ticker(); + } + gametic++; + maketic++; + } else { + gameNetworking.TryRunTics(); // will run at least one tic (in NET) + } + S_UpdateSounds: + { + doomSound.UpdateSounds(players[consoleplayer].mo); // move positional sounds + } + D_Display: + { // Update display, next frame, with current state. + Display(); + } + //#ifndef SNDSERV + // Sound mixing for the buffer is snychronous. + soundDriver.UpdateSound(); + //#endif + // Synchronous sound output is explicitly called. + //#ifndef SNDINTR + // Update sound output. + soundDriver.SubmitSound(); + //#endif + } + } + + // To keep an "eye" on the renderer. + protected ViewVars view; + + // + // DEMO LOOP + // + int demosequence; + int pagetic; + String pagename; + + /** + * D_PageTicker + * Handles timing for warped projection + */ + public final void PageTicker() { + if (--pagetic < 0) { + AdvanceDemo(); + } + } + + /** + * D_PageDrawer + */ + public final void PageDrawer() { + // FIXME: this check wasn't necessary in vanilla, since pagename was + // guaranteed(?) not to be null or had a safe default value. + if (pagename != null) { + graphicSystem.DrawPatchScaled(FG, wadLoader.CachePatchName(pagename, PU_CACHE), vs, 0, 0, DoomGraphicSystem.V_SAFESCALE); + } + } + + /** + * D_AdvanceDemo + * Called after each demo or intro demosequence finishes + */ + public void AdvanceDemo() { + advancedemo = true; + } + + /** + * This cycles through the demo sequences. + * FIXME - version dependant demo numbers? + */ + public void DoAdvanceDemo() { + players[consoleplayer].playerstate = PST_LIVE; // not reborn + advancedemo = false; + usergame = false; // no save / end game here + paused = false; + gameaction = ga_nothing; + + if (isRetail()) // Allows access to a 4th demo. + { + demosequence = (demosequence + 1) % 7; + } else { + demosequence = (demosequence + 1) % 6; + } + + switch (demosequence) { + case 0: + if (isCommercial()) { + pagetic = 35 * 11; + } else { + pagetic = 170; + } + gamestate = GS_DEMOSCREEN; + + if (wadLoader.CheckNumForName("TITLEPIC") != -1) { + pagename = "TITLEPIC"; + } else { + if (wadLoader.CheckNumForName("DMENUPIC") != -1) { + pagename = "DMENUPIC"; + } + } + + if (isCommercial()) { + doomSound.StartMusic(musicenum_t.mus_dm2ttl); + } else { + doomSound.StartMusic(musicenum_t.mus_intro); + } + break; + case 1: + DeferedPlayDemo("demo1"); + break; + case 2: + pagetic = 200; + gamestate = GS_DEMOSCREEN; + pagename = "CREDIT"; + break; + case 3: + DeferedPlayDemo("demo2"); + break; + case 4: + gamestate = GS_DEMOSCREEN; + if (isCommercial()) { + pagetic = 35 * 11; + pagename = "TITLEPIC"; + doomSound.StartMusic(musicenum_t.mus_dm2ttl); + } else { + pagetic = 200; + + if (isRetail()) { + pagename = "CREDIT"; + } else { + pagename = "HELP1"; + } + } + break; + case 5: + DeferedPlayDemo("demo3"); + break; + // THE DEFINITIVE DOOM Special Edition demo + case 6: + DeferedPlayDemo("demo4"); + break; + } + } + + /** + * D_StartTitle + */ + public void StartTitle() { + gameaction = ga_nothing; + demosequence = -1; + AdvanceDemo(); + } + + // print title for every printed line + StringBuffer title = new StringBuffer(); + + /** + * D_AddFile + * + * Adds file to the end of the wadfiles[] list. + * Quite crude, we could use a listarray instead. + * + * @param file + */ + private void AddFile(String file) { + int numwadfiles; + for (numwadfiles = 0; eval(wadfiles[numwadfiles]); numwadfiles++) { + } + wadfiles[numwadfiles] = file; + } + + /** + * IdentifyVersion + * Checks availability of IWAD files by name, + * to determine whether registered/commercial features + * should be executed (notably loading PWAD's). + */ + public final String IdentifyVersion() { + String doomwaddir; + // By default. + language = Language_t.english; + + // First, check for -iwad parameter. + // If valid, then it trumps all others. + if (cVarManager.present(CommandVariable.IWAD)) { + LOGGER.log(Level.INFO, "-iwad specified. Will be used with priority"); + // It might be quoted. + final String test = C2JUtils.unquoteIfQuoted(cVarManager.get(CommandVariable.IWAD, String.class, 0).get(), '"'); + final String separator = System.getProperty("file.separator"); + final String iwad = test.substring(1 + test.lastIndexOf(separator)); + doomwaddir = test.substring(0, 1 + test.lastIndexOf(separator)); + final GameMode attempt = DoomVersion.tryOnlyOne(iwad, doomwaddir); + // Note: at this point we can't distinguish between "doom" retail + // and "doom" ultimate yet. + if (attempt != null) { + AddFile(doomwaddir + iwad); + this.setGameMode(attempt); + return (doomwaddir + iwad); + } + } else { + // Unix-like checking. Might come in handy sometimes. + // This should ALWAYS be activated, else doomwaddir etc. won't be defined. + + doomwaddir = System.getenv("DOOMWADDIR"); + if (doomwaddir != null) { + LOGGER.log(Level.INFO, "DOOMWADDIR found. Will be used with priority"); + } + + // None found, using current. + if (!eval(doomwaddir)) { + doomwaddir = System.getProperty("user.dir") + "\\wads"; + } + + System.out.println("DOOMWADDIR: " + doomwaddir); + } + + for (GameMode mode : GameMode.values()) { + if (mode != GameMode.indetermined && cVarManager.bool(mode.devVar)) { + return devParmOn(mode); + } + } + + final String wadFullPath = DoomVersion.tryAllWads(this, doomwaddir); + if (wadFullPath == null) { + LOGGER.log(Level.INFO, "Game mode indeterminate."); + setGameMode(GameMode.indetermined); + // We don't abort. Let's see what the PWAD contains. + //exit(1); + //I_Error ("Game mode indeterminate\n"); + } else { + AddFile(wadFullPath); + } + + return wadFullPath; + } + + private String devParmOn(GameMode mode) { + setGameMode(mode); + devparm = true; + AddFile(dstrings.DEVDATA + mode.version); + AddFile(dstrings.DEVMAPS + mode.devDir + "/texture1.lmp"); + if (mode.hasTexture2()) { + AddFile(dstrings.DEVMAPS + mode.devDir + "/texture2.lmp"); + } + AddFile(dstrings.DEVMAPS + mode.devDir + "/pnames.lmp"); + return (dstrings.DEVDATA + mode.version); + } + + /** + * + */ + protected final void CheckForPWADSInShareware() { + if (modifiedgame) { + // These are the lumps that will be checked in IWAD, + // if any one is not present, execution will be aborted. + String[] name = { + "e2m1", "e2m2", "e2m3", "e2m4", "e2m5", "e2m6", "e2m7", "e2m8", "e2m9", + "e3m1", "e3m3", "e3m3", "e3m4", "e3m5", "e3m6", "e3m7", "e3m8", "e3m9", + "dphoof", "bfgga0", "heada1", "cybra1", "spida1d1" + }; + int i; + + // Oh yes I can. + if (isShareware()) { + LOGGER.log(Level.WARNING, "You cannot -file with the shareware version. Register!"); + } + + // Check for fake IWAD with right name, + // but w/o all the lumps of the registered version. + if (isRegistered()) { + for (i = 0; i < 23; i++) { + if (wadLoader.CheckNumForName(name[i].toUpperCase()) < 0) { + doomSystem.Error("This is not the registered version: " + name[i]); + } + } + } + } + } + + /** Check whether the "doom.wad" we actually loaded + * is ultimate Doom's, by checking if it contains + * e4m1 - e4m9. + * + */ + protected final void CheckForUltimateDoom(WadLoader W) { + if (isRegistered()) { + // These are the lumps that will be checked in IWAD, + // if any one is not present, execution will be aborted. + String[] lumps = {"e4m1", "e4m2", "e4m3", "e4m4", "e4m5", "e4m6", "e4m7", "e4m8", "e4m9"}; + + // Check for fake IWAD with right name, + // but w/o all the lumps of the registered version. + if (!CheckForLumps(lumps, W)) { + return; + } + // Checks passed, so we can set the mode to Ultimate + setGameMode(GameMode.retail); + } + + } + + /** Check if ALL of the lumps exist. + * + * @param name + * @return + */ + protected boolean CheckForLumps(String[] name, WadLoader W) { + for (String name1 : name) { + if (W.CheckNumForName(name1.toUpperCase()) < 0) { + // Even one is missing? Not OK. + return false; + } + } + return true; + } + + protected final void GenerateTitle() { + switch (getGameMode()) { + case retail: + title.append("The Ultimate DOOM Startup v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case shareware: + title.append("DOOM Shareware Startup v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case registered: + title.append("DOOM Registered Startup v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case commercial: + title.append("DOOM 2: Hell on Earth v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case pack_plut: + title.append("DOOM 2: Plutonia Experiment v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case pack_tnt: + title.append("DOOM 2: TNT - Evilution v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case pack_xbla: + title.append("DOOM 2: No Rest for the Living v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case freedm: + title.append("FreeDM v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case freedoom1: + title.append("FreeDoom: Phase 1 v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + case freedoom2: + title.append("FreeDoom: Phase 2 v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + default: + title.append("Public DOOM - v"); + title.append(VERSION / 100); + title.append("."); + title.append(VERSION % 100); + break; + } + } + + // Used in BuildTiccmd. + protected ticcmd_t base = new ticcmd_t(); + + /** + * G_BuildTiccmd + * Builds a ticcmd from all of the available inputs + * or reads it from the demo buffer. + * If recording a demo, write it out . + * + * The CURRENT event to process is written to the various + * gamekeydown etc. arrays by the Responder method. + * So look there for any fuckups in constructing them. + * + */ + @SourceCode.Compatible + @G_Game.C(G_BuildTiccmd) + private void BuildTiccmd(ticcmd_t cmd) { + int i; + boolean strafe; + boolean bstrafe; + int speed, tspeed, lspeed; + int forward; + int side; + int look; + + I_BaseTiccmd: + ; // empty, or external driver + base.copyTo(cmd); + + cmd.consistancy = consistancy[consoleplayer][maketic % BACKUPTICS]; + + strafe = gamekeydown[key_strafe] || mousebuttons(mousebstrafe) || joybuttons(joybstrafe); + speed = ((gamekeydown[key_speed] ^ alwaysrun) || joybuttons(joybspeed)) ? 1 : 0; + + forward = side = look = 0; + + // use two stage accelerative turning + // on the keyboard and joystick + if (joyxmove < 0 || joyxmove > 0 || gamekeydown[key_right] || gamekeydown[key_left]) { + turnheld += ticdup; + } else { + turnheld = 0; + } + + tspeed = turnheld < SLOWTURNTICS ? 2 /* slowturn */ : speed; + + if (gamekeydown[key_lookdown] || gamekeydown[key_lookup]) { + lookheld += ticdup; + } else { + lookheld = 0; + } + + lspeed = lookheld < SLOWTURNTICS ? 1 : 2; + + // let movement keys cancel each other out + if (strafe) { + if (gamekeydown[key_right]) { + // fprintf(stderr, "strafe right\n"); + side += sidemove[speed]; + } + + if (gamekeydown[key_left]) { + // fprintf(stderr, "strafe left\n"); + side -= sidemove[speed]; + } + + if (joyxmove > 0) { + side += sidemove[speed]; + } else if (joyxmove < 0) { + side -= sidemove[speed]; + } + } else { + if (gamekeydown[key_right]) { + cmd.angleturn -= angleturn[tspeed]; + } + + if (gamekeydown[key_left]) { + cmd.angleturn += angleturn[tspeed]; + } + + if (joyxmove > 0) { + cmd.angleturn -= angleturn[tspeed]; + } else if (joyxmove < 0) { + cmd.angleturn += angleturn[tspeed]; + } + } + + if (gamekeydown[key_up]) { + forward += forwardmove[speed]; + } + + if (gamekeydown[key_down]) { + forward -= forwardmove[speed]; + } + + if (joyymove < 0) { + forward += forwardmove[speed]; + } else if (joyymove > 0) { + forward -= forwardmove[speed]; + } + + if (gamekeydown[key_straferight]) { + side += sidemove[speed]; + } + + if (gamekeydown[key_strafeleft]) { + side -= sidemove[speed]; + } + + // Look up/down/center keys + if (gamekeydown[key_lookup]) { + look = lspeed; + } + + if (gamekeydown[key_lookdown]) { + look = -lspeed; + } + + if (gamekeydown[key_lookcenter]) { + look = TOCENTER; + } + + // buttons + cmd.chatchar = headsUp.dequeueChatChar(); + + if (gamekeydown[key_fire] || mousebuttons(mousebfire) || joybuttons(joybfire)) { + cmd.buttons |= BT_ATTACK; + } + + if (gamekeydown[key_use] || joybuttons(joybuse)) { + cmd.buttons |= BT_USE; + // clear double clicks if hit use button + dclicks = 0; + } + + // chainsaw overrides + for (i = 0; i < NUMWEAPONS - 1; i++) { + if (gamekeydown[key_numbers[i]]) { + //System.out.println("Attempting weapon change (building ticcmd)"); + cmd.buttons |= BT_CHANGE; + cmd.buttons |= i << BT_WEAPONSHIFT; + break; + } + } + + // mouse + if (mousebuttons(mousebforward)) { + forward += forwardmove[speed]; + } + + // forward double click + if (mousebuttons(mousebforward) != eval(dclickstate) && dclicktime > 1) { + dclickstate = mousebuttons(mousebforward) ? 1 : 0; + if (dclickstate != 0) { + dclicks++; + } + if (dclicks == 2) { + cmd.buttons |= BT_USE; + dclicks = 0; + } else { + dclicktime = 0; + } + } else { + dclicktime += ticdup; + if (dclicktime > 20) { + dclicks = 0; + dclickstate = 0; + } + } + + // strafe double click + bstrafe = mousebuttons(mousebstrafe) || joybuttons(joybstrafe); + if ((bstrafe != eval(dclickstate2)) && dclicktime2 > 1) { + dclickstate2 = bstrafe ? 1 : 0; + if (dclickstate2 != 0) { + dclicks2++; + } + if (dclicks2 == 2) { + cmd.buttons |= BT_USE; + dclicks2 = 0; + } else { + dclicktime2 = 0; + } + } else { + dclicktime2 += ticdup; + if (dclicktime2 > 20) { + dclicks2 = 0; + dclickstate2 = 0; + } + } + + // By default, no vertical mouse movement + if (!novert) { + forward += mousey; + } + + if (strafe) { + side += mousex * 2; + } else { + cmd.angleturn -= mousex * 0x8; + } + + mousex = mousey = 0; + + if (forward > MAXPLMOVE()) { + forward = MAXPLMOVE(); + } else if (forward < -MAXPLMOVE()) { + forward = -MAXPLMOVE(); + } + if (side > MAXPLMOVE()) { + side = MAXPLMOVE(); + } else if (side < -MAXPLMOVE()) { + side = -MAXPLMOVE(); + } + + cmd.forwardmove += forward; + cmd.sidemove += side; + + if (players[consoleplayer].playerstate == PST_LIVE) { + if (look < 0) { + look += 16; + } + + cmd.lookfly = (char) look; + } + + // special buttons + if (sendpause) { + sendpause = false; + cmd.buttons = BT_SPECIAL | BTS_PAUSE; + } + + if (sendsave) { + sendsave = false; + cmd.buttons = (char) (BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT)); + } + } + + /** + * G_DoLoadLevel + * + * //extern gamestate_t wipegamestate; + */ + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @G_Game.C(G_DoLoadLevel) + public boolean DoLoadLevel() { + /** + * Added a config switch to this fix + * - Good Sign 2017/04/26 + * + * Fixed R_FlatNumForName was a part of the fix, not vanilla code + * - Good Sign 2017/05/07 + * + * DOOM determines the sky texture to be used + * depending on the current episode, and the game version. + * + * @SourceCode.Compatible + */ + if (Engine.getConfig().equals(Settings.fix_sky_change, Boolean.TRUE) && (isCommercial() + || (gamemission == GameMission_t.pack_tnt) + || (gamemission == GameMission_t.pack_plut))) { + // Set the sky map. + // First thing, we have a dummy sky texture name, + // a flat. The data is in the WAD only because + // we look for an actual index, instead of simply + // setting one. + textureManager.setSkyFlatNum(textureManager.FlatNumForName(SKYFLATNAME)); + + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY3")); + if (gamemap < 12) { + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY1")); + } else { + if (gamemap < 21) { + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY2")); + } + } + } + + levelstarttic = gametic; // for time calculation + + if (wipegamestate == GS_LEVEL) { + wipegamestate = GS_MINUS_ONE; // force a wipe + } + gamestate = GS_LEVEL; + + for (int i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i] && players[i].playerstate == PST_DEAD) { + players[i].playerstate = PST_REBORN; + } + + memset(players[i].frags, 0, players[i].frags.length); + } + + try { + P_SetupLevel: + { + levelLoader.SetupLevel(gameepisode, gamemap, 0, gameskill); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failure loading level.", e); + // Failure loading level. + return false; + } + + displayplayer = consoleplayer; // view the guy you are playing + I_GetTime: + { + starttime = ticker.GetTime(); + } + gameaction = ga_nothing; + Z_CheckHeap: + ; + + // clear cmd building stuff + memset(gamekeydown, false, gamekeydown.length); + keysCleared = true; + joyxmove = joyymove = 0; + mousex = mousey = 0; + sendpause = sendsave = paused = false; + memset(mousearray, false, mousearray.length); + memset(joyarray, false, joyarray.length); + + /** + * Probably no desync-effect + * - GoodSign 2017/05/07 + * + * @SourceCode.Suspicious + */ + // killough 5/13/98: in case netdemo has consoleplayer other than green + statusBar.Start(); + headsUp.Start(); + + // killough: make -timedemo work on multilevel demos + // Move to end of function to minimize noise -- killough 2/22/98: + if (timingdemo) { + if (first) { + starttime = RealTime.GetTime(); + first = false; + } + } + + // Try reclaiming some memory from limit-expanded buffers. + sceneRenderer.resetLimits(); + return true; + } + + protected boolean first = true; + + /** + * G_Responder + * Get info needed to make ticcmd_ts for the players. + */ + @SourceCode.Compatible + @G_Game.C(G_Responder) + public boolean Responder(event_t ev) { + // allow spy mode changes even during the demo + if (gamestate == GS_LEVEL && ev.isKey(SC_F12, ev_keydown) && (singledemo || !deathmatch)) { + // spy mode + do { + displayplayer++; + if (displayplayer == MAXPLAYERS) { + displayplayer = 0; + } + } while (!playeringame[displayplayer] && displayplayer != consoleplayer); + return true; + } + + // any other key pops up menu if in demos + if (gameaction == ga_nothing && !singledemo && (demoplayback || gamestate == GS_DEMOSCREEN)) { + if (ev.isType(ev_keydown) + || (use_mouse && ev.ifMouse(ev_mouse, event_t::hasData)) + || (use_joystick && ev.ifJoy(ev_joystick, event_t::hasData))) { + M_StartControlPanel: + { + menu.StartControlPanel(); + } + return true; + } + return false; + } + + if (gamestate == GS_LEVEL) { + if (devparm && ev.isKey(SC_SEMICOLON, ev_keydown)) { + G_DeathMatchSpawnPlayer: + { + DeathMatchSpawnPlayer(0); + } + return true; + } + + HU_Responder: + { + if (headsUp.Responder(ev)) { + return true; // chat ate the event + } + } + ST_Responder: + { + if (statusBar.Responder(ev)) { + return true; // status window ate it + } + } + AM_Responder: + { + if (autoMap.Responder(ev)) { + return true; // automap ate it + } + } + } + + if (gamestate == GS_FINALE) { + F_Responder: + { + if (finale.Responder(ev)) { + return true; // finale ate the event + } + } + } + + switch (ev.type()) { + case ev_keydown: + if (ev.isKey(SC_PAUSE)) { + sendpause = true; + return true; + } + + ev.withKey(sc -> { + gamekeydown[sc.ordinal()] = true; + if (vanillaKeyBehavior) { + handleVanillaKeys(sc, true); + } + }); + return true; // eat key down events + case ev_keyup: + /* CAPS lock will only go through as a keyup event */ + if (ev.isKey(SC_CAPSLK)) { + // Just toggle it. It's too hard to read the state. + alwaysrun = !alwaysrun; + players[consoleplayer].message = String.format("Always run: %s", alwaysrun); + } + + ev.withKey(sc -> { + gamekeydown[sc.ordinal()] = false; + if (vanillaKeyBehavior) { + handleVanillaKeys(sc, false); + } + }); + return false; // always let key up events filter down + + case ev_mouse: + // Ignore them at the responder level + if (use_mouse) { + mousebuttons(0, ev.isMouse(event_t.MOUSE_LEFT)); + mousebuttons(1, ev.isMouse(event_t.MOUSE_RIGHT)); + mousebuttons(2, ev.isMouse(event_t.MOUSE_MID)); + ev.withMouse(mouseEvent -> { + mousex = mouseEvent.x * (mouseSensitivity + 5) / 10; + mousey = mouseEvent.y * (mouseSensitivity + 5) / 10; + }); + } + return true; // eat events + case ev_joystick: + if (use_joystick) { + joybuttons(0, ev.isJoy(event_t.JOY_1)); + joybuttons(1, ev.isJoy(event_t.JOY_2)); + joybuttons(2, ev.isJoy(event_t.JOY_3)); + joybuttons(3, ev.isJoy(event_t.JOY_4)); + ev.withJoy(joyEvent -> { + joyxmove = joyEvent.x; + joyymove = joyEvent.y; + }); + } + return true; // eat events + default: + break; + } + + return false; + } + + private void handleVanillaKeys(Signals.ScanCode sc, boolean keyDown) { + switch (sc) { + case SC_LSHIFT: + case SC_RSHIFT: + gamekeydown[SC_RSHIFT.ordinal()] = gamekeydown[SC_LSHIFT.ordinal()] = keyDown; + break; + case SC_LCTRL: + case SC_RCTRL: + gamekeydown[SC_RCTRL.ordinal()] = gamekeydown[SC_LCTRL.ordinal()] = keyDown; + break; + case SC_LALT: + case SC_RALT: + gamekeydown[SC_RALT.ordinal()] = gamekeydown[SC_LALT.ordinal()] = keyDown; + break; + case SC_UP: + gamekeydown[SC_NUMKEY8.ordinal()] = keyDown; + break; + case SC_DOWN: + gamekeydown[SC_NUMKEY2.ordinal()] = keyDown; + break; + case SC_LEFT: + gamekeydown[SC_NUMKEY4.ordinal()] = keyDown; + break; + case SC_RIGHT: + gamekeydown[SC_NUMKEY6.ordinal()] = keyDown; + break; + default: + break; + } + } + + private final String turbomessage = "is turbo!"; + + /** + * G_Ticker + * + * Make ticcmd_ts for the players. + */ + @G_Game.C(G_Ticker) + public void Ticker() { + // do player reborns if needed + for (int i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i] && players[i].playerstate == PST_REBORN) { + G_DoReborn: + { + DoReborn(i); + } + } + } + + // do things to change the game state + while (gameaction != ga_nothing) { + switch (gameaction) { + case ga_loadlevel: + G_DoLoadLevel: + { + DoLoadLevel(); + } + break; + case ga_newgame: + G_DoNewGame: + { + DoNewGame(); + } + break; + case ga_loadgame: + G_DoLoadGame: + { + DoLoadGame(); + } + break; + case ga_savegame: + G_DoSaveGame: + { + DoSaveGame(); + } + break; + case ga_playdemo: + G_DoPlayDemo: + { + DoPlayDemo(); + } + break; + case ga_completed: + G_DoCompleted: + { + DoCompleted(); + } + break; + case ga_victory: + finale.StartFinale(); + break; + case ga_worlddone: + DoWorldDone(); + break; + case ga_screenshot: + ScreenShot(); + gameaction = ga_nothing; + break; + case ga_nothing: + break; + default: + break; + } + } + + // get commands, check consistancy, + // and build new consistancy check + final int buf = (gametic / ticdup) % BACKUPTICS; + for (int i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + final ticcmd_t cmd = players[i].cmd; + //System.out.println("Current command:"+cmd); + + //memcpy (cmd, &netcmds[i][buf], sizeof(ticcmd_t)); + netcmds[i][buf].copyTo(cmd); + + // MAES: this is where actual demo commands are being issued or created! + // Essentially, a demo is a sequence of stored ticcmd_t with a header. + // Knowing that, it's possible to objectify it. + if (demoplayback) { + ReadDemoTiccmd(cmd); + } + + if (demorecording) { + WriteDemoTiccmd(cmd); + } + + // check for turbo cheats + if (cmd.forwardmove > TURBOTHRESHOLD && ((gametic & 31) == 0) && ((gametic >> 5) & 3) == i) { + //extern char *player_names[4]; + //sprintf (turbomessage, "%s is turbo!",player_names[i]); + players[consoleplayer].message = hu.HU.player_names[i] + turbomessage; + } + + if (netgame && !netdemo && (gametic % ticdup) == 0) { + if (gametic > BACKUPTICS && consistancy[i][buf] != cmd.consistancy) { + doomSystem.Error("consistency failure (%d should be %d)", cmd.consistancy, consistancy[i][buf]); + } + + if (players[i].mo != null) { + consistancy[i][buf] = (short) players[i].mo.x; + } else { + consistancy[i][buf] = (short) random.getIndex(); + } + } + } + } + + // check for special buttons + for (int i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + if ((players[i].cmd.buttons & BT_SPECIAL) != 0) { + switch (players[i].cmd.buttons & BT_SPECIALMASK) { + case BTS_PAUSE: + // MAES: fixed stupid ^pause bug. + paused = !paused; + if (paused) { + doomSound.PauseSound(); + } else { + doomSound.ResumeSound(); + } + break; + case BTS_SAVEGAME: + if (savedescription == null) { + savedescription = "NET GAME"; + } + savegameslot = (players[i].cmd.buttons & BTS_SAVEMASK) >> BTS_SAVESHIFT; + gameaction = ga_savegame; + break; + } + } + } + } + + // do main actions + switch (gamestate) { + case GS_LEVEL: + actions.Ticker(); + statusBar.Ticker(); + autoMap.Ticker(); + headsUp.Ticker(); + break; + + case GS_INTERMISSION: + endLevel.Ticker(); + break; + + case GS_FINALE: + finale.Ticker(); + break; + + case GS_DEMOSCREEN: + PageTicker(); + break; + + default: + break; + } + } + + // + // PLAYER STRUCTURE FUNCTIONS + // also see P_SpawnPlayer in P_Things + // + /** + * G_InitPlayer + * Called at the start. + * Called by the game initialization functions. + * + * MAES: looks like dead code. It's never called. + * + */ + protected void InitPlayer(int player) { + // set up the saved info + // clear everything else to defaults + players[player].PlayerReborn(); + } + + // + // G_CheckSpot + // Returns false if the player cannot be respawned + // at the given mapthing_t spot + // because something is occupying it + // + //void P_SpawnPlayer (mapthing_t* mthing); + @SourceCode.Exact + @G_Game.C(G_CheckSpot) + private boolean CheckSpot(int playernum, mapthing_t mthing) { + if (players[playernum].mo == null) { + // first spawn of level, before corpses + for (int i = 0; i < playernum; i++) { + if (players[i].mo.x == mthing.x << FRACBITS && players[i].mo.y == mthing.y << FRACBITS) { + return false; + } + } + return true; + } + + final int x = mthing.x << FRACBITS, y = mthing.y << FRACBITS; + + P_CheckPosition: + { + if (!actions.CheckPosition(players[playernum].mo, x, y)) { + return false; + } + } + + // flush an old corpse if needed + if (bodyqueslot >= BODYQUESIZE) { + P_RemoveMobj: + { + actions.RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]); + } + } + bodyque[bodyqueslot % BODYQUESIZE] = players[playernum].mo; + bodyqueslot++; + + // spawn a teleport fog + final subsector_t ss; + R_PointInSubsector: + { + ss = levelLoader.PointInSubsector(x, y); + } + // Angles stored in things are supposed to be "sanitized" against rollovers. + final int angle = (int) ((ANG45 * (mthing.angle / 45)) >>> ANGLETOFINESHIFT); + final mobj_t mo; + P_SpawnMobj: + { + mo = actions.SpawnMobj(x + 20 * finecosine[angle], y + 20 * finesine[angle], ss.sector.floorheight, mobjtype_t.MT_TFOG); + } + + // FIXME: maybe false fix + if (players[consoleplayer].viewz != 1) { + S_StartSound: + { + doomSound.StartSound(mo, sfxenum_t.sfx_telept); // don't start sound on first frame + } + } + + return true; + } + + // + // G_DeathMatchSpawnPlayer + // Spawns a player at one of the random death match spots + // called at level load and each death + // + @Override + @SourceCode.Exact + @G_Game.C(G_DeathMatchSpawnPlayer) + public void DeathMatchSpawnPlayer(int playernum) { + final int selections = deathmatch_p; + if (selections < 4) { + I_Error: + { + doomSystem.Error("Only %d deathmatch spots, 4 required", selections); + } + } + + for (int j = 0; j < 20; j++) { + final int i; + P_Random: + { + i = random.P_Random() % selections; + } + G_CheckSpot: + { + if (CheckSpot(playernum, deathmatchstarts[i])) { + deathmatchstarts[i].type = (short) (playernum + 1); + P_SpawnPlayer: + { + actions.SpawnPlayer(deathmatchstarts[i]); + } + return; + } + } + } + + // no good spot, so the player will probably get stuck + // MAES: seriously, fuck him. + P_SpawnPlayer: + { + actions.SpawnPlayer(playerstarts[playernum]); + } + } + + /** + * G_DoReborn + */ + @SourceCode.Exact + @G_Game.C(G_DoReborn) + public void DoReborn(int playernum) { + if (!netgame) { + // reload the level from scratch + gameaction = ga_loadlevel; + } else { + // respawn at the start + + // first dissasociate the corpse + players[playernum].mo.player = null; + + // spawn at random spot if in death match + if (deathmatch) { + G_DeathMatchSpawnPlayer: + { + DeathMatchSpawnPlayer(playernum); + } + return; + } + + G_CheckSpot: + { + if (CheckSpot(playernum, playerstarts[playernum])) { + P_SpawnPlayer: + { + actions.SpawnPlayer(playerstarts[playernum]); + } + return; + } + } + + // try to spawn at one of the other players spots + for (int i = 0; i < MAXPLAYERS; i++) { + G_CheckSpot: + { + if (CheckSpot(playernum, playerstarts[i])) { + playerstarts[i].type = (short) (playernum + 1); // fake as other player + P_SpawnPlayer: + { + actions.SpawnPlayer(playerstarts[i]); + } + playerstarts[i].type = (short) (i + 1); // restore + return; + } + } + // he's going to be inside something. Too bad. + // MAES: Yeah, they're like, fuck him. + } + + P_SpawnPlayer: + { + actions.SpawnPlayer(playerstarts[playernum]); + } + } + } + + /** DOOM Par Times [4][10] */ + final int[][] pars = { + {0}, + {0, 30, 75, 120, 90, 165, 180, 180, 30, 165}, + {0, 90, 90, 90, 120, 90, 360, 240, 30, 170}, + {0, 90, 45, 90, 150, 90, 90, 165, 30, 135} + }; + + /** DOOM II Par Times */ + final int[] cpars = { + 30, 90, 120, 120, 90, 150, 120, 120, 270, 90, // 1-10 + 210, 150, 150, 150, 210, 150, 420, 150, 210, 150, // 11-20 + 240, 150, 180, 150, 150, 300, 330, 420, 300, 180, // 21-30 + 120, 30 // 31-32 + }; + + // + // G_DoCompleted + // + boolean secretexit; + + public final void ExitLevel() { + secretexit = false; + gameaction = ga_completed; + } + + // Here's for the german edition. + public void SecretExitLevel() { + // IF NO WOLF3D LEVELS, NO SECRET EXIT! + secretexit = !(isCommercial() && (wadLoader.CheckNumForName("MAP31") < 0)); + gameaction = ga_completed; + } + + @SourceCode.Exact + @G_Game.C(G_DoCompleted) + protected void DoCompleted() { + gameaction = ga_nothing; + + for (int i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + G_PlayerFinishLevel: + { // take away cards and stuff + players[i].PlayerFinishLevel(); + } + } + } + + if (automapactive) { + AM_Stop: + { + autoMap.Stop(); + } + } + + if (!isCommercial()) { + switch (gamemap) { + case 8: + // MAES: end of episode + gameaction = ga_victory; + return; + case 9: + // MAES: end of secret level + for (int i = 0; i < MAXPLAYERS; i++) { + players[i].didsecret = true; + } + break; + default: + break; + } + } + + wminfo.didsecret = players[consoleplayer].didsecret; + wminfo.epsd = gameepisode - 1; + wminfo.last = gamemap - 1; + + // wminfo.next is 0 biased, unlike gamemap + if (isCommercial()) { + if (secretexit) { + switch (gamemap) { + case 2: + wminfo.next = 32; //Fix Doom 3 BFG Edition, MAP02 secret exit to MAP33 Betray + break; + case 15: + wminfo.next = 30; + break; + case 31: + wminfo.next = 31; + break; + default: + break; + } + } else { + switch (gamemap) { + case 31: + case 32: + wminfo.next = 15; + break; + case 33: + wminfo.next = 2; //Fix Doom 3 BFG Edition, MAP33 Betray exit back to MAP03 + break; + default: + wminfo.next = gamemap; + } + } + } else { + if (secretexit) { + wminfo.next = 8; // go to secret level + } else if (gamemap == 9) { + // returning from secret level + switch (gameepisode) { + case 1: + wminfo.next = 3; + break; + case 2: + wminfo.next = 5; + break; + case 3: + wminfo.next = 6; + break; + case 4: + wminfo.next = 2; + break; + default: + break; + } + } else { + wminfo.next = gamemap; // go to next level + } + } + + wminfo.maxkills = totalkills; + wminfo.maxitems = totalitems; + wminfo.maxsecret = totalsecret; + wminfo.maxfrags = 0; + + if (isCommercial()) { + wminfo.partime = 35 * cpars[gamemap - 1]; + } else if (gameepisode >= pars.length) { + wminfo.partime = 0; + } else { + wminfo.partime = 35 * pars[gameepisode][gamemap]; + } + + wminfo.pnum = consoleplayer; + + for (int i = 0; i < MAXPLAYERS; i++) { + wminfo.plyr[i].in = playeringame[i]; + wminfo.plyr[i].skills = players[i].killcount; + wminfo.plyr[i].sitems = players[i].itemcount; + wminfo.plyr[i].ssecret = players[i].secretcount; + wminfo.plyr[i].stime = leveltime; + memcpy(wminfo.plyr[i].frags, players[i].frags, wminfo.plyr[i].frags.length); + } + + gamestate = GS_INTERMISSION; + viewactive = false; + automapactive = false; + + if (statcopy != null) { + memcpy(statcopy, wminfo, 1); + } + + WI_Start: + { + endLevel.Start(wminfo); + } + } + + /** + * G_WorldDone + */ + public void WorldDone() { + gameaction = ga_worlddone; + + if (secretexit) { + players[consoleplayer].didsecret = true; + } + + if (isCommercial()) { + switch (gamemap) { + case 15: + case 31: + if (!secretexit) { + break; + } + case 6: + case 11: + case 20: + case 30: + finale.StartFinale(); + break; + } + } + } + + public void DoWorldDone() { + gamestate = GS_LEVEL; + gamemap = wminfo.next + 1; + DoLoadLevel(); + gameaction = ga_nothing; + viewactive = true; + } + + // + // G_InitFromSavegame + // Can be called by the startup code or the menu task. + // + //extern boolean setsizeneeded; + //void R_ExecuteSetViewSize (void); + String savename; + + public void LoadGame(String name) { + savename = name; + gameaction = ga_loadgame; + } + + /** + * This is fugly. Making a "savegame object" will make at least certain comparisons easier, and avoid writing code + * twice. + */ + @SourceCode.Suspicious(CauseOfDesyncProbability.MEDIUM) + @G_Game.C(G_DoLoadGame) + protected void DoLoadGame() { + try { + StringBuffer vcheck = new StringBuffer(); + VanillaDSGHeader header = new VanillaDSGHeader(); + IDoomSaveGame dsg = new VanillaDSG<>(this); + + gameaction = ga_nothing; + + DataInputStream f = new DataInputStream(new BufferedInputStream(new FileInputStream(savename))); + + header.read(f); + f.close(); + + // skip the description field + vcheck.append("version "); + vcheck.append(VERSION); + + if (vcheck.toString().compareTo(header.getVersion()) != 0) { + f.close(); + return; // bad version + } + + // Ok so far, reopen stream. + f = new DataInputStream(new BufferedInputStream(new FileInputStream(savename))); + gameskill = header.getGameskill(); + gameepisode = header.getGameepisode(); + gamemap = header.getGamemap(); + System.arraycopy(header.getPlayeringame(), 0, playeringame, 0, MAXPLAYERS); + + // load a base level + G_InitNew: + { + InitNew(gameskill, gameepisode, gamemap); + } + + if (gameaction == ga_failure) { + // failure to load. Abort. + f.close(); + return; + } + + gameaction = ga_nothing; + + // get the times + leveltime = header.getLeveltime(); + + boolean ok; + // dearchive all the modifications + P_UnArchivePlayers: + P_UnArchiveWorld: + P_UnArchiveThinkers: + P_UnArchiveSpecials: + { + ok = dsg.doLoad(f); + } + f.close(); + + // MAES: this will cause a forced exit. + // The problem is that the status will have already been altered + // (perhaps VERY badly) so it makes no sense to progress. + // If you want it bullet-proof, you could implement + // a "tentative loading" subsystem, which will only alter the game + // if everything works out without errors. But who cares :-p + if (!ok) { + I_Error: + { + doomSystem.Error("Bad savegame"); + } + } + + // done + //Z_Free (savebuffer); + if (sceneRenderer.getSetSizeNeeded()) { + R_ExecuteSetViewSize: + { + sceneRenderer.ExecuteSetViewSize(); + } + } + + // draw the pattern into the back screen + R_FillBackScreen: + { + sceneRenderer.FillBackScreen(); + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failure loading game.", e); + } + } + + // + // G_SaveGame + // Called by the menu task. + // Description is a 24 byte text string + // + public void SaveGame(int slot, String description) { + savegameslot = slot; + savedescription = description; + sendsave = true; + } + + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @G_Game.C(G_DoSaveGame) + protected void DoSaveGame() { + + try { + String name; + //char[] name2=new char[VERSIONSIZE]; + String description; + StringBuffer build = new StringBuffer(); + IDoomSaveGameHeader header = new VanillaDSGHeader(); + IDoomSaveGame dsg = new VanillaDSG<>(this); + + M_CheckParm: + { + if (cVarManager.bool(CommandVariable.CDROM)) { + build.append("c:\\doomdata\\"); + } + } + + build.append(String.format("%s%d.dsg", SAVEGAMENAME, savegameslot)); + name = build.toString(); + + description = savedescription; + + header.setName(description); + header.setVersion(String.format("version %d", VERSION)); + header.setGameskill(gameskill); + header.setGameepisode(gameepisode); + header.setGamemap(gamemap); + header.setPlayeringame(playeringame); + header.setLeveltime(leveltime); + dsg.setHeader(header); + + // Try opening a save file. No intermediate buffer (performance?) + try ( DataOutputStream f = new DataOutputStream(new FileOutputStream(name))) { + P_ArchivePlayers: + P_ArchiveWorld: + P_ArchiveThinkers: + P_ArchiveSpecials: + { + boolean ok = dsg.doSave(f); + } + } + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failure saving game.", e); + } + // Saving is not as destructive as loading. + + gameaction = ga_nothing; + savedescription = ""; + + players[consoleplayer].message = GGSAVED; + + // draw the pattern into the back screen + R_FillBackScreen: + { + sceneRenderer.FillBackScreen(); + } + } + + skill_t d_skill; + int d_episode; + int d_map; + + public void DeferedInitNew(skill_t skill, int episode, int map) { + d_skill = skill; + d_episode = episode; + d_map = map; + gameaction = ga_newgame; + } + + @SourceCode.Exact + @G_Game.C(G_DoNewGame) + public void DoNewGame() { + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = false; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + G_InitNew: + { + InitNew(d_skill, d_episode, d_map); + } + gameaction = ga_nothing; + } + + /** + * G_InitNew + * Can be called by the startup code or the menu task, + * consoleplayer, displayplayer, playeringame[] should be set. + */ + @SourceCode.Compatible + @G_Game.C(G_InitNew) + public void InitNew(skill_t skill, int episode, int map) { + InitNew(skill, episode, map, false); + } + + private void InitNew(skill_t skill, int episode, int map, boolean noSwitchRandom) { + if (paused) { + paused = false; + S_ResumeSound: + { + doomSound.ResumeSound(); + } + } + + if (skill.ordinal() > skill_t.sk_nightmare.ordinal()) { + skill = skill_t.sk_nightmare; + } + + // This was quite messy with SPECIAL and commented parts. + // Supposedly hacks to make the latest edition work. + // It might not work properly. + if (episode < 1) { + episode = 1; + } + + if (isRetail()) { + if (episode > 4) { + episode = 4; + } + } else if (isShareware()) { + if (episode > 1) { + episode = 1; // only start episode 1 on shareware + } + } else { + if (episode > 3) { + episode = 3; + } + } + + if (map < 1) { + map = 1; + } + + if ((map > 9) && (!isCommercial())) { + map = 9; + } + + /** + * I wrote it that way. No worries JavaRandom will never be picked on vanilla demo playback + * - Good Sign 2017/05/08 + * + * @SourceCode.Compatible + */ + if (!noSwitchRandom) { + if (cVarManager.bool(CommandVariable.JAVARANDOM)) { + random.requireRandom(VERSION | JAVARANDOM_MASK); + } else { + random.requireRandom(VERSION); + } + } + + M_ClearRandom: + { + random.ClearRandom(); + } + + respawnmonsters = skill == skill_t.sk_nightmare || respawnparm; + + // If on nightmare/fast monsters make everything MOAR pimp. + if (fastparm || (skill == skill_t.sk_nightmare && gameskill != skill_t.sk_nightmare)) { + for (int i = statenum_t.S_SARG_RUN1.ordinal(); i <= statenum_t.S_SARG_PAIN2.ordinal(); i++) { + states[i].tics >>= 1; + } + + mobjinfo[mobjtype_t.MT_BRUISERSHOT.ordinal()].speed = 20 * MAPFRACUNIT; + mobjinfo[mobjtype_t.MT_HEADSHOT.ordinal()].speed = 20 * MAPFRACUNIT; + mobjinfo[mobjtype_t.MT_TROOPSHOT.ordinal()].speed = 20 * MAPFRACUNIT; + } else if (skill != skill_t.sk_nightmare && gameskill == skill_t.sk_nightmare) { + for (int i = statenum_t.S_SARG_RUN1.ordinal(); i <= statenum_t.S_SARG_PAIN2.ordinal(); i++) { + states[i].tics <<= 1; + } + + mobjinfo[mobjtype_t.MT_BRUISERSHOT.ordinal()].speed = 15 * MAPFRACUNIT; + mobjinfo[mobjtype_t.MT_HEADSHOT.ordinal()].speed = 10 * MAPFRACUNIT; + mobjinfo[mobjtype_t.MT_TROOPSHOT.ordinal()].speed = 10 * MAPFRACUNIT; + } + + // force players to be initialized upon first level load + for (int i = 0; i < MAXPLAYERS; i++) { + players[i].playerstate = PST_REBORN; + } + + // will be set false if a demo + usergame = true; + paused = false; + demoplayback = false; + automapactive = false; + viewactive = true; + gameepisode = episode; + gamemap = map; + gameskill = skill; + viewactive = true; + + // set the sky map for the episode + if (isCommercial()) { + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY3")); + if (gamemap < 12) { + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY1")); + } else if (gamemap < 21) { + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY2")); + } + } else { + switch (episode) { + case 1: + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY1")); + break; + case 2: + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY2")); + break; + case 3: + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY3")); + break; + case 4: // Special Edition sky + textureManager.setSkyTexture(textureManager.TextureNumForName("SKY4")); + break; + default: + break; + } + } + + G_DoLoadLevel: + { + if (!DoLoadLevel()) { + levelLoadFailure(); + } + } + } + + protected void levelLoadFailure() { + boolean endgame = doomSystem.GenerateAlert(Strings.LEVEL_FAILURE_TITLE, Strings.LEVEL_FAILURE_CAUSE, true); + + // Initiate endgame + if (endgame) { + gameaction = ga_failure; + gamestate = GS_DEMOSCREEN; + menu.ClearMenus(); + StartTitle(); + } else { + // Shutdown immediately. + doomSystem.Quit(); + } + } + + // + // DEMO RECORDING + // + public void ReadDemoTiccmd(ticcmd_t cmd) { + final IDemoTicCmd democmd = demobuffer.getNextTic(); + if (democmd == null) { + // end of demo data stream + CheckDemoStatus(); + + // Force status resetting + this.demobuffer.resetDemo(); + return; + } + + democmd.decode(cmd); + } + + public void WriteDemoTiccmd(ticcmd_t cmd) { + // press q to end demo recording + if (gamekeydown[key_recordstop]) { + CheckDemoStatus(); + } + + final IDemoTicCmd reccmd = new VanillaTiccmd(); + reccmd.encode(cmd); + demobuffer.putTic(reccmd); + + // MAES: Useless, we can't run out of space anymore (at least not in theory). + + /* demobuffer[demo_p++] = cmd.forwardmove; + demobuffer[demo_p++] = cmd.sidemove; + demobuffer[demo_p++] = (byte) ((cmd.angleturn+128)>>8); + demobuffer[demo_p++] = (byte) cmd.buttons; + demo_p -= 4; + if (demo_p > demoend - 16) + { + // no more space + CheckDemoStatus (); + return; + } */ + //ReadDemoTiccmd (cmd); // make SURE it is exactly the same + // MAES: this is NOT the way to do in Mocha, because we are not manipulating + // the demo index directly anymore. Instead, decode what we have just saved. + reccmd.decode(cmd); + } + + /** + * G_RecordDemo + */ + public void RecordDemo(String name) { + StringBuffer buf = new StringBuffer(); + usergame = false; + buf.append(name); + buf.append(".lmp"); + demoname = buf.toString(); + demobuffer = new VanillaDoomDemo(); + demorecording = true; + } + + @G_Game.C(G_BeginRecording) + public void BeginRecording() { + demobuffer.setVersion(cVarManager.bool(CommandVariable.JAVARANDOM) ? VERSION | JAVARANDOM_MASK : VERSION); + demobuffer.setSkill(gameskill); + demobuffer.setEpisode(gameepisode); + demobuffer.setMap(gamemap); + demobuffer.setDeathmatch(deathmatch); + demobuffer.setRespawnparm(respawnparm); + demobuffer.setFastparm(fastparm); + demobuffer.setNomonsters(nomonsters); + demobuffer.setConsoleplayer(consoleplayer); + demobuffer.setPlayeringame(playeringame); + } + + String defdemoname; + + /** + * G_PlayDemo + */ + public void DeferedPlayDemo(String name) { + defdemoname = name; + gameaction = ga_playdemo; + } + + @SuppressWarnings("UnusedAssignment") + @SourceCode.Compatible + @G_Game.C(G_DoPlayDemo) + public void DoPlayDemo() { + + skill_t skill; + boolean fail; + int i, episode, map; + + gameaction = ga_nothing; + // MAES: Yeah, it's OO all the way now, baby ;-) + W_CacheLumpName: + { + try { + demobuffer = wadLoader.CacheLumpName(defdemoname.toUpperCase(), PU_STATIC, VanillaDoomDemo.class); + } catch (Exception e) { + fail = true; + } + } + + fail = (demobuffer.getSkill() == null); + + final int version; + if (fail || ((version = demobuffer.getVersion() & 0xFF) & ~JAVARANDOM_MASK) != VERSION) { + LOGGER.log(Level.WARNING, String.format("Demo is from a different game version, version code read: %d", + demobuffer.getVersion())); + gameaction = ga_nothing; + return; + } + + random.requireRandom(version); + + skill = demobuffer.getSkill(); + episode = demobuffer.getEpisode(); + map = demobuffer.getMap(); + deathmatch = demobuffer.isDeathmatch(); + respawnparm = demobuffer.isRespawnparm(); + fastparm = demobuffer.isFastparm(); + nomonsters = demobuffer.isNomonsters(); + consoleplayer = demobuffer.getConsoleplayer(); + // Do this, otherwise previously loaded demos will be stuck at their end. + demobuffer.resetDemo(); + + boolean[] pigs = demobuffer.getPlayeringame(); + for (i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = pigs[i]; + } + if (playeringame[1]) { + netgame = true; + netdemo = true; + } + + // don't spend a lot of time in loadlevel + precache = false; + G_InitNew: + { + InitNew(skill, episode, map, true); + } + precache = true; + + usergame = false; + demoplayback = true; + + } + + // + // G_TimeDemo + // + public void TimeDemo(String name) { + nodrawers = cVarManager.bool(CommandVariable.NODRAW); + noblit = cVarManager.bool(CommandVariable.NOBLIT); + timingdemo = true; + singletics = true; + defdemoname = name; + gameaction = ga_playdemo; + } + + /** G_CheckDemoStatus + * + * Called after a death or level completion to allow demos to be cleaned up + * Returns true if a new demo loop action will take place + * + */ + public boolean CheckDemoStatus() { + int endtime; + + if (timingdemo) { + endtime = RealTime.GetTime(); + // killough -- added fps information and made it work for longer demos: + long realtics = endtime - starttime; + + this.commit(); + CM.SaveDefaults(); + doomSystem.Error("timed %d gametics in %d realtics = %f frames per second", gametic, + realtics, gametic * (double) (TICRATE) / realtics); + } + + if (demoplayback) { + if (singledemo) { + doomSystem.Quit(); + } + + // Z_ChangeTag (demobuffer, PU_CACHE); + demoplayback = false; + netdemo = false; + netgame = false; + deathmatch = false; + playeringame[1] = playeringame[2] = playeringame[3] = false; + respawnparm = false; + fastparm = false; + nomonsters = false; + consoleplayer = 0; + AdvanceDemo(); + return true; + } + + if (demorecording) { + //demobuffer[demo_p++] = (byte) DEMOMARKER; + + MenuMisc.WriteFile(demoname, demobuffer); + //Z_Free (demobuffer); + demorecording = false; + doomSystem.Error("Demo %s recorded", demoname); + } + + return false; + } + + /** This should always be available for real timing */ + protected ITicker RealTime; + + // Bookkeeping on players - state. + public player_t[] players; + + public DelegateRandom random; + public final CVarManager cVarManager; + public final IWadLoader wadLoader; + public final IDoomSound doomSound; + public final ISoundDriver soundDriver; + public final IMusic music; + public final AbstractStatusBar statusBar; + public final DoomGraphicSystem graphicSystem; + public final DoomSystemNetworking systemNetworking; + public final IDoomGameNetworking gameNetworking; + public final AbstractLevelLoader levelLoader; + public final IDoomMenu menu; + public final ActionFunctions actions; + public final SceneRenderer sceneRenderer; + public final HU headsUp; + public final IAutoMap autoMap; + public final Finale finale; + public final EndLevel endLevel; + public final Wiper wiper; + public final TextureManager textureManager; + public final ISpriteManager spriteManager; + public final ITicker ticker; + public final IDiskDrawer diskDrawer; + public final IDoomSystem doomSystem; + public final BppMode bppMode; + + /** + * Since this is a fully OO implementation, we need a way to create + * the instances of the Refresh daemon, the Playloop, the Wadloader + * etc. which however are now completely independent of each other + * (well, ALMOST), and are typically only passed context when + * instantiated. + * + * If you instantiate one too early, it will have null context. + * + * The trick is to construct objects in the correct order. Some of + * them have Init() methods which are NOT yet safe to call. + * + */ + @SuppressWarnings("LeakingThisInConstructor") + public DoomMain() throws IOException { + // Init game status... + super(); + + // Init players + players = new player_t[MAXPLAYERS]; + Arrays.setAll(players, i -> new player_t(this)); + + // Init objects + this.cVarManager = Engine.getCVM(); + + // Prepare events array with event instances + Arrays.fill(events, event_t.EMPTY_EVENT); + + // Create DoomSystem + this.doomSystem = new DoomSystem(this); + + // Choose bppMode depending on CVar's + // TODO: add config options + this.bppMode = BppMode.chooseBppMode(cVarManager); + + // Create real time ticker + this.RealTime = new MilliTicker(); + + // Doommain is both "main" and handles most of the game status. + this.gameNetworking = this; // DoomMain also handles its own Game Networking. + + // Set ticker. It is a shared status object, but not a holder itself. + this.ticker = ITicker.createTicker(cVarManager); + + // Network "driver" + this.systemNetworking = new DummyNetworkDriver<>(this); + + // Random number generator, but we can have others too. + this.random = new DelegateRandom(); + LOGGER.log(Level.INFO, String.format("M_Random: Using %s.", random.getClass().getSimpleName())); + + // Sound can be left until later, in Start + this.wadLoader = new WadLoader(this.doomSystem); // The wadloader is a "weak" status holder. + + // TODO: find out if we have requests for a specific resolution, + // and try honouring them as closely as possible. + // 23/5/2011: Experimental dynamic resolution subsystem + this.vs = VisualSettings.parse(cVarManager, CM); + this.spriteManager = new SpriteManager<>(this); + + // Heads-up, Menu, Level Loader + this.headsUp = new HU(this); + this.menu = new Menu<>(this); + this.levelLoader = new BoomLevelLoader(this); + + // Renderer, Actions, StatusBar, AutoMap + this.sceneRenderer = bppMode.sceneRenderer(this); + this.actions = new ActionFunctions(this); + this.statusBar = new StatusBar(this); + + // Let the renderer pick its own. It makes linking easier. + this.textureManager = sceneRenderer.getTextureManager(); + // Instantiating EndLevel, Finale + this.endLevel = new EndLevel<>(this); + this.finale = selectFinale(); + + readCVars(); + LOGGER.log(Level.INFO, String.format("W_Init: Init WAD files: [%s]", + Arrays.stream(wadfiles).filter(Objects::nonNull).collect(Collectors.joining(", ")))); + try { + wadLoader.InitMultipleFiles(wadfiles); + } catch (Exception e1) { + LOGGER.log(Level.SEVERE, "Could not init WAD files", e1); + } + + // Video Renderer + this.graphicSystem = RendererFactory.newBuilder() + .setVideoScale(vs).setBppMode(bppMode).setWadLoader(wadLoader) + .build(); + + LOGGER.log(Level.INFO, "V_Init: Allocate screens."); + + // Disk access visualizer + this.diskDrawer = new DiskDrawer(this, DiskDrawer.STDISK); + + // init subsystems + LOGGER.log(Level.INFO, "AM_Init: Init Automap colors."); + this.autoMap = new automap.Map<>(this); + + this.wiper = graphicSystem.createWiper(random); + + // Update variables and stuff NOW. + this.update(); + + // Check for -file in shareware + CheckForPWADSInShareware(); + + printGameInfo(); + + LOGGER.log(Level.INFO, "Tables.InitTables: Init trigonometric LUTs."); + Tables.InitTables(); + + LOGGER.log(Level.INFO, "M_Init: Init miscellaneous info."); + menu.Init(); + + LOGGER.log(Level.INFO, "R_Init: Init DOOM refresh daemon."); + sceneRenderer.Init(); + + LOGGER.log(Level.INFO, "P_Init: Init Playloop state."); + actions.Init(); + + LOGGER.log(Level.INFO, "I_Init: Setting up machine state."); + doomSystem.Init(); + + LOGGER.log(Level.INFO, "D_CheckNetGame: Checking network game status."); + CheckNetGame(); + + LOGGER.log(Level.INFO, "S_Init: Setting up sound."); + // Sound "drivers" before the game sound controller. + this.music = IMusic.chooseModule(cVarManager); + this.soundDriver = ISoundDriver.chooseModule(this, cVarManager); + this.doomSound = IDoomSound.chooseSoundIsPresent(this, cVarManager, soundDriver); + + music.InitMusic(); + doomSound.Init(snd_SfxVolume * 8, snd_MusicVolume * 8); + + LOGGER.log(Level.INFO, "HU_Init: Setting up heads up display."); + headsUp.Init(); + + LOGGER.log(Level.INFO, "ST_Init: Init status bar."); + statusBar.Init(); + + if (statcopy != null) { + LOGGER.log(Level.INFO, "External statistics registered."); + } + + // NOW it's safe to init the disk reader. + diskDrawer.Init(); + } + + @Override + public final void update() { + super.update(); + // Video...so you should wait until video renderer is active. + this.graphicSystem.setUsegamma(CM.getValue(Settings.usegamma, Integer.class)); + + // These should really be handled by the menu. + this.menu.setShowMessages(CM.equals(Settings.show_messages, 1)); + this.menu.setScreenBlocks(CM.getValue(Settings.screenblocks, Integer.class)); + + // These should be handled by the HU + for (int i = 0; i <= 9; i++) { + + String chatmacro = String.format("chatmacro%d", i); + this.headsUp.setChatMacro(i, CM.getValue(Settings.valueOf(chatmacro), String.class)); + } + } + + @Override + public final void commit() { + super.commit(); + // Video... + CM.update(Settings.usegamma, graphicSystem.getUsegamma()); + + // These should really be handled by the menu. + CM.update(Settings.show_messages, this.menu.getShowMessages()); + CM.update(Settings.screenblocks, this.menu.getScreenBlocks()); + + // These should be handled by the HU + for (int i = 0; i <= 9; i++) { + CM.update(Settings.valueOf(String.format("chatmacro%d", i)), this.headsUp.chat_macros[i]); + } + } + + public void setupLoop() throws IOException { + // check for a driver that wants intermission stats + cVarManager.with(CommandVariable.STATCOPY, 0, (String s) -> { + // TODO: this should be chained to a logger + statcopy = s; + LOGGER.log(Level.INFO, "External statistics registered."); + }); + + // start the apropriate game based on parms + cVarManager.with(CommandVariable.RECORD, 0, (String s) -> { + RecordDemo(s); + autostart = true; + }); + + //p = CM.CheckParm ("-timedemo"); + ChooseLoop: + { + if (singletics) { + TimeDemo(loaddemo); + autostart = true; + break ChooseLoop; // DoomLoop(); // never returns + } + + if (fastdemo || normaldemo) { + singledemo = true; // quit after one demo + if (fastdemo) { + timingdemo = true; + } + InitNew(startskill, startepisode, startmap); + gamestate = GS_DEMOSCREEN; + DeferedPlayDemo(loaddemo); + break ChooseLoop; // DoomLoop(); // never returns + } + + if (gameaction != ga_loadgame) { + if (autostart || netgame) { + InitNew(startskill, startepisode, startmap); + } else { + StartTitle(); // start up intro loop + } + } + } + + DoomLoop(); // never returns + } + + private void printGameInfo() { + // Iff additonal PWAD files are used, print modified banner + if (modifiedgame) // Generate WAD loading alert. Abort upon denial. + { + if (!doomSystem.GenerateAlert(Strings.MODIFIED_GAME_TITLE, Strings.MODIFIED_GAME_DIALOG, true)) { + wadLoader.CloseAllHandles(); + System.exit(-2); + } + } + + // Check and print which version is executed. + switch (getGameMode()) { + case shareware: + case indetermined: + LOGGER.log(Level.INFO, "Game Info: Shareware!"); + break; + case registered: + case retail: + case commercial: + case pack_tnt: + case pack_plut: + case pack_xbla: + LOGGER.log(Level.INFO, "Game Info: Commercial product - do not distribute!"); + LOGGER.log(Level.INFO, "Game Note: Please report software piracy to the SPA: 1-800-388-PIR8"); + break; + case freedoom1: + case freedoom2: + case freedm: + LOGGER.log(Level.INFO, "Game Info: Copyright (c) 2001-2017 Contributors to the Freedoom project. All rights reserved."); + LOGGER.log(Level.INFO, "Game Note: See https://github.com/freedoom/freedoom/blob/master/COPYING.adoc"); + break; + default: + // Ouch. + break; + } + } + + private void readCVars() { + /** + * D_DoomMain + * + * Completes the job started by Init. Here everything priority-critical is called and created in more detail. + */ + + final StringBuffer file = new StringBuffer(); + final String iwadfilename = IdentifyVersion(); + nomonsters = cVarManager.bool(CommandVariable.NOMONSTERS); + respawnparm = cVarManager.bool(CommandVariable.RESPAWN); + fastparm = cVarManager.bool(CommandVariable.FAST); + devparm = cVarManager.bool(CommandVariable.DEVPARM); + + if (!(altdeath = cVarManager.bool(CommandVariable.ALTDEATH))) { + deathmatch = cVarManager.bool(CommandVariable.DEATHMATCH); + } + + // MAES: Check for Ultimate Doom in "doom.wad" filename. + final WadLoader tmpwad = new WadLoader(); + try { + tmpwad.InitFile(iwadfilename); + // Check using a reloadable hack. + CheckForUltimateDoom(tmpwad); + } catch (Exception e2) { + // TODO Auto-generated catch block + LOGGER.log(Level.SEVERE, "Failure reading CVars.", e2); + } finally { + tmpwad.CloseAllHandles(); + } + + // MAES: better extract a method for this. + GenerateTitle(); + // Print ticker info. It has already been set at Init() though. + if (cVarManager.bool(CommandVariable.MILLIS)) { + LOGGER.log(Level.INFO, "ITicker: Using millisecond accuracy timer."); + } else if (cVarManager.bool(CommandVariable.FASTTIC)) { + LOGGER.log(Level.INFO, "ITicker: Using fastest possible timer."); + } else { + LOGGER.log(Level.INFO, "ITicker: Using nanosecond accuracy timer."); + } + LOGGER.log(Level.INFO, title.toString()); + if (devparm) { + LOGGER.log(Level.INFO, D_DEVSTR); + } + // Running from CDROM? + if (cVarManager.bool(CommandVariable.CDROM)) { + LOGGER.log(Level.INFO, D_CDROM); + //System.get("c:\\doomdata",0); + //System.out.println (Settings.basedefault+"c:/doomdata/default.cfg"); + } + // turbo option + if (cVarManager.specified(CommandVariable.TURBO)) { + int scale = 200; + if (cVarManager.present(CommandVariable.TURBO)) { + scale = cVarManager.get(CommandVariable.TURBO, Integer.class, 0).get(); + } + if (scale < 10) { + scale = 10; + } + if (scale > 400) { + scale = 400; + } + LOGGER.log(Level.INFO, String.format("turbo scale: %d", scale)); + forwardmove[0] = forwardmove[0] * scale / 100; + forwardmove[1] = forwardmove[1] * scale / 100; + sidemove[0] = sidemove[0] * scale / 100; + sidemove[1] = sidemove[1] * scale / 100; + } + // add any files specified on the command line with -file wadfile + // to the wad list + // + // convenience hack to allow -wart e m to add a wad file + // prepend a tilde to the filename so wadfile will be reloadable + if (cVarManager.present(CommandVariable.WART)) { + final int ep = cVarManager.get(CommandVariable.WART, Integer.class, 0).get(); + final int map = cVarManager.get(CommandVariable.WART, Integer.class, 1).get(); + cVarManager.override(CommandVariable.WARP, new CommandVariable.WarpFormat(ep * 10 + map), 0); + GameMode gamemode = getGameMode(); + // Map name handling. + switch (gamemode) { + case shareware: + case retail: + case registered: + case freedoom1: + file.append("~"); + file.append(DEVMAPS); + file.append(String.format("E%dM%d.wad", ep, map)); + file.append(String.format("Warping to Episode %s, Map %s.\n", ep, map)); + break; + case commercial: + case freedoom2: + case freedm: + default: + if (ep < 10) { + file.append("~"); + file.append(DEVMAPS); + file.append(String.format("cdata/map0%d.wad", ep)); + } else { + file.append("~"); + file.append(DEVMAPS); + file.append(String.format("cdata/map%d.wad", ep)); + } + break; + } + AddFile(file.toString()); + } + + if (cVarManager.present(CommandVariable.FILE)) { + // the parms after p are wadfile/lump names, + // until end of parms or another - preceded parm + modifiedgame = true; // homebrew levels + cVarManager.with(CommandVariable.FILE, 0, (String[] a) -> { + Arrays.stream(a) + .map(s -> C2JUtils.unquoteIfQuoted(s, '"')) + .forEach(this::AddFile); + }); + } + + if (cVarManager.present(CommandVariable.PLAYDEMO)) { + normaldemo = true; + loaddemo = cVarManager.get(CommandVariable.PLAYDEMO, String.class, 0).get(); + } else if (cVarManager.present(CommandVariable.FASTDEMO)) { + LOGGER.log(Level.INFO, "Fastdemo mode. Boundless clock!"); + fastdemo = true; + loaddemo = cVarManager.get(CommandVariable.FASTDEMO, String.class, 0).get(); + } else if (cVarManager.present(CommandVariable.TIMEDEMO)) { + singletics = true; + loaddemo = cVarManager.get(CommandVariable.TIMEDEMO, String.class, 0).get(); + } + + // If any of the previous succeeded, try grabbing the filename. + if (loaddemo != null) { + loaddemo = C2JUtils.unquoteIfQuoted(loaddemo, '"'); + AddFile(loaddemo + ".lmp"); + LOGGER.log(Level.INFO, String.format("Playing demo %s.lmp.", loaddemo)); + autostart = true; + } + + // Subsequent uses of loaddemo use only the lump name. + loaddemo = C2JUtils.extractFileBase(loaddemo, 0, true); + // get skill / episode / map from parms + // FIXME: should get them FROM THE DEMO itself. + startskill = skill_t.sk_medium; + startepisode = 1; + startmap = 1; + //autostart = false; + + if (cVarManager.present(CommandVariable.NOVERT)) { + novert = cVarManager.get(CommandVariable.NOVERT, CommandVariable.ForbidFormat.class, 0) + .filter(CommandVariable.ForbidFormat.FORBID::equals) + .isPresent(); + + if (!novert) { + LOGGER.log(Level.INFO, "-novert ENABLED (default)"); + } else { + LOGGER.log(Level.INFO, "-novert DISABLED. Hope you know what you're doing..."); + } + } + + cVarManager.with(CommandVariable.SKILL, 0, (Integer s) -> { + startskill = skill_t.values()[s - 1]; + autostart = true; + }); + + cVarManager.with(CommandVariable.EPISODE, 0, (Integer ep) -> { + startepisode = ep; + startmap = 1; + autostart = true; + }); + + if (cVarManager.present(CommandVariable.TIMER) && deathmatch) { + // Good Sign (2017/03/31) How this should work? + final int time = cVarManager.get(CommandVariable.TIMER, Integer.class, 0).get(); + LOGGER.log(Level.INFO, String.format("Levels will end after %d minute(s)", time)); + } + // OK, and exactly how is this enforced? + if (cVarManager.bool(CommandVariable.AVG) && deathmatch) { + LOGGER.log(Level.INFO, "Austin Virtual Gaming: Levels will end after 20 minutes"); + } + + // MAES 31/5/2011: added support for +map variation. + cVarManager.with(CommandVariable.WARP, 0, (CommandVariable.WarpFormat w) -> { + final CommandVariable.WarpMetric metric = w.getMetric(isCommercial()); + startepisode = metric.getEpisode(); + startmap = metric.getMap(); + autostart = true; + }); + + // Maes: 1/6/2011 Added +map support + cVarManager.with(CommandVariable.MAP, 0, (CommandVariable.MapFormat m) -> { + final CommandVariable.WarpMetric metric = m.getMetric(isCommercial()); + startepisode = metric.getEpisode(); + startmap = metric.getMap(); + autostart = true; + }); + + cVarManager.with(CommandVariable.LOADGAME, 0, (Character c) -> { + file.delete(0, file.length()); + if (cVarManager.bool(CommandVariable.CDROM)) { + file.append("c:\\doomdata\\"); + } + + file.append(String.format("%s%d.dsg", SAVEGAMENAME, c)); + LoadGame(file.toString()); + }); + } + + /** + * Since it's so intimately tied, it's less troublesome to merge the "main" and "network" code. + */ + /** To be initialized by the DoomNetworkingInterface via a setter */ + //private doomcom_t doomcom; + //private doomdata_t netbuffer; // points inside doomcom + protected StringBuilder sb = new StringBuilder(); + + // + // NETWORKING + // + // gametic is the tic about to (or currently being) run + // maketic is the tick that hasn't had control made for it yet + // nettics[] has the maketics for all players + // + // a gametic cannot be run until nettics[] > gametic for all players + // + //ticcmd_t[] localcmds= new ticcmd_t[BACKUPTICS]; + //ticcmd_t [][] netcmds=new ticcmd_t [MAXPLAYERS][BACKUPTICS]; + int[] nettics = new int[MAXNETNODES]; + boolean[] nodeingame = new boolean[MAXNETNODES]; // set false as nodes leave game + boolean[] remoteresend = new boolean[MAXNETNODES]; // set when local needs tics + int[] resendto = new int[MAXNETNODES]; // set when remote needs tics + int[] resendcount = new int[MAXNETNODES]; + + int[] nodeforplayer = new int[MAXPLAYERS]; + + int maketic; + int lastnettic; + int skiptics; + protected int ticdup; + + public int getTicdup() { + return ticdup; + } + + public void setTicdup(int ticdup) { + this.ticdup = ticdup; + } + + int maxsend; // BACKUPTICS/(2*ticdup)-1; + + //void D_ProcessEvents (void); + //void G_BuildTiccmd (ticcmd_t *cmd); + //void D_DoAdvanceDemo (void); + // _D_ + boolean reboundpacket = false; + doomdata_t reboundstore = new doomdata_t(); + + /** + * MAES: interesting. After testing it was found to return the following size: + * (8*(netbuffer.numtics+1)); + */ + int NetbufferSize() { + // return (int)(((doomdata_t)0).cmds[netbuffer.numtics]); + return (8 * (netbuffer.numtics + 1)); + } + + protected long NetbufferChecksum() { + // FIXME -endianess? + if (NORMALUNIX) { + return 0; // byte order problems + } + + /** + * Here it was trying to get the length of a doomdata_t struct up to retransmit from. + * l = (NetbufferSize () - (int)&(((doomdata_t *)0)->retransmitfrom))/4; + * (int)&(((doomdata_t *)0)->retransmitfrom) evaluates to "4" + * Therefore, l= (netbuffersize - 4)/4 + */ + final int l = (NetbufferSize() - 4) / 4; + long c = 0x1234567L; + for (int i = 0; i < l; i++) { // TODO: checksum would be better computer in the netbuffer itself. + // The C code actually takes all fields into account. + c += 0;// TODO: (netbuffer->retransmitfrom)[i] * (i+1); + } + return c & NCMD_CHECKSUM; + } + + protected int ExpandTics(int low) { + int delta; + + delta = low - (maketic & 0xff); + + if (delta >= -64 && delta <= 64) { + return (maketic & ~0xff) + low; + } + + if (delta > 64) { + return (maketic & ~0xff) - 256 + low; + } + + if (delta < -64) { + return (maketic & ~0xff) + 256 + low; + } + + doomSystem.Error("ExpandTics: strange value %d at maketic %d", low, maketic); + return 0; + } + + /** + * HSendPacket + * + * Will send out a packet to all involved parties. A special case is the rebound storage, which acts as a local + * "echo" which is then picked up by the host itself. This is necessary to simulate a 1-node network. + * + * @throws IOException + */ + void HSendPacket(int node, int flags) { + netbuffer.checksum = (int) (NetbufferChecksum() | flags); + + // Local node's comms are sent to rebound packet, which is + // then picked up again. THIS IS VITAL FOR SINGLE-PLAYER + // SPEED THROTTLING TOO, AS IT RELIES ON NETWORK ACKS/BUSY + // WAITING. + if (node == 0) { + // _D_ + reboundstore.copyFrom(netbuffer); + reboundpacket = true; + return; + } + + if (demoplayback) { + return; + } + + if (!netgame) { + doomSystem.Error("Tried to transmit to another node"); + } + + doomcom.command = CMD_SEND; + doomcom.remotenode = (short) node; + doomcom.datalength = (short) NetbufferSize(); + + if (debugfile != null) { + int i; + int realretrans; + if (flags(netbuffer.checksum, NCMD_RETRANSMIT)) { + realretrans = ExpandTics(netbuffer.retransmitfrom); + } else { + realretrans = -1; + } + + logger(debugfile, "send (" + ExpandTics(netbuffer.starttic) + ", " + netbuffer.numtics + ", R " + + realretrans + "[" + doomcom.datalength + "]"); + + for (i = 0; i < doomcom.datalength; i++) // TODO: get a serialized string representation. + { + logger(debugfile, netbuffer.toString() + "\n"); + } + } + + // This should execute a "send" command for the current stuff in doomcom. + systemNetworking.NetCmd(); + } + + // + // HGetPacket + // Returns false if no packet is waiting + // + private boolean HGetPacket() { + // Fugly way of "clearing" the buffer. + sb.setLength(0); + if (reboundpacket) { + // FIXME: MAES: this looks like a struct copy + netbuffer.copyFrom(reboundstore); + doomcom.remotenode = 0; + reboundpacket = false; + return true; + } + + // If not actually a netgame (e.g. single player, demo) return. + if (!netgame) { + return false; + } + + if (demoplayback) { + return false; + } + + doomcom.command = CMD_GET; + systemNetworking.NetCmd(); + + // Invalid node? + if (doomcom.remotenode == -1) { + return false; + } + + if (doomcom.datalength != NetbufferSize()) { + if (eval(debugfile)) { + logger(debugfile, "bad packet length " + doomcom.datalength + "\n"); + } + return false; + } + + if (NetbufferChecksum() != (netbuffer.checksum & NCMD_CHECKSUM)) { + if (eval(debugfile)) { + logger(debugfile, "bad packet checksum\n"); + } + return false; + } + + if (eval(debugfile)) { + int realretrans; + int i; + + if (flags(netbuffer.checksum, NCMD_SETUP)) { + logger(debugfile, "setup packet\n"); + } else { + if (flags(netbuffer.checksum, NCMD_RETRANSMIT)) { + realretrans = ExpandTics(netbuffer.retransmitfrom); + } else { + realretrans = -1; + } + + sb.append("get "); + sb.append(doomcom.remotenode); + sb.append(" = ("); + sb.append(ExpandTics(netbuffer.starttic)); + sb.append(" + "); + sb.append(netbuffer.numtics); + sb.append(", R "); + sb.append(realretrans); + sb.append(")["); + sb.append(doomcom.datalength); + sb.append("]"); + + logger(debugfile, sb.toString()); + + // Trick: force update of internal buffer. + netbuffer.pack(); + + /** + * TODO: Could it be actually writing stuff beyond the boundaries of a single doomdata object? + * A doomcom object has a lot of header info, and a single "raw" data placeholder, which by now + * should be inside netbuffer....right? + **/ + try { + for (i = 0; i < doomcom.datalength; i++) { + debugfile.write(Integer.toHexString(netbuffer.cached()[i])); + debugfile.write('\n'); + } + } catch (IOException e) { + } // "Drown" IOExceptions here. + } + } + return true; + } + + //// GetPackets + StringBuilder exitmsg = new StringBuilder(80); + + public void GetPackets() { + int netconsole; + int netnode; + ticcmd_t src, dest; + int realend; + int realstart; + + while (HGetPacket()) { + if (flags(netbuffer.checksum, NCMD_SETUP)) { + continue; // extra setup packet + } + netconsole = netbuffer.player & ~PL_DRONE; + netnode = doomcom.remotenode; + + // to save bytes, only the low byte of tic numbers are sent + // Figure out what the rest of the bytes are + realstart = ExpandTics(netbuffer.starttic); + realend = (realstart + netbuffer.numtics); + + // check for exiting the game + if (flags(netbuffer.checksum, NCMD_EXIT)) { + if (!nodeingame[netnode]) { + continue; + } + nodeingame[netnode] = false; + playeringame[netconsole] = false; + exitmsg.insert(0, "Player 1 left the game"); + exitmsg.setCharAt(7, (char) (exitmsg.charAt(7) + netconsole)); + players[consoleplayer].message = exitmsg.toString(); + if (demorecording) { + CheckDemoStatus(); + } + continue; + } + + // check for a remote game kill + if (flags(netbuffer.checksum, NCMD_KILL)) { + doomSystem.Error("Killed by network driver"); + } + + nodeforplayer[netconsole] = netnode; + + // check for retransmit request + if (resendcount[netnode] <= 0 + && flags(netbuffer.checksum, NCMD_RETRANSMIT)) { + resendto[netnode] = ExpandTics(netbuffer.retransmitfrom); + if (eval(debugfile)) { + sb.setLength(0); + sb.append("retransmit from "); + sb.append(resendto[netnode]); + sb.append('\n'); + logger(debugfile, sb.toString()); + resendcount[netnode] = RESENDCOUNT; + } + } else { + resendcount[netnode]--; + } + + // check for out of order / duplicated packet + if (realend == nettics[netnode]) { + continue; + } + + if (realend < nettics[netnode]) { + if (eval(debugfile)) { + sb.setLength(0); + sb.append("out of order packet ("); + sb.append(realstart); + sb.append(" + "); + sb.append(netbuffer.numtics); + sb.append(")\n"); + logger(debugfile, sb.toString()); + } + continue; + } + + // check for a missed packet + if (realstart > nettics[netnode]) { + // stop processing until the other system resends the missed tics + if (eval(debugfile)) { + sb.setLength(0); + sb.append("missed tics from "); + sb.append(netnode); + sb.append(" ("); + sb.append(realstart); + sb.append(" - "); + sb.append(nettics[netnode]); + sb.append(")\n"); + logger(debugfile, sb.toString()); + } + remoteresend[netnode] = true; + continue; + } + + // update command store from the packet + { + int start; + + remoteresend[netnode] = false; + + start = nettics[netnode] - realstart; + src = netbuffer.cmds[start]; + + while (nettics[netnode] < realend) { + dest = netcmds[netconsole][nettics[netnode] % BACKUPTICS]; + nettics[netnode]++; + // MAES: this is a struct copy. + src.copyTo(dest); + // Advance src + start++; + + //_D_: had to add this (see linuxdoom source). That fixed that damn consistency failure!!! + if (start < netbuffer.cmds.length) { + src = netbuffer.cmds[start]; + } + + } + } + } + } + + protected void logger(OutputStreamWriter debugfile, String string) { + try { + debugfile.write(string); + } catch (IOException e) { + // TODO Auto-generated catch block + LOGGER.log(Level.SEVERE, "Failure writing debug file.", e); + } + } + + int gametime; + + @Override + public void NetUpdate() { + int nowtime; + int newtics; + int i, j; + int realstart; + int gameticdiv; + + // check time + nowtime = ticker.GetTime() / ticdup; + newtics = nowtime - gametime; + gametime = nowtime; + + if (newtics <= 0) { // nothing new to update + // listen for other packets + GetPackets(); + } else { + + if (skiptics <= newtics) { + newtics -= skiptics; + skiptics = 0; + } else { + skiptics -= newtics; + newtics = 0; + } + + netbuffer.player = (byte) consoleplayer; + + // build new ticcmds for console player + gameticdiv = gametic / ticdup; + for (i = 0; i < newtics; i++) { + //videoInterface.StartTic(); + ProcessEvents(); + if (maketic - gameticdiv >= BACKUPTICS / 2 - 1) { + break; // can't hold any more + } + //System.out.printf ("mk:%d ",maketic); + BuildTiccmd(localcmds[maketic % BACKUPTICS]); + maketic++; + } + + if (singletics) { + return; // singletic update is syncronous + } + // send the packet to the other nodes + for (i = 0; i < doomcom.numnodes; i++) { + if (nodeingame[i]) { + netbuffer.starttic = (byte) (realstart = resendto[i]); + netbuffer.numtics = (byte) (maketic - realstart); + if (netbuffer.numtics > BACKUPTICS) { + doomSystem.Error("NetUpdate: netbuffer.numtics > BACKUPTICS"); + } + + resendto[i] = maketic - doomcom.extratics; + + for (j = 0; j < netbuffer.numtics; j++) { + localcmds[(realstart + j) % BACKUPTICS].copyTo(netbuffer.cmds[j]); + } + // MAES: one of _D_ fixes. + //netbuffer.cmds[j] = localcmds[(realstart+j)%BACKUPTICS]; + + if (remoteresend[i]) { + netbuffer.retransmitfrom = (byte) nettics[i]; + HSendPacket(i, NCMD_RETRANSMIT); + } else { + netbuffer.retransmitfrom = 0; + HSendPacket(i, 0); + } + } + } + GetPackets(); + } + } + + // + // CheckAbort + // + private void CheckAbort() { + event_t ev; + int stoptic; + + stoptic = ticker.GetTime() + 2; + while (ticker.GetTime() < stoptic) { + } + //videoInterface.StartTic (); + + //videoInterface.StartTic (); + for (; eventtail != eventhead; eventtail = (++eventtail) & (MAXEVENTS - 1)) { + ev = events[eventtail]; + if (ev.isKey(SC_ESCAPE, ev_keydown)) { + doomSystem.Error("Network game synchronization aborted."); + } + } + } + + boolean[] gotinfo = new boolean[MAXNETNODES]; + + /** + * D_ArbitrateNetStart + * @throws IOException + * + * + */ + public void ArbitrateNetStart() throws IOException { + int i; + autostart = true; + + // Clear it up... + memset(gotinfo, false, gotinfo.length); + if (doomcom.consoleplayer != 0) { + // listen for setup info from key player + LOGGER.log(Level.INFO, "listening for network start info..."); + while (true) { + CheckAbort(); + if (!HGetPacket()) { + continue; + } + if (flags(netbuffer.checksum, NCMD_SETUP)) { + if (netbuffer.player != VERSION) { + doomSystem.Error("Different DOOM versions cannot play a net game!"); + } + startskill = skill_t.values()[netbuffer.retransmitfrom & 15]; + + if (((netbuffer.retransmitfrom & 0xc0) >> 6) == 1) { + // Deathmatch + deathmatch = true; + } else if (((netbuffer.retransmitfrom & 0xc0) >> 6) == 2) { + // Cooperative + altdeath = true; + } + + nomonsters = (netbuffer.retransmitfrom & 0x20) > 0; + respawnparm = (netbuffer.retransmitfrom & 0x10) > 0; + startmap = netbuffer.starttic & 0x3f; + startepisode = netbuffer.starttic >> 6; + return; + } + } + } else { + // key player, send the setup info + LOGGER.log(Level.INFO, "sending network start info..."); + do { + CheckAbort(); + for (i = 0; i < doomcom.numnodes; i++) { + netbuffer.retransmitfrom = (byte) startskill.ordinal(); + if (deathmatch) { + netbuffer.retransmitfrom |= (1 << 6); + } else if (altdeath) { + netbuffer.retransmitfrom |= (2 << 6); + } + + if (nomonsters) { + netbuffer.retransmitfrom |= 0x20; + } + + if (respawnparm) { + netbuffer.retransmitfrom |= 0x10; + } + + netbuffer.starttic = (byte) (startepisode * 64 + startmap); + netbuffer.player = VERSION; + netbuffer.numtics = 0; + HSendPacket(i, NCMD_SETUP); + } + + //#if 1 + for (i = 10; (i > 0) && HGetPacket(); --i) { + if ((netbuffer.player & 0x7f) < MAXNETNODES) { + gotinfo[netbuffer.player & 0x7f] = true; + } + } + /* + while (HGetPacket ()) + { + gotinfo[netbuffer.player&0x7f] = true; + } + */ + + for (i = 1; i < doomcom.numnodes; i++) { + if (!gotinfo[i]) { + break; + } + } + } while (i < doomcom.numnodes); + } + } + + /** + * D_CheckNetGame + * Works out player numbers among the net participants + **/ + private void CheckNetGame() throws IOException { + for (int i = 0; i < MAXNETNODES; i++) { + nodeingame[i] = false; + nettics[i] = 0; + remoteresend[i] = false; // set when local needs tics + resendto[i] = 0; // which tic to start sending + } + + // I_InitNetwork sets doomcom and netgame + systemNetworking.InitNetwork(); + if (doomcom.id != DOOMCOM_ID) { + doomSystem.Error("Doomcom buffer invalid!"); + } + + // Maes: This is the only place where netbuffer is definitively set to something + netbuffer = doomcom.data; + consoleplayer = displayplayer = doomcom.consoleplayer; + if (netgame) { + ArbitrateNetStart(); + } + + LOGGER.log(Level.FINE, String.format("startskill %s, deathmatch: %s, startmap: %d, startepisode: %d", + startskill.toString(), Boolean.toString(deathmatch), startmap, startepisode)); + + // read values out of doomcom + ticdup = doomcom.ticdup; + // MAES: ticdup must not be zero at this point. Obvious, no? + maxsend = BACKUPTICS / (2 * ticdup) - 1; + if (maxsend < 1) { + maxsend = 1; + } + + for (int i = 0; i < doomcom.numplayers; i++) { + playeringame[i] = true; + } + + for (int i = 0; i < doomcom.numnodes; i++) { + nodeingame[i] = true; + } + + LOGGER.log(Level.INFO, String.format("Player %d of %d (%d node(s))", (consoleplayer + 1), doomcom.numplayers, doomcom.numnodes)); + } + + /** + * D_QuitNetGame + * Called before quitting to leave a net game + * without hanging the other players + **/ + @Override + public void QuitNetGame() throws IOException { + if (eval(debugfile)) { + try { + debugfile.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Quit net game failure.", e); + } + } + + if (!netgame || !usergame || consoleplayer == -1 || demoplayback) { + return; + } + + // send a bunch of packets for security + netbuffer.player = (byte) consoleplayer; + netbuffer.numtics = 0; + for (int i = 0; i < 4; i++) { + for (int j = 1; j < doomcom.numnodes; j++) { + if (nodeingame[j]) { + HSendPacket(j, NCMD_EXIT); + } + } + doomSystem.WaitVBL(1); + } + } + + /** + * TryRunTics + **/ + int[] frametics = new int[4]; + int frameon; + boolean[] frameskip = new boolean[4]; + int oldnettics; + int oldentertics; + + @Override + public void TryRunTics() throws IOException { + int i; + int lowtic; + int entertic; + + int realtics; + int availabletics; + int counts; + int numplaying; + + // get real tics + entertic = ticker.GetTime() / ticdup; + realtics = entertic - oldentertics; + oldentertics = entertic; + + //System.out.printf("Entertic %d, realtics %d, oldentertics %d\n",entertic,realtics,oldentertics); + // get available tics + NetUpdate(); + + lowtic = MAXINT; + numplaying = 0; + for (i = 0; i < doomcom.numnodes; i++) { + if (nodeingame[i]) { + numplaying++; + if (nettics[i] < lowtic) { + lowtic = nettics[i]; + } + } + } + availabletics = lowtic - gametic / ticdup; + + // decide how many tics to run + if (realtics < availabletics - 1) { + counts = realtics + 1; + } else if (realtics < availabletics) { + counts = realtics; + } else { + counts = availabletics; + } + + if (counts < 1) { + counts = 1; + } + + frameon++; + + if (eval(debugfile)) { + sb.setLength(0); + sb.append("=======real: "); + sb.append(realtics); + sb.append(" avail: "); + sb.append(availabletics); + sb.append(" game: "); + sb.append(counts); + sb.append("\n"); + debugfile.write(sb.toString()); + } + + if (!demoplayback) { + // ideally nettics[0] should be 1 - 3 tics above lowtic + // if we are consistantly slower, speed up time + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i]) { + break; + } + } + if (consoleplayer == i) { + // the key player does not adapt + } else { + if (nettics[0] <= nettics[nodeforplayer[i]]) { + gametime--; + //System.out.print("-"); + } + frameskip[frameon & 3] = oldnettics > nettics[nodeforplayer[i]]; + oldnettics = nettics[0]; + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) { + skiptics = 1; + //System.out.print("+"); + } + } + } // demoplayback + + // wait for new tics if needed + while (lowtic < gametic / ticdup + counts) { + NetUpdate(); + lowtic = MAXINT; + + // Finds the node with the lowest number of tics. + for (i = 0; i < doomcom.numnodes; i++) { + if (nodeingame[i] && nettics[i] < lowtic) { + lowtic = nettics[i]; + } + } + + if (lowtic < gametic / ticdup) { + doomSystem.Error("TryRunTics: lowtic < gametic"); + } + + // don't stay in here forever -- give the menu a chance to work + int time = ticker.GetTime(); + if (time / ticdup - entertic >= 20) { + menu.Ticker(); + return; + } + } + + // run the count * ticdup dics + while (counts-- > 0) { + for (i = 0; i < ticdup; i++) { + if (gametic / ticdup > lowtic) { + doomSystem.Error("gametic>lowtic"); + } + if (advancedemo) { + DoAdvanceDemo(); + } + menu.Ticker(); + Ticker(); + gametic++; + + // modify command for duplicated tics + if (i != ticdup - 1) { + ticcmd_t cmd; + int buf; + int j; + + buf = (gametic / ticdup) % BACKUPTICS; + for (j = 0; j < MAXPLAYERS; j++) { + cmd = netcmds[j][buf]; + cmd.chatchar = 0; + if (flags(cmd.buttons, BT_SPECIAL)) { + cmd.buttons = 0; + } + } + } + } + NetUpdate(); // check for new console commands + } + } + + @Override + public doomcom_t getDoomCom() { + return this.doomcom; + } + + @Override + public void setDoomCom(doomcom_t doomcom) { + this.doomcom = doomcom; + } + + @Override + public void setGameAction(gameaction_t action) { + this.gameaction = action; + } + + @Override + public gameaction_t getGameAction() { + return this.gameaction; + } + + public final VideoScale vs; + + public boolean shouldPollLockingKeys() { + if (keysCleared) { + keysCleared = false; + return true; + } + return false; + } + + private String findFileNameToSave() { + String format = "DOOM%d%d%d%d.png"; + String lbmname = null; + // find a file name to save it to + int[] digit = new int[4]; + int i; + for (i = 0; i <= 9999; i++) { + digit[0] = ((i / 1000) % 10); + digit[1] = ((i / 100) % 10); + digit[2] = ((i / 10) % 10); + digit[3] = (i % 10); + lbmname = String.format(format, digit[0], digit[1], digit[2], digit[3]); + if (!C2JUtils.testReadAccess(lbmname)) { + break; // file doesn't exist + } + } + if (i == 10000) { + doomSystem.Error("M_ScreenShot: Couldn't create a PNG"); + } + return lbmname; + } + + protected final Finale selectFinale() { + return new Finale<>(this); + } + + /** + * M_Screenshot + * + * Currently saves PCX screenshots, and only in devparm. + * Very oldschool ;-) + * + * TODO: add non-devparm hotkey for screenshots, sequential screenshot + * messages, option to save as either PCX or PNG. Also, request + * current palette from VI (otherwise gamma settings and palette effects + * don't show up). + * + */ + public void ScreenShot() { + // find a file name to save it to + final String lbmname = findFileNameToSave(); // file doesn't exist + + if (graphicSystem.writeScreenShot(lbmname, FG)) { + players[consoleplayer].message = SCREENSHOT; + } + } +} + +//$Log: DoomMain.java,v $ +//Revision 1.109 2012/11/06 16:04:58 velktron +//Variables manager less tightly integrated. +// +//Revision 1.108 2012/11/05 17:25:29 velktron +//Fixed tinting system according to SodaHolic's advice. +// +//Revision 1.107 2012/09/27 16:53:46 velktron +//Stupid brokeness prevented -loadgame from working. +// +//Revision 1.106 2012/09/26 23:15:20 velktron +//Parallel renderer restored...sort of. +// +//Revision 1.105 2012/09/26 15:54:22 velktron +//Spritemanager is set up by renderer. +// +//Revision 1.104 2012/09/24 22:36:49 velktron +//Fixed HOM detection. +// +//Revision 1.103 2012/09/24 17:16:22 velktron +//Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +//Revision 1.101.2.11 2012/09/24 16:58:06 velktron +//TrueColor, Generics. +// +//Revision 1.101.2.10 2012/09/21 16:17:25 velktron +//More generic. +// +//Revision 1.101.2.9 2012/09/20 14:25:13 velktron +//Unified DOOM!!! +// \ No newline at end of file diff --git a/doom/src/doom/DoomStatus.java b/doom/src/doom/DoomStatus.java new file mode 100644 index 0000000..fe2863d --- /dev/null +++ b/doom/src/doom/DoomStatus.java @@ -0,0 +1,688 @@ +package doom; + +import static data.Defines.BACKUPTICS; +import static data.Limits.MAXPLAYERS; +import static data.Limits.MAXWADFILES; +import static data.Limits.MAX_DM_STARTS; +import data.mapthing_t; +import defines.GameMission_t; +import defines.GameMode; +import defines.Language_t; +import defines.gamestate_t; +import defines.skill_t; +import demo.IDoomDemo; +import f.Finale; +import static g.Signals.ScanCode.SC_0; +import static g.Signals.ScanCode.SC_1; +import static g.Signals.ScanCode.SC_2; +import static g.Signals.ScanCode.SC_3; +import static g.Signals.ScanCode.SC_4; +import static g.Signals.ScanCode.SC_5; +import static g.Signals.ScanCode.SC_6; +import static g.Signals.ScanCode.SC_7; +import static g.Signals.ScanCode.SC_8; +import static g.Signals.ScanCode.SC_9; +import static g.Signals.ScanCode.SC_COMMA; +import static g.Signals.ScanCode.SC_END; +import static g.Signals.ScanCode.SC_LALT; +import static g.Signals.ScanCode.SC_LCTRL; +import static g.Signals.ScanCode.SC_NUMKEY2; +import static g.Signals.ScanCode.SC_NUMKEY4; +import static g.Signals.ScanCode.SC_NUMKEY6; +import static g.Signals.ScanCode.SC_NUMKEY8; +import static g.Signals.ScanCode.SC_PERIOD; +import static g.Signals.ScanCode.SC_PGDOWN; +import static g.Signals.ScanCode.SC_PGUP; +import static g.Signals.ScanCode.SC_Q; +import static g.Signals.ScanCode.SC_RSHIFT; +import static g.Signals.ScanCode.SC_SPACE; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.stream.Stream; +import m.Settings; +import mochadoom.Engine; +import p.mobj_t; + +/** + * We need globally shared data structures, for defining the global state + * variables. MAES: in pure OO style, this should be a global "Doom state" + * object to be passed along various modules. No ugly globals here!!! Now, some + * of the variables that appear here were actually defined in separate modules. + * Pretty much, whatever needs to be shared with other modules was placed here, + * either as a local definition, or as an extern share. The very least, I'll + * document where everything is supposed to come from/reside. + */ +public abstract class DoomStatus { + + public static final int BGCOLOR = 7; + public static final int FGCOLOR = 8; + public static int RESENDCOUNT = 10; + public static int PL_DRONE = 0x80; // bit flag in doomdata->player + + public String[] wadfiles = new String[MAXWADFILES]; + + boolean drone; + + /** Command line parametersm, actually defined in d_main.c */ + public boolean nomonsters; // checkparm of -nomonsters + + public boolean respawnparm; // checkparm of -respawn + + public boolean fastparm; // checkparm of -fast + + public boolean devparm; // DEBUG: launched with -devparm + + // MAES: declared as "extern", shared with Menu.java + public boolean inhelpscreens; + + boolean advancedemo; + + /////////// Local to doomstat.c //////////// + // TODO: hide those behind getters + /** Game Mode - identify IWAD as shareware, retail etc. + * This is now hidden behind getters so some cases like plutonia + * etc. can be handled more cleanly. + * */ + private GameMode gamemode; + + public void setGameMode(GameMode mode) { + this.gamemode = mode; + } + + public GameMode getGameMode() { + return gamemode; + } + + public boolean isShareware() { + return (gamemode == GameMode.shareware); + } + + /** Commercial means Doom 2, Plutonia, TNT, and possibly others like XBLA. + * + * @return + */ + public boolean isCommercial() { + return (gamemode == GameMode.commercial + || gamemode == GameMode.pack_plut + || gamemode == GameMode.pack_tnt + || gamemode == GameMode.pack_xbla + || gamemode == GameMode.freedoom2 + || gamemode == GameMode.freedm); + } + + /** Retail means Ultimate. + * + * @return + */ + public boolean isRetail() { + return (gamemode == GameMode.retail || gamemode == GameMode.freedoom1); + } + + /** Registered is a subset of Ultimate + * + * @return + */ + public boolean isRegistered() { + return (gamemode == GameMode.registered || gamemode == GameMode.retail || gamemode == GameMode.freedoom1); + } + + public GameMission_t gamemission; + + /** Language. */ + public Language_t language; + + // /////////// Normally found in d_main.c /////////////// + // Selected skill type, map etc. + /** Defaults for menu, methinks. */ + public skill_t startskill; + + public int startepisode; + + public int startmap; + + public boolean autostart; + + /** Selected by user */ + public skill_t gameskill; + + public int gameepisode; + + public int gamemap; + + /** Nightmare mode flag, single player. */ + public boolean respawnmonsters; + + /** Netgame? Only true if >1 player. */ + public boolean netgame; + + /** + * Flag: true only if started as net deathmatch. An enum might handle + * altdeath/cooperative better. Use altdeath for the "2" value + */ + public boolean deathmatch; + + /** Use this instead of "deathmatch=2" which is bullshit. */ + public boolean altdeath; + + //////////// STUFF SHARED WITH THE RENDERER /////////////// + // ------------------------- + // Status flags for refresh. + // + public boolean nodrawers; + + public boolean noblit; + + public boolean viewactive; + + // Player taking events, and displaying. + public int consoleplayer; + + public int displayplayer; + + // Depending on view size - no status bar? + // Note that there is no way to disable the + // status bar explicitely. + public boolean statusbaractive; + + public boolean automapactive; // In AutoMap mode? + + public boolean menuactive; // Menu overlayed? + + public boolean mousecaptured = true; + + public boolean paused; // Game Pause? + + // ------------------------- + // Internal parameters for sound rendering. + // These have been taken from the DOS version, + // but are not (yet) supported with Linux + // (e.g. no sound volume adjustment with menu. + // These are not used, but should be (menu). + // From m_menu.c: + // Sound FX volume has default, 0 - 15 + // Music volume has default, 0 - 15 + // These are multiplied by 8. + /** maximum volume for sound */ + public int snd_SfxVolume; + + /** maximum volume for music */ + public int snd_MusicVolume; + + /** Maximum number of sound channels */ + public int numChannels; + + // Current music/sfx card - index useless + // w/o a reference LUT in a sound module. + // Ideally, this would use indices found + // in: /usr/include/linux/soundcard.h + public int snd_MusicDevice; + + public int snd_SfxDevice; + + // Config file? Same disclaimer as above. + public int snd_DesiredMusicDevice; + + public int snd_DesiredSfxDevice; + + // ------------------------------------- + // Scores, rating. + // Statistics on a given map, for intermission. + // + public int totalkills; + + public int totalitems; + + public int totalsecret; + + /** TNTHOM "cheat" for flashing HOM-detecting BG */ + public boolean flashing_hom; + + // Added for prBoom+ code + public int totallive; + + // Timer, for scores. + public int levelstarttic; // gametic at level start + + public int leveltime; // tics in game play for par + + // -------------------------------------- + // DEMO playback/recording related stuff. + // No demo, there is a human player in charge? + // Disable save/end game? + public boolean usergame; + + // ? + public boolean demoplayback; + + public boolean demorecording; + + // Quit after playing a demo from cmdline. + public boolean singledemo; + + public boolean mapstrobe; + + /** + * Set this to GS_DEMOSCREEN upon init, else it will be null + * Good Sign at 2017/03/21: I hope it is no longer true info, since I've checked its assignment by NetBeans + */ + public gamestate_t gamestate = gamestate_t.GS_DEMOSCREEN; + + // ----------------------------- + // Internal parameters, fixed. + // These are set by the engine, and not changed + // according to user inputs. Partly load from + // WAD, partly set at startup time. + public int gametic; + + // Alive? Disconnected? + public boolean[] playeringame = new boolean[MAXPLAYERS]; + + public mapthing_t[] deathmatchstarts = new mapthing_t[MAX_DM_STARTS]; + + /** pointer into deathmatchstarts */ + public int deathmatch_p; + + /** Player spawn spots. */ + public mapthing_t[] playerstarts = new mapthing_t[MAXPLAYERS]; + + /** Intermission stats. + Parameters for world map / intermission. */ + public wbstartstruct_t wminfo; + + /** LUT of ammunition limits for each kind. + This doubles with BackPack powerup item. + NOTE: this "maxammo" is treated like a global. + */ + public final static int[] maxammo = {200, 50, 300, 50}; + + // ----------------------------------------- + // Internal parameters, used for engine. + // + // File handling stuff. + public OutputStreamWriter debugfile; + + // if true, load all graphics at level load + public boolean precache; + + // wipegamestate can be set to -1 + // to force a wipe on the next draw + // wipegamestate can be set to -1 to force a wipe on the next draw + public gamestate_t wipegamestate = gamestate_t.GS_DEMOSCREEN; + + public int mouseSensitivity = 5; // AX: Fix wrong defaut mouseSensitivity + + /** Set if homebrew PWAD stuff has been added. */ + public boolean modifiedgame = false; + + /** debug flag to cancel adaptiveness set to true during timedemos. */ + public boolean singletics = false; + + /* A "fastdemo" is a demo with a clock that tics as + * fast as possible, yet it maintains adaptiveness and doesn't + * try to render everything at all costs. + */ + protected boolean fastdemo; + protected boolean normaldemo; + + protected String loaddemo = null; + + public int bodyqueslot; + + // Needed to store the number of the dummy sky flat. + // Used for rendering, + // as well as tracking projectiles etc. + //public int skyflatnum; + // TODO: Netgame stuff (buffers and pointers, i.e. indices). + // TODO: This is ??? + public doomcom_t doomcom; + + // TODO: This points inside doomcom. + public doomdata_t netbuffer; + + public ticcmd_t[] localcmds = new ticcmd_t[BACKUPTICS]; + + public int rndindex; + + public ticcmd_t[][] netcmds;// [MAXPLAYERS][BACKUPTICS]; + + /** MAES: this WAS NOT in the original. + * Remember to call it! + */ + protected final void initNetGameStuff() { + //this.netbuffer = new doomdata_t(); + this.doomcom = new doomcom_t(); + this.netcmds = new ticcmd_t[MAXPLAYERS][BACKUPTICS]; + + Arrays.setAll(localcmds, i -> new ticcmd_t()); + for (int i = 0; i < MAXPLAYERS; i++) { + Arrays.setAll(netcmds[i], j -> new ticcmd_t()); + } + } + + // Fields used for selecting variable BPP implementations. + protected abstract Finale selectFinale(); + + // MAES: Fields specific to DoomGame. A lot of them were + // duplicated/externalized + // in d_game.c and d_game.h, so it makes sense adopting a more unified + // approach. + protected gameaction_t gameaction = gameaction_t.ga_nothing; + + public boolean sendpause; // send a pause event next tic + + protected boolean sendsave; // send a save event next tic + + protected int starttime; + + protected boolean timingdemo; // if true, exit with report on completion + + public boolean getPaused() { + return paused; + } + + public void setPaused(boolean paused) { + this.paused = paused; + } + + // ////////// DEMO SPECIFIC STUFF///////////// + protected String demoname; + + protected boolean netdemo; + + //protected IDemoTicCmd[] demobuffer; + protected IDoomDemo demobuffer; + /** pointers */ + // USELESS protected int demo_p; + + // USELESS protected int demoend; + protected short[][] consistancy = new short[MAXPLAYERS][BACKUPTICS]; + + protected byte[] savebuffer; + + /* TODO Proper reconfigurable controls. Defaults hardcoded for now. T3h h4x, d00d. */ + public int key_right = SC_NUMKEY6.ordinal(); + public int key_left = SC_NUMKEY4.ordinal(); + public int key_up = SC_NUMKEY8.ordinal(); + public int key_down = SC_NUMKEY2.ordinal(); + public int key_strafeleft = SC_COMMA.ordinal(); + public int key_straferight = SC_PERIOD.ordinal(); + public int key_fire = SC_LCTRL.ordinal(); + public int key_use = SC_SPACE.ordinal(); + public int key_strafe = SC_LALT.ordinal(); + public int key_speed = SC_RSHIFT.ordinal(); + + public boolean vanillaKeyBehavior; + + public int key_recordstop = SC_Q.ordinal(); + public int[] key_numbers = Stream.of(SC_1, SC_2, SC_3, SC_4, SC_5, SC_6, SC_7, SC_8, SC_9, SC_0) + .mapToInt(Enum::ordinal).toArray(); + + // Heretic stuff + public int key_lookup = SC_PGUP.ordinal(); + public int key_lookdown = SC_PGDOWN.ordinal(); + public int key_lookcenter = SC_END.ordinal(); + + public int mousebfire = 0; + public int mousebstrafe = 2; // AX: Fixed - Now we use the right mouse buttons + public int mousebforward = 1; // AX: Fixed - Now we use the right mouse buttons + + public int joybfire; + + public int joybstrafe; + + public int joybuse; + + public int joybspeed; + + /** Cancel vertical mouse movement by default */ + protected boolean novert = true; + + protected int MAXPLMOVE() { + return forwardmove[1]; + } + + protected static final int TURBOTHRESHOLD = 0x32; + + /** fixed_t */ + protected final int[] forwardmove = {0x19, 0x32}; // + slow turn + + protected final int[] sidemove = {0x18, 0x28}; + + protected final int[] angleturn = {640, 1280, 320}; + + protected static final int SLOWTURNTICS = 6; + + protected static final int NUMKEYS = 256; + + protected boolean[] gamekeydown = new boolean[NUMKEYS]; + + protected boolean keysCleared; + + public boolean alwaysrun; + + protected int turnheld; // for accelerative turning + protected int lookheld; // for accelerative looking? + + protected boolean[] mousearray = new boolean[4]; + + /** This is an alias for mousearray [1+i] */ + protected boolean mousebuttons(int i) { + return mousearray[1 + i]; // allow [-1] + } + + protected void mousebuttons(int i, boolean value) { + mousearray[1 + i] = value; // allow [-1] + } + + protected void mousebuttons(int i, int value) { + mousearray[1 + i] = value != 0; // allow [-1] + } + + /** mouse values are used once */ + protected int mousex, mousey; + + protected int dclicktime; + + protected int dclickstate; + + protected int dclicks; + + protected int dclicktime2, dclickstate2, dclicks2; + + /** joystick values are repeated */ + protected int joyxmove, joyymove; + + protected boolean[] joyarray = new boolean[5]; + + protected boolean joybuttons(int i) { + return joyarray[1 + i]; // allow [-1] + } + + protected void joybuttons(int i, boolean value) { + joyarray[1 + i] = value; // allow [-1] + } + + protected void joybuttons(int i, int value) { + joyarray[1 + i] = value != 0; // allow [-1] + } + + protected int savegameslot; + + protected String savedescription; + + protected static final int BODYQUESIZE = 32; + + protected mobj_t[] bodyque = new mobj_t[BODYQUESIZE]; + + public String statcopy; // for statistics driver + + /** Not documented/used in linuxdoom. I supposed it could be used to + * ignore mouse input? + */ + public boolean use_mouse, use_joystick; + + /** More prBoom+ stuff. Used mostly for code uhm..reuse, rather + * than to actually change the way stuff works. + * + */ + public static int compatibility_level; + + public final ConfigManager CM = Engine.getConfig(); + + public DoomStatus() { + this.wminfo = new wbstartstruct_t(); + initNetGameStuff(); + } + + public void update() { + + this.snd_SfxVolume = CM.getValue(Settings.sfx_volume, Integer.class); + this.snd_MusicVolume = CM.getValue(Settings.music_volume, Integer.class); + this.alwaysrun = CM.equals(Settings.alwaysrun, Boolean.TRUE); + + // Keys... + this.key_right = CM.getValue(Settings.key_right, Integer.class); + this.key_left = CM.getValue(Settings.key_left, Integer.class); + this.key_up = CM.getValue(Settings.key_up, Integer.class); + this.key_down = CM.getValue(Settings.key_down, Integer.class); + this.key_strafeleft = CM.getValue(Settings.key_strafeleft, Integer.class); + this.key_straferight = CM.getValue(Settings.key_straferight, Integer.class); + this.key_fire = CM.getValue(Settings.key_fire, Integer.class); + this.key_use = CM.getValue(Settings.key_use, Integer.class); + this.key_strafe = CM.getValue(Settings.key_strafe, Integer.class); + this.key_speed = CM.getValue(Settings.key_speed, Integer.class); + + // Mouse buttons + this.use_mouse = CM.equals(Settings.use_mouse, 1); + this.mousebfire = CM.getValue(Settings.mouseb_fire, Integer.class); + this.mousebstrafe = CM.getValue(Settings.mouseb_strafe, Integer.class); + this.mousebforward = CM.getValue(Settings.mouseb_forward, Integer.class); + + // Joystick + this.use_joystick = CM.equals(Settings.use_joystick, 1); + this.joybfire = CM.getValue(Settings.joyb_fire, Integer.class); + this.joybstrafe = CM.getValue(Settings.joyb_strafe, Integer.class); + this.joybuse = CM.getValue(Settings.joyb_use, Integer.class); + this.joybspeed = CM.getValue(Settings.joyb_speed, Integer.class); + + // Sound + this.numChannels = CM.getValue(Settings.snd_channels, Integer.class); + + // Map strobe + this.mapstrobe = CM.equals(Settings.vestrobe, Boolean.TRUE); + + // Mouse sensitivity + this.mouseSensitivity = CM.getValue(Settings.mouse_sensitivity, Integer.class); + + // This should indicate keyboard behavior should be as close as possible to vanilla + this.vanillaKeyBehavior = CM.equals(Settings.vanilla_key_behavior, Boolean.TRUE); + } + + public void commit() { + CM.update(Settings.sfx_volume, this.snd_SfxVolume); + CM.update(Settings.music_volume, this.snd_MusicVolume); + CM.update(Settings.alwaysrun, this.alwaysrun); + + // Keys... + CM.update(Settings.key_right, this.key_right); + CM.update(Settings.key_left, this.key_left); + CM.update(Settings.key_up, this.key_up); + CM.update(Settings.key_down, this.key_down); + CM.update(Settings.key_strafeleft, this.key_strafeleft); + CM.update(Settings.key_straferight, this.key_straferight); + CM.update(Settings.key_fire, this.key_fire); + CM.update(Settings.key_use, this.key_use); + CM.update(Settings.key_strafe, this.key_strafe); + CM.update(Settings.key_speed, this.key_speed); + + // Mouse buttons + CM.update(Settings.use_mouse, this.use_mouse ? 1 : 0); + CM.update(Settings.mouseb_fire, this.mousebfire); + CM.update(Settings.mouseb_strafe, this.mousebstrafe); + CM.update(Settings.mouseb_forward, this.mousebforward); + + // Joystick + CM.update(Settings.use_joystick, this.use_joystick ? 1 : 0); + CM.update(Settings.joyb_fire, this.joybfire); + CM.update(Settings.joyb_strafe, this.joybstrafe); + CM.update(Settings.joyb_use, this.joybuse); + CM.update(Settings.joyb_speed, this.joybspeed); + + // Sound + CM.update(Settings.snd_channels, this.numChannels); + + // Map strobe + CM.update(Settings.vestrobe, this.mapstrobe); + + // Mouse sensitivity + CM.update(Settings.mouse_sensitivity, this.mouseSensitivity); + } +} + +// $Log: DoomStatus.java,v $ +// Revision 1.36 2012/11/06 16:04:58 velktron +// Variables manager less tightly integrated. +// +// Revision 1.35 2012/09/24 17:16:22 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.34.2.3 2012/09/24 16:58:06 velktron +// TrueColor, Generics. +// +// Revision 1.34.2.2 2012/09/20 14:25:13 velktron +// Unified DOOM!!! +// +// Revision 1.34.2.1 2012/09/17 16:06:52 velktron +// Now handling updates of all variables, though those specific to some subsystems should probably be moved??? +// +// Revision 1.34 2011/11/01 23:48:10 velktron +// Added tnthom stuff. +// +// Revision 1.33 2011/10/24 02:11:27 velktron +// Stream compliancy +// +// Revision 1.32 2011/10/07 16:01:16 velktron +// Added freelook stuff, using Keys. +// +// Revision 1.31 2011/09/27 16:01:41 velktron +// -complevel_t +// +// Revision 1.30 2011/09/27 15:54:51 velktron +// Added some more prBoom+ stuff. +// +// Revision 1.29 2011/07/28 17:07:04 velktron +// Added always run hack. +// +// Revision 1.28 2011/07/16 10:57:50 velktron +// Merged finnw's changes for enabling polling of ?_LOCK keys. +// +// Revision 1.27 2011/06/14 20:59:47 velktron +// Channel settings now read from default.cfg. Changes in sound creation order. +// +// Revision 1.26 2011/06/04 11:04:25 velktron +// Fixed registered/ultimate identification. +// +// Revision 1.25 2011/06/01 17:35:56 velktron +// Techdemo v1.4a level. Default novert and experimental mochaevents interface. +// +// Revision 1.24 2011/06/01 00:37:58 velktron +// Changed default keys to WASD. +// +// Revision 1.23 2011/05/31 21:45:51 velktron +// Added XBLA version as explicitly supported. +// +// Revision 1.22 2011/05/30 15:50:42 velktron +// Changed to work with new Abstract classes +// +// Revision 1.21 2011/05/26 17:52:11 velktron +// Now using ICommandLineManager +// +// Revision 1.20 2011/05/26 13:39:52 velktron +// Now using ICommandLineManager +// +// Revision 1.19 2011/05/25 17:56:52 velktron +// Introduced some fixes for mousebuttons etc. +// +// Revision 1.18 2011/05/24 17:44:37 velktron +// usemouse added for defaults +// \ No newline at end of file diff --git a/doom/src/doom/IDatagramSerializable.java b/doom/src/doom/IDatagramSerializable.java new file mode 100644 index 0000000..17c1000 --- /dev/null +++ b/doom/src/doom/IDatagramSerializable.java @@ -0,0 +1,52 @@ +package doom; + +/** Meant to provide a more lightweight alternative to Java's serialization model, + * specifically for the purpose of sending + * Objects implementing this can return references to one same byte array, with minimal + * overhead. Since it's for send-only purposes, it won't matter if it's modified. + * * + * But don't use it in lieu of CacheableDoomObject! + * + * @author admin + * + */ +public interface IDatagramSerializable { + + /** Packs object into a byte array suitable to send over + * datagram networks. Typically, objects cache this array + * for later use, and is availabe through cached() + * + * @return + */ + public byte[] pack(); + + /** Packs object into a byte array suitable to send over + * datagram networks. The array is supplied externally + * (good for daisy-chaining stuff into a single packet). + * + * @return + */ + public void pack(byte[] buf, int offset); + + /** Deserializes an object from a given byte buffer. + * Only the first (sizeof) bytes will be used, dependant + * on each object's implementation. Will NOT also copy + * the byte[] caches. + */ + public void unpack(byte[] buf); + + /** Deserializes an object from a given byte buffer. + * Only the first (sizeof) bytes will be used, starting + * from a specified offset, dependant on each object's + * implementation. + */ + public void unpack(byte[] buf, int offset); + + /** Only use this if you are 100% sure that the object's content + * won't have changed since the last call of pack(). + * + * @return Should return the underlying byte[] array directly. + */ + public byte[] cached(); + +} \ No newline at end of file diff --git a/doom/src/doom/IDoom.java b/doom/src/doom/IDoom.java new file mode 100644 index 0000000..39bcaf6 --- /dev/null +++ b/doom/src/doom/IDoom.java @@ -0,0 +1,25 @@ +package doom; + +import java.io.IOException; + +/** Stuff that the "main" is supposed to do. DoomMain implements those. + * + * @author Maes + * + */ +public interface IDoom { + + /** Called by IO functions when input is detected. */ + void PostEvent(event_t ev); + + void PageTicker(); + + void PageDrawer(); + + void AdvanceDemo(); + + void StartTitle(); + + void QuitNetGame() throws IOException; + +} \ No newline at end of file diff --git a/doom/src/doom/IDoomGame.java b/doom/src/doom/IDoomGame.java new file mode 100644 index 0000000..69d38d0 --- /dev/null +++ b/doom/src/doom/IDoomGame.java @@ -0,0 +1,42 @@ +package doom; + +import defines.skill_t; + +/** Groups functions formerly in d_game, + * in case you want to provide a different implementation + */ +public interface IDoomGame { + + void ExitLevel(); + + void WorldDone(); + + boolean CheckDemoStatus(); + + /** Can be called by the startup code or M_Responder. + A normal game starts at map 1, + but a warp test can start elsewhere */ + public void DeferedInitNew(skill_t skill, int episode, int map); + + /** Can be called by the startup code or M_Responder, + calls P_SetupLevel or W_EnterWorld. */ + public void LoadGame(String name); + + /** Called by M_Responder. */ + public void SaveGame(int slot, String description); + + /** Takes a screenshot *NOW* + * + */ + public void ScreenShot(); + + public void StartTitle(); + + public gameaction_t getGameAction(); + + public void setGameAction(gameaction_t ga); + + // public void PlayerReborn(int player); + void DeathMatchSpawnPlayer(int playernum); + +} \ No newline at end of file diff --git a/doom/src/doom/IDoomGameNetworking.java b/doom/src/doom/IDoomGameNetworking.java new file mode 100644 index 0000000..3f043e2 --- /dev/null +++ b/doom/src/doom/IDoomGameNetworking.java @@ -0,0 +1,36 @@ +package doom; + +import java.io.IOException; + +/** Doom is actually tied to its networking module. + * Therefore, no matter where and how you implement it, these functions + * need to be callable from within many modules. + * + * This is the so called "game networking" which is internal and game-specific, + * and not system networking which deals with the low level sockets and packet + * stuff. You'll need DoomSystemNetworking for that one. + * + * @author Velktron + * + */ +public interface IDoomGameNetworking { + + public void TryRunTics() throws IOException; + + /** + * NetUpdate + * Builds ticcmds for console player, + * sends out a packet + * @throws IOException + */ + public void NetUpdate(); + + public doomcom_t getDoomCom(); + + public void setDoomCom(doomcom_t doomcom); + + public int getTicdup(); + + public void setTicdup(int ticdup); + +} \ No newline at end of file diff --git a/doom/src/doom/KeyboardManager.java b/doom/src/doom/KeyboardManager.java new file mode 100644 index 0000000..43a7e60 --- /dev/null +++ b/doom/src/doom/KeyboardManager.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +/** + * + * @author Good Sign + */ +public class KeyboardManager { + +} \ No newline at end of file diff --git a/doom/src/doom/NetConsts.java b/doom/src/doom/NetConsts.java new file mode 100644 index 0000000..f61da63 --- /dev/null +++ b/doom/src/doom/NetConsts.java @@ -0,0 +1,19 @@ +package doom; + +public interface NetConsts { + + public static int NCMD_EXIT = 0x80000000; + public static int NCMD_RETRANSMIT = 0x40000000; + public static int NCMD_SETUP = 0x20000000; + public static int NCMD_KILL = 0x10000000; // kill game + public static int NCMD_CHECKSUM = 0x0fffffff; + + public static int DOOMCOM_ID = 0x12345678; + + //Networking and tick handling related. Moved to DEFINES + //protected static int BACKUPTICS = 12; + // command_t + public static short CMD_SEND = 1; + public static short CMD_GET = 2; + +} \ No newline at end of file diff --git a/doom/src/doom/SourceCode.java b/doom/src/doom/SourceCode.java new file mode 100644 index 0000000..b9399a1 --- /dev/null +++ b/doom/src/doom/SourceCode.java @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +import java.lang.annotation.Documented; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.SOURCE; +import java.lang.annotation.Target; + +@Target({}) +@Retention(SOURCE) +public @interface SourceCode { + + public enum AM_Map { + AM_Responder, + AM_Ticker, + AM_Drawer, + AM_Stop; + + @Documented + @Retention(SOURCE) + public @interface C { + + AM_Map value(); + } + } + + public enum D_Main { + D_DoomLoop, + D_ProcessEvents; + + @Documented + @Retention(SOURCE) + public @interface C { + + D_Main value(); + } + } + + public enum F_Finale { + F_Responder, + F_Ticker, + F_Drawer, + F_StartFinale; + + @Documented + @Retention(SOURCE) + public @interface C { + + F_Finale value(); + } + } + + public enum G_Game { + G_BuildTiccmd, + G_DoCompleted, + G_DoReborn, + G_DoLoadLevel, + G_DoSaveGame, + G_DoPlayDemo, + G_PlayerFinishLevel, + G_DoNewGame, + G_PlayerReborn, + G_CheckSpot, + G_DeathMatchSpawnPlayer, + G_InitNew, + G_DeferedInitNew, + G_DeferedPlayDemo, + G_LoadGame, + G_DoLoadGame, + G_SaveGame, + G_RecordDemo, + G_BeginRecording, + G_PlayDemo, + G_TimeDemo, + G_CheckDemoStatus, + G_ExitLevel, + G_SecretExitLevel, + G_WorldDone, + G_Ticker, + G_Responder, + G_ScreenShot; + + @Documented + @Retention(SOURCE) + public @interface C { + + G_Game value(); + } + } + + public enum HU_Lib { + HUlib_init, + HUlib_clearTextLine, + HUlib_initTextLine, + HUlib_addCharToTextLine, + HUlib_delCharFromTextLine, + HUlib_drawTextLine, + HUlib_eraseTextLine, + HUlib_initSText, + HUlib_addLineToSText, + HUlib_addMessageToSText, + HUlib_drawSText, + HUlib_eraseSText, + HUlib_initIText, + HUlib_delCharFromIText, + HUlib_eraseLineFromIText, + HUlib_resetIText, + HUlib_addPrefixToIText, + HUlib_keyInIText, + HUlib_drawIText, + HUlib_eraseIText; + + @Documented + @Retention(SOURCE) + public @interface C { + + HU_Lib value(); + } + } + + public enum HU_Stuff { + HU_Init, + HU_Start, + HU_Responder, + HU_Ticker, + HU_Drawer, + HU_queueChatChar, + HU_dequeueChatChar, + HU_Erase; + + @Documented + @Retention(SOURCE) + public @interface C { + + HU_Stuff value(); + } + } + + public enum I_IBM { + I_GetTime, + I_WaitVBL, + I_SetPalette, + I_FinishUpdate, + I_StartTic, + I_InitNetwork, + I_NetCmd; + + @Documented + @Retention(SOURCE) + public @interface C { + + I_IBM value(); + } + } + + public enum M_Argv { + M_CheckParm; + + @Documented + @Retention(SOURCE) + public @interface C { + + M_Argv value(); + } + } + + public enum M_Menu { + M_Responder, + M_Ticker, + M_Drawer, + M_Init, + M_StartControlPanel; + + @Documented + @Retention(SOURCE) + public @interface C { + + M_Menu value(); + } + } + + public enum M_Random { + M_Random, + P_Random, + M_ClearRandom; + + @Documented + @Retention(SOURCE) + public @interface C { + + M_Random value(); + } + } + + public enum P_Doors { + T_VerticalDoor, + EV_VerticalDoor, + EV_DoDoor, + EV_DoLockedDoor, + P_SpawnDoorCloseIn30, + P_SpawnDoorRaiseIn5Mins, + P_InitSlidingDoorFrames, + P_FindSlidingDoorType, + T_SlidingDoor, + EV_SlidingDoor; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Doors value(); + } + } + + public enum P_Map { + P_CheckPosition, + PIT_CheckThing, + PIT_CheckLine, + PIT_RadiusAttack, + PIT_ChangeSector, + PIT_StompThing, + PTR_SlideTraverse, + PTR_AimTraverse, + PTR_ShootTraverse, + PTR_UseTraverse; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Map value(); + } + } + + public enum P_MapUtl { + P_BlockThingsIterator, + P_BlockLinesIterator, + P_PathTraverse, + P_UnsetThingPosition, + P_SetThingPosition, + PIT_AddLineIntercepts, + PIT_AddThingIntercepts; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_MapUtl value(); + } + } + + public enum P_Mobj { + G_PlayerReborn, + P_SpawnMapThing, + P_SetMobjState, + P_ExplodeMissile, + P_XYMovement, + P_ZMovement, + P_NightmareRespawn, + P_MobjThinker, + P_SpawnMobj, + P_RemoveMobj, + P_RespawnSpecials, + P_SpawnPlayer, + P_SpawnPuff, + P_SpawnBlood, + P_CheckMissileSpawn, + P_SpawnMissile, + P_SpawnPlayerMissile; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Mobj value(); + } + } + + public enum P_Enemy { + PIT_VileCheck; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Enemy value(); + } + } + + public enum P_Lights { + T_FireFlicker, + P_SpawnFireFlicker, + T_LightFlash, + P_SpawnLightFlash, + T_StrobeFlash, + P_SpawnStrobeFlash, + EV_StartLightStrobing, + EV_TurnTagLightsOff, + EV_LightTurnOn, + T_Glow, + P_SpawnGlowingLight; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Lights value(); + } + } + + public enum P_SaveG { + P_ArchivePlayers, + P_UnArchivePlayers, + P_ArchiveWorld, + P_UnArchiveWorld, + P_ArchiveThinkers, + P_UnArchiveThinkers, + P_ArchiveSpecials, + P_UnArchiveSpecials; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_SaveG value(); + } + } + + public enum P_Setup { + P_SetupLevel, + P_LoadThings; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Setup value(); + } + } + + public enum P_Spec { + P_InitPicAnims, + P_SpawnSpecials, + P_UpdateSpecials, + P_UseSpecialLine, + P_ShootSpecialLine, + P_CrossSpecialLine, + P_PlayerInSpecialSector, + twoSided, + getSector, + getSide, + P_FindLowestFloorSurrounding, + P_FindHighestFloorSurrounding, + P_FindNextHighestFloor, + P_FindLowestCeilingSurrounding, + P_FindHighestCeilingSurrounding, + P_FindSectorFromLineTag, + P_FindMinSurroundingLight, + getNextSector, + EV_DoDonut, + P_ChangeSwitchTexture, + P_InitSwitchList, + T_PlatRaise, + EV_DoPlat, + P_AddActivePlat, + P_RemoveActivePlat, + EV_StopPlat, + P_ActivateInStasis, + EV_DoCeiling, + T_MoveCeiling, + P_AddActiveCeiling, + P_RemoveActiveCeiling, + EV_CeilingCrushStop, + P_ActivateInStasisCeiling, + T_MovePlane, + EV_BuildStairs, + EV_DoFloor, + T_MoveFloor, + EV_Teleport; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Spec value(); + } + } + + public enum P_Ceiling { + EV_DoCeiling; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Ceiling value(); + } + } + + public enum P_Tick { + P_InitThinkers, + P_RemoveThinker, + P_AddThinker, + P_AllocateThinker, + P_RunThinkers, + P_Ticker; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Tick value(); + } + } + + public enum P_Pspr { + P_SetPsprite, + P_CalcSwing, + P_BringUpWeapon, + P_CheckAmmo, + P_FireWeapon, + P_DropWeapon, + A_WeaponReady, + A_ReFire, + A_CheckReload, + A_Lower, + A_Raise, + A_GunFlash, + A_Punch, + A_Saw, + A_FireMissile, + A_FireBFG, + A_FirePlasma, + P_BulletSlope, + P_GunShot, + A_FirePistol, + A_FireShotgun, + A_FireShotgun2, + A_FireCGun, + A_Light0, + A_Light1, + A_Light2, + A_BFGSpray, + A_BFGsound, + P_SetupPsprites, + P_MovePsprites; + + @Documented + @Retention(SOURCE) + public @interface C { + + P_Pspr value(); + } + } + + public enum R_Data { + R_GetColumn, + R_InitData, + R_PrecacheLevel, + R_FlatNumForName, + R_TextureNumForName, + R_CheckTextureNumForName; + + @Documented + @Retention(SOURCE) + public @interface C { + + R_Data value(); + } + } + + public enum R_Draw { + R_FillBackScreen; + + @Documented + @Retention(SOURCE) + public @interface C { + + R_Draw value(); + } + } + + public enum R_Main { + R_PointOnSide, + R_PointOnSegSide, + R_PointToAngle, + R_PointToAngle2, + R_PointToDist, + R_ScaleFromGlobalAngle, + R_PointInSubsector, + R_AddPointToBox, + R_RenderPlayerView, + R_Init, + R_SetViewSize; + + @Documented + @Retention(SOURCE) + public @interface C { + + R_Main value(); + } + } + + public enum ST_Stuff { + ST_Responder, + ST_Ticker, + ST_Drawer, + ST_Start, + ST_Init; + + @Documented + @Retention(SOURCE) + public @interface C { + + ST_Stuff value(); + } + } + + public enum W_Wad { + W_InitMultipleFiles, + W_Reload, + W_CheckNumForName, + W_GetNumForName, + W_LumpLength, + W_ReadLump, + W_CacheLumpNum, + W_CacheLumpName; + + @Documented + @Retention(SOURCE) + public @interface C { + + W_Wad value(); + } + } + + public enum WI_Stuff { + WI_initVariables, + WI_loadData, + WI_initDeathmatchStats, + WI_initAnimatedBack, + WI_initNetgameStats, + WI_initStats, + WI_Ticker, + WI_Drawer, + WI_Start; + + @Documented + @Retention(SOURCE) + public @interface C { + + WI_Stuff value(); + } + } + + public interface D_Think { + + public enum actionf_t { + acp1, + acv, + acp2 + } + + @Documented + @Retention(SOURCE) + public @interface C { + + actionf_t value(); + } + } + + public enum Z_Zone { + Z_Malloc; + + @Documented + @Retention(SOURCE) + public @interface C { + + Z_Zone value(); + } + } + + @Documented + @Retention(SOURCE) + public @interface Exact { + + String description() default "Indicates that the method behaves exactly in vanilla way\n" + + " and can be skipped when traversing for compatibility"; + } + + @Documented + @Retention(SOURCE) + public @interface Compatible { + + String[] value() default ""; + + String description() default "Indicates that the method can behave differently from vanilla way,\n" + + " but this behavior is reviewed and can be turned back to vanilla as an option." + + "A value might be specivied with the equivalent vanilla code"; + } + + public enum CauseOfDesyncProbability { + LOW, + MEDIUM, + HIGH + } + + @Documented + @Retention(SOURCE) + public @interface Suspicious { + + CauseOfDesyncProbability value() default CauseOfDesyncProbability.HIGH; + + String description() default "Indicates that the method contains behavior totally different\n" + + "from vanilla, and by so should be considered suspicious\n" + + "in terms of compatibility"; + } + + @Documented + @Retention(SOURCE) + @Target({METHOD, FIELD, LOCAL_VARIABLE, PARAMETER}) + public @interface angle_t { + } + + @Documented + @Retention(SOURCE) + @Target({METHOD, FIELD, LOCAL_VARIABLE, PARAMETER}) + public @interface fixed_t { + } + + @Documented + @Retention(SOURCE) + public @interface actionf_p1 { + } + + @Documented + @Retention(SOURCE) + public @interface actionf_v { + } + + @Documented + @Retention(SOURCE) + public @interface actionf_p2 { + } + + @Documented + @Retention(SOURCE) + @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) + public @interface thinker_t { + } + + @Documented + @Retention(SOURCE) + @Target({FIELD, LOCAL_VARIABLE, PARAMETER}) + public @interface think_t { + } +} \ No newline at end of file diff --git a/doom/src/doom/doomcom_t.java b/doom/src/doom/doomcom_t.java new file mode 100644 index 0000000..20343c3 --- /dev/null +++ b/doom/src/doom/doomcom_t.java @@ -0,0 +1,57 @@ +package doom; + +public class doomcom_t { + + public doomcom_t() { + this.data = new doomdata_t(); + + } + + // Supposed to be DOOMCOM_ID? + // Maes: was "long", but they intend 32-bit "int" here. Hurray for C's consistency! + public int id; + + // DOOM executes an int to execute commands. + public short intnum; + // Communication between DOOM and the driver. + // Is CMD_SEND or CMD_GET. + public short command; + // Is dest for send, set by get (-1 = no packet). + public short remotenode; + + // Number of bytes in doomdata to be sent + public short datalength; + + // Info common to all nodes. + // Console is allways node 0. + public short numnodes; + // Flag: 1 = no duplication, 2-5 = dup for slow nets. + public short ticdup; + // Flag: 1 = send a backup tic in every packet. + public short extratics; + // Flag: 1 = deathmatch. + public short deathmatch; + // Flag: -1 = new game, 0-5 = load savegame + public short savegame; + public short episode; // 1-3 + public short map; // 1-9 + public short skill; // 1-5 + + // Info specific to this node. + public short consoleplayer; + public short numplayers; + + // These are related to the 3-display mode, + // in which two drones looking left and right + // were used to render two additional views + // on two additional computers. + // Probably not operational anymore. + // 1 = left, 0 = center, -1 = right + public short angleoffset; + // 1 = drone + public short drone; + + // The packet data to be sent. + public doomdata_t data; + +} \ No newline at end of file diff --git a/doom/src/doom/doomdata_t.java b/doom/src/doom/doomdata_t.java new file mode 100644 index 0000000..9611b67 --- /dev/null +++ b/doom/src/doom/doomdata_t.java @@ -0,0 +1,127 @@ +package doom; + +import java.nio.ByteBuffer; +import static utils.GenericCopy.malloc; +import w.DoomBuffer; + +public class doomdata_t implements IDatagramSerializable { + + public static final int DOOMDATALEN = 8 + data.Defines.BACKUPTICS * ticcmd_t.TICCMDLEN; + + // High bit is retransmit request. + /** MAES: was "unsigned" */ + public int checksum; + + /* + * CAREFUL!!! Those "bytes" are actually unsigned + */ + /** Only valid if NCMD_RETRANSMIT. */ + public byte retransmitfrom; + + public byte starttic; + public byte player; + public byte numtics; + public ticcmd_t[] cmds; + + public doomdata_t() { + cmds = malloc(ticcmd_t::new, ticcmd_t[]::new, data.Defines.BACKUPTICS); + // Enough space for its own header + the ticcmds; + buffer = new byte[DOOMDATALEN]; + // This "pegs" the ByteBuffer to this particular array. + // Separate updates are not necessary. + bbuf = ByteBuffer.wrap(buffer); + } + + // Used for datagram serialization. + private byte[] buffer; + private ByteBuffer bbuf; + + @Override + public byte[] pack() { + bbuf.rewind(); + + // Why making it harder? + bbuf.putInt(checksum); + bbuf.put(retransmitfrom); + bbuf.put(starttic); + bbuf.put(player); + bbuf.put(numtics); + + // FIXME: it's probably more efficient to use System.arraycopy ? + // Or are the packets too small anyway? At most we'll be sending "doomdata_t's" + for (int i = 0; i < cmds.length; i++) { + bbuf.put(cmds[i].pack()); + } + + return bbuf.array(); + } + + @Override + public void pack(byte[] buf, int offset) { + // No need to make it harder...just pack it and slap it in. + byte[] tmp = this.pack(); + System.arraycopy(tmp, 0, buf, offset, tmp.length); + } + + @Override + public void unpack(byte[] buf) { + unpack(buf, 0); + } + + @Override + public void unpack(byte[] buf, int offset) { + checksum = DoomBuffer.getBEInt(buf); + offset = +4; + retransmitfrom = buf[offset++]; + starttic = buf[offset++]; + player = buf[offset++]; + numtics = buf[offset++]; + + for (int i = 0; i < cmds.length; i++) { + cmds[i].unpack(buf, offset); + offset += ticcmd_t.TICCMDLEN; + } + } + + public void selfUnpack() { + unpack(this.buffer); + } + + public void copyFrom(doomdata_t source) { + this.checksum = source.checksum; + this.numtics = source.numtics; + this.player = source.player; + this.retransmitfrom = source.retransmitfrom; + this.starttic = source.starttic; + + // MAES: this was buggy as hell, and didn't work at all, which + // in turn prevented other subsystems such as speed throttling and + // networking to work. + // + // This should be enough to alter the ByteBuffer too. + // System.arraycopy(source.cached(), 0, this.buffer, 0, DOOMDATALEN); + // This should set all fields + // selfUnpack(); + } + + @Override + public byte[] cached() { + return this.buffer; + } + + StringBuilder sb = new StringBuilder(); + + public String toString() { + sb.setLength(0); + sb.append("doomdata_t "); + sb.append(retransmitfrom); + sb.append(" starttic "); + sb.append(starttic); + sb.append(" player "); + sb.append(player); + sb.append(" numtics "); + sb.append(numtics); + return sb.toString(); + } + +} \ No newline at end of file diff --git a/doom/src/doom/englsh.java b/doom/src/doom/englsh.java new file mode 100644 index 0000000..93a5b4f --- /dev/null +++ b/doom/src/doom/englsh.java @@ -0,0 +1,642 @@ +package doom; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: englsh.java,v 1.5 2011/05/31 21:46:20 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Printed strings for translation. +// English language support (default). +// +//----------------------------------------------------------------------------- +// +// Printed strings for translation +// +// +// D_Main.C +// +public class englsh { + + public final static String D_DEVSTR = "Development mode ON.\n"; + public final static String D_CDROM = "CD-ROM Version: default.cfg from c:\\doomdata\n"; + +// +// M_Misc.C +// + public final static String SCREENSHOT = "screen shot"; + +// +// M_Menu.C +// + public final static String PRESSKEY = "press a key."; + public final static String PRESSYN = "press y or n."; + public final static String QUITMSG = "are you sure you want to\nquit this great game?"; + public final static String LOADNET = "you can't do load while in a net game!\n\n" + PRESSKEY; + public final static String QLOADNET = "you can't quickload during a netgame!\n\n" + PRESSKEY; + public final static String QSAVESPOT = "you haven't picked a quicksave slot yet!\n\n" + PRESSKEY; + public final static String SAVEDEAD = "you can't save if you aren't playing!\n\n" + PRESSKEY; + public final static String QSPROMPT = "quicksave over your game named\n\n'%s'?\n\n" + PRESSYN; + public final static String QLPROMPT = "do you want to quickload the game named\n\n'%s'?\n\n" + PRESSYN; + + public final static String NEWGAME = "you can't start a new game\nwhile in a network game.\n\n" + PRESSKEY; + + public final static String NIGHTMARE = "are you sure? this skill level\nisn't even remotely fair.\n\n" + PRESSYN; + + public final static String SWSTRING = "this is the shareware version of doom.\n\nyou need to order the entire trilogy.\n\n" + PRESSKEY; + + public final static String MSGOFF = "Messages OFF"; + public final static String MSGON = "Messages ON"; + public final static String NETEND = "you can't end a netgame!\n\n" + PRESSKEY; + public final static String ENDGAME = "are you sure you want to end the game?\n\n" + PRESSYN; + + public final static String DOSY = "(press y to quit)"; + + public final static String DETAILHI = "High detail"; + public final static String DETAILLO = "Low detail"; + public final static String GAMMALVL0 = "Gamma correction OFF"; + public final static String GAMMALVL1 = "Gamma correction level 1"; + public final static String GAMMALVL2 = "Gamma correction level 2"; + public final static String GAMMALVL3 = "Gamma correction level 3"; + public final static String GAMMALVL4 = "Gamma correction level 4"; + public final static String EMPTYSTRING = "empty slot"; + +// +// P_inter.C +// + public final static String GOTARMOR = "Picked up the armor."; + public final static String GOTMEGA = "Picked up the MegaArmor!"; + public final static String GOTHTHBONUS = "Picked up a health bonus."; + public final static String GOTARMBONUS = "Picked up an armor bonus."; + public final static String GOTSTIM = "Picked up a stimpack."; + public final static String GOTMEDINEED = "Picked up a medikit that you REALLY need!"; + public final static String GOTMEDIKIT = "Picked up a medikit."; + public final static String GOTSUPER = "Supercharge!"; + + public final static String GOTBLUECARD = "Picked up a blue keycard."; + public final static String GOTYELWCARD = "Picked up a yellow keycard."; + public final static String GOTREDCARD = "Picked up a red keycard."; + public final static String GOTBLUESKUL = "Picked up a blue skull key."; + public final static String GOTYELWSKUL = "Picked up a yellow skull key."; + public final static String GOTREDSKULL = "Picked up a red skull key."; + + public final static String GOTINVUL = "Invulnerability!"; + public final static String GOTBERSERK = "Berserk!"; + public final static String GOTINVIS = "Partial Invisibility"; + public final static String GOTSUIT = "Radiation Shielding Suit"; + public final static String GOTMAP = "Computer Area Map"; + public final static String GOTVISOR = "Light Amplification Visor"; + public final static String GOTMSPHERE = "MegaSphere!"; + + public final static String GOTCLIP = "Picked up a clip."; + public final static String GOTCLIPBOX = "Picked up a box of bullets."; + public final static String GOTROCKET = "Picked up a rocket."; + public final static String GOTROCKBOX = "Picked up a box of rockets."; + public final static String GOTCELL = "Picked up an energy cell."; + public final static String GOTCELLBOX = "Picked up an energy cell pack."; + public final static String GOTSHELLS = "Picked up 4 shotgun shells."; + public final static String GOTSHELLBOX = "Picked up a box of shotgun shells."; + public final static String GOTBACKPACK = "Picked up a backpack full of ammo!"; + + public final static String GOTBFG9000 = "You got the BFG9000! Oh, yes."; + public final static String GOTCHAINGUN = "You got the chaingun!"; + public final static String GOTCHAINSAW = "A chainsaw! Find some meat!"; + public final static String GOTLAUNCHER = "You got the rocket launcher!"; + public final static String GOTPLASMA = "You got the plasma gun!"; + public final static String GOTSHOTGUN = "You got the shotgun!"; + public final static String GOTSHOTGUN2 = "You got the super shotgun!"; + +// +// P_Doors.C +// + public final static String PD_BLUEO = "You need a blue key to activate this object"; + public final static String PD_REDO = "You need a red key to activate this object"; + public final static String PD_YELLOWO = "You need a yellow key to activate this object"; + public final static String PD_BLUEK = "You need a blue key to open this door"; + public final static String PD_REDK = "You need a red key to open this door"; + public final static String PD_YELLOWK = "You need a yellow key to open this door"; + +// +// G_game.C +// + public final static String GGSAVED = "game saved."; + +// +// HU_stuff.C +// + public final static String HUSTR_MSGU = "[Message unsent]"; + + public final static String HUSTR_E1M1 = "E1M1: Hangar"; + public final static String HUSTR_E1M2 = "E1M2: Nuclear Plant"; + public final static String HUSTR_E1M3 = "E1M3: Toxin Refinery"; + public final static String HUSTR_E1M4 = "E1M4: Command Control"; + public final static String HUSTR_E1M5 = "E1M5: Phobos Lab"; + public final static String HUSTR_E1M6 = "E1M6: Central Processing"; + public final static String HUSTR_E1M7 = "E1M7: Computer Station"; + public final static String HUSTR_E1M8 = "E1M8: Phobos Anomaly"; + public final static String HUSTR_E1M9 = "E1M9: Military Base"; + + public final static String HUSTR_E2M1 = "E2M1: Deimos Anomaly"; + public final static String HUSTR_E2M2 = "E2M2: Containment Area"; + public final static String HUSTR_E2M3 = "E2M3: Refinery"; + public final static String HUSTR_E2M4 = "E2M4: Deimos Lab"; + public final static String HUSTR_E2M5 = "E2M5: Command Center"; + public final static String HUSTR_E2M6 = "E2M6: Halls of the Damned"; + public final static String HUSTR_E2M7 = "E2M7: Spawning Vats"; + public final static String HUSTR_E2M8 = "E2M8: Tower of Babel"; + public final static String HUSTR_E2M9 = "E2M9: Fortress of Mystery"; + + public final static String HUSTR_E3M1 = "E3M1: Hell Keep"; + public final static String HUSTR_E3M2 = "E3M2: Slough of Despair"; + public final static String HUSTR_E3M3 = "E3M3: Pandemonium"; + public final static String HUSTR_E3M4 = "E3M4: House of Pain"; + public final static String HUSTR_E3M5 = "E3M5: Unholy Cathedral"; + public final static String HUSTR_E3M6 = "E3M6: Mt. Erebus"; + public final static String HUSTR_E3M7 = "E3M7: Limbo"; + public final static String HUSTR_E3M8 = "E3M8: Dis"; + public final static String HUSTR_E3M9 = "E3M9: Warrens"; + + public final static String HUSTR_E4M1 = "E4M1: Hell Beneath"; + public final static String HUSTR_E4M2 = "E4M2: Perfect Hatred"; + public final static String HUSTR_E4M3 = "E4M3: Sever The Wicked"; + public final static String HUSTR_E4M4 = "E4M4: Unruly Evil"; + public final static String HUSTR_E4M5 = "E4M5: They Will Repent"; + public final static String HUSTR_E4M6 = "E4M6: Against Thee Wickedly"; + public final static String HUSTR_E4M7 = "E4M7: And Hell Followed"; + public final static String HUSTR_E4M8 = "E4M8: Unto The Cruel"; + public final static String HUSTR_E4M9 = "E4M9: Fear"; + + public final static String HUSTR_1 = "level 1: entryway"; + public final static String HUSTR_2 = "level 2: underhalls"; + public final static String HUSTR_3 = "level 3: the gantlet"; + public final static String HUSTR_4 = "level 4: the focus"; + public final static String HUSTR_5 = "level 5: the waste tunnels"; + public final static String HUSTR_6 = "level 6: the crusher"; + public final static String HUSTR_7 = "level 7: dead simple"; + public final static String HUSTR_8 = "level 8: tricks and traps"; + public final static String HUSTR_9 = "level 9: the pit"; + public final static String HUSTR_10 = "level 10: refueling base"; + public final static String HUSTR_11 = "level 11: 'o' of destruction!"; + + public final static String HUSTR_12 = "level 12: the factory"; + public final static String HUSTR_13 = "level 13: downtown"; + public final static String HUSTR_14 = "level 14: the inmost dens"; + public final static String HUSTR_15 = "level 15: industrial zone"; + public final static String HUSTR_16 = "level 16: suburbs"; + public final static String HUSTR_17 = "level 17: tenements"; + public final static String HUSTR_18 = "level 18: the courtyard"; + public final static String HUSTR_19 = "level 19: the citadel"; + public final static String HUSTR_20 = "level 20: gotcha!"; + + public final static String HUSTR_21 = "level 21: nirvana"; + public final static String HUSTR_22 = "level 22: the catacombs"; + public final static String HUSTR_23 = "level 23: barrels o' fun"; + public final static String HUSTR_24 = "level 24: the chasm"; + public final static String HUSTR_25 = "level 25: bloodfalls"; + public final static String HUSTR_26 = "level 26: the abandoned mines"; + public final static String HUSTR_27 = "level 27: monster condo"; + public final static String HUSTR_28 = "level 28: the spirit world"; + public final static String HUSTR_29 = "level 29: the living end"; + public final static String HUSTR_30 = "level 30: icon of sin"; + + public final static String HUSTR_31 = "level 31: wolfenstein"; + public final static String HUSTR_32 = "level 32: grosse"; + public final static String HUSTR_33 = "level 33: betray"; + + public final static String PHUSTR_1 = "level 1: congo"; + public final static String PHUSTR_2 = "level 2: well of souls"; + public final static String PHUSTR_3 = "level 3: aztec"; + public final static String PHUSTR_4 = "level 4: caged"; + public final static String PHUSTR_5 = "level 5: ghost town"; + public final static String PHUSTR_6 = "level 6: baron's lair"; + public final static String PHUSTR_7 = "level 7: caughtyard"; + public final static String PHUSTR_8 = "level 8: realm"; + public final static String PHUSTR_9 = "level 9: abattoire"; + public final static String PHUSTR_10 = "level 10: onslaught"; + public final static String PHUSTR_11 = "level 11: hunted"; + + public final static String PHUSTR_12 = "level 12: speed"; + public final static String PHUSTR_13 = "level 13: the crypt"; + public final static String PHUSTR_14 = "level 14: genesis"; + public final static String PHUSTR_15 = "level 15: the twilight"; + public final static String PHUSTR_16 = "level 16: the omen"; + public final static String PHUSTR_17 = "level 17: compound"; + public final static String PHUSTR_18 = "level 18: neurosphere"; + public final static String PHUSTR_19 = "level 19: nme"; + public final static String PHUSTR_20 = "level 20: the death domain"; + + public final static String PHUSTR_21 = "level 21: slayer"; + public final static String PHUSTR_22 = "level 22: impossible mission"; + public final static String PHUSTR_23 = "level 23: tombstone"; + public final static String PHUSTR_24 = "level 24: the final frontier"; + public final static String PHUSTR_25 = "level 25: the temple of darkness"; + public final static String PHUSTR_26 = "level 26: bunker"; + public final static String PHUSTR_27 = "level 27: anti-christ"; + public final static String PHUSTR_28 = "level 28: the sewers"; + public final static String PHUSTR_29 = "level 29: odyssey of noises"; + public final static String PHUSTR_30 = "level 30: the gateway of hell"; + + public final static String PHUSTR_31 = "level 31: cyberden"; + public final static String PHUSTR_32 = "level 32: go 2 it"; + + public final static String THUSTR_1 = "level 1: system control"; + public final static String THUSTR_2 = "level 2: human bbq"; + public final static String THUSTR_3 = "level 3: power control"; + public final static String THUSTR_4 = "level 4: wormhole"; + public final static String THUSTR_5 = "level 5: hanger"; + public final static String THUSTR_6 = "level 6: open season"; + public final static String THUSTR_7 = "level 7: prison"; + public final static String THUSTR_8 = "level 8: metal"; + public final static String THUSTR_9 = "level 9: stronghold"; + public final static String THUSTR_10 = "level 10: redemption"; + public final static String THUSTR_11 = "level 11: storage facility"; + + public final static String THUSTR_12 = "level 12: crater"; + public final static String THUSTR_13 = "level 13: nukage processing"; + public final static String THUSTR_14 = "level 14: steel works"; + public final static String THUSTR_15 = "level 15: dead zone"; + public final static String THUSTR_16 = "level 16: deepest reaches"; + public final static String THUSTR_17 = "level 17: processing area"; + public final static String THUSTR_18 = "level 18: mill"; + public final static String THUSTR_19 = "level 19: shipping/respawning"; + public final static String THUSTR_20 = "level 20: central processing"; + + public final static String THUSTR_21 = "level 21: administration center"; + public final static String THUSTR_22 = "level 22: habitat"; + public final static String THUSTR_23 = "level 23: lunar mining project"; + public final static String THUSTR_24 = "level 24: quarry"; + public final static String THUSTR_25 = "level 25: baron's den"; + public final static String THUSTR_26 = "level 26: ballistyx"; + public final static String THUSTR_27 = "level 27: mount pain"; + public final static String THUSTR_28 = "level 28: heck"; + public final static String THUSTR_29 = "level 29: river styx"; + public final static String THUSTR_30 = "level 30: last call"; + + public final static String THUSTR_31 = "level 31: pharaoh"; + public final static String THUSTR_32 = "level 32: caribbean"; + + public final static String HUSTR_CHATMACRO1 = "I'm ready to kick butt!"; + public final static String HUSTR_CHATMACRO2 = "I'm OK."; + public final static String HUSTR_CHATMACRO3 = "I'm not looking too good!"; + public final static String HUSTR_CHATMACRO4 = "Help!"; + public final static String HUSTR_CHATMACRO5 = "You suck!"; + public final static String HUSTR_CHATMACRO6 = "Next time, scumbag..."; + public final static String HUSTR_CHATMACRO7 = "Come here!"; + public final static String HUSTR_CHATMACRO8 = "I'll take care of it."; + public final static String HUSTR_CHATMACRO9 = "Yes"; + public final static String HUSTR_CHATMACRO0 = "No"; + + public final static String HUSTR_TALKTOSELF1 = "You mumble to yourself"; + public final static String HUSTR_TALKTOSELF2 = "Who's there?"; + public final static String HUSTR_TALKTOSELF3 = "You scare yourself"; + public final static String HUSTR_TALKTOSELF4 = "You start to rave"; + public final static String HUSTR_TALKTOSELF5 = "You've lost it..."; + + public final static String HUSTR_MESSAGESENT = "[Message Sent]"; + +// The following should NOT be changed unless it seems +// just AWFULLY necessary + public final static String HUSTR_PLRGREEN = "Green: "; + public final static String HUSTR_PLRINDIGO = "Indigo: "; + public final static String HUSTR_PLRBROWN = "Brown: "; + public final static String HUSTR_PLRRED = "Red: "; + + public final static char HUSTR_KEYGREEN = 'g'; + public final static char HUSTR_KEYINDIGO = 'i'; + public final static char HUSTR_KEYBROWN = 'b'; + public final static char HUSTR_KEYRED = 'r'; + +// +// AM_map.C +// + public final static String AMSTR_FOLLOWON = "Follow Mode ON"; + public final static String AMSTR_FOLLOWOFF = "Follow Mode OFF"; + + public final static String AMSTR_GRIDON = "Grid ON"; + public final static String AMSTR_GRIDOFF = "Grid OFF"; + + public final static String AMSTR_MARKEDSPOT = "Marked Spot"; + public final static String AMSTR_MARKSCLEARED = "All Marks Cleared"; + +// +// ST_stuff.C +// + public final static String STSTR_MUS = "Music Change"; + public final static String STSTR_NOMUS = "IMPOSSIBLE SELECTION"; + public final static String STSTR_DQDON = "Degreelessness Mode On"; + public final static String STSTR_DQDOFF = "Degreelessness Mode Off"; + + public final static String STSTR_KFAADDED = "Very Happy Ammo Added"; + public final static String STSTR_FAADDED = "Ammo (no keys) Added"; + + public final static String STSTR_NCON = "No Clipping Mode ON"; + public final static String STSTR_NCOFF = "No Clipping Mode OFF"; + + public final static String STSTR_BEHOLD = "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp"; + public final static String STSTR_BEHOLDX = "Power-up Toggled"; + + public final static String STSTR_CHOPPERS = "... doesn't suck - GM"; + public final static String STSTR_CLEV = "Changing Level..."; + +// +// F_Finale.C +// + public final static String E1TEXT = ("Once you beat the big badasses and\n" + + "clean out the moon base you're supposed\n" + + "to win, aren't you? Aren't you? Where's\n" + + "your fat reward and ticket home? What\n" + + "the hell is this? It's not supposed to\n" + + "end this way!\n" + + "\n" + + "It stinks like rotten meat, but looks\n" + + "like the lost Deimos base. Looks like\n" + + "you're stuck on The Shores of Hell.\n" + + "The only way out is through.\n" + + "\n" + + "To continue the DOOM experience, play\n" + + "The Shores of Hell and its amazing\n" + + "sequel, Inferno!\n"); + + public final static String E2TEXT = ("You've done it! The hideous cyber-\n" + + "demon lord that ruled the lost Deimos\n" + + "moon base has been slain and you\n" + + "are triumphant! But ... where are\n" + + "you? You clamber to the edge of the\n" + + "moon and look down to see the awful\n" + + "truth.\n" + + "\n" + + "Deimos floats above Hell itself!\n" + + "You've never heard of anyone escaping\n" + + "from Hell, but you'll make the bastards\n" + + "sorry they ever heard of you! Quickly,\n" + + "you rappel down to the surface of\n" + + "Hell.\n" + + "\n" + + "Now, it's on to the final chapter of\n" + + "DOOM! -- Inferno."); + + public final static String E3TEXT = ("The loathsome spiderdemon that\n" + + "masterminded the invasion of the moon\n" + + "bases and caused so much death has had\n" + + "its ass kicked for all time.\n" + + "\n" + + "A hidden doorway opens and you enter.\n" + + "You've proven too tough for Hell to\n" + + "contain, and now Hell at last plays\n" + + "fair -- for you emerge from the door\n" + + "to see the green fields of Earth!\n" + + "Home at last.\n" + + "\n" + + "You wonder what's been happening on\n" + + "Earth while you were battling evil\n" + + "unleashed. It's good that no Hell-\n" + + "spawn could have come through that\n" + + "door with you ..."); + + public final static String E4TEXT = ("the spider mastermind must have sent forth\n" + + "its legions of hellspawn before your\n" + + "final confrontation with that terrible\n" + + "beast from hell. but you stepped forward\n" + + "and brought forth eternal damnation and\n" + + "suffering upon the horde as a true hero\n" + + "would in the face of something so evil.\n" + + "\n" + + "besides, someone was gonna pay for what\n" + + "happened to daisy, your pet rabbit.\n" + + "\n" + + "but now, you see spread before you more\n" + + "potential pain and gibbitude as a nation\n" + + "of demons run amok among our cities.\n" + + "\n" + + "next stop, hell on earth!"); + +// after level 6, put this: + public final static String C1TEXT = ("YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" + + "STARPORT. BUT SOMETHING IS WRONG. THE\n" + + "MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" + + "WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" + + "IS BEING SUBVERTED BY THEIR PRESENCE.\n" + + "\n" + + "AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" + + "FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" + + "YOU CAN PENETRATE INTO THE HAUNTED HEART\n" + + "OF THE STARBASE AND FIND THE CONTROLLING\n" + + "SWITCH WHICH HOLDS EARTH'S POPULATION\n" + + "HOSTAGE."); + +// After level 11, put this: + public final static String C2TEXT = ("YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" + + "HUMANKIND TO EVACUATE EARTH AND ESCAPE\n" + + "THE NIGHTMARE. NOW YOU ARE THE ONLY\n" + + "HUMAN LEFT ON THE FACE OF THE PLANET.\n" + + "CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n" + + "AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n" + + "YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n" + + "THAT YOU HAVE SAVED YOUR SPECIES.\n" + + "\n" + + "BUT THEN, EARTH CONTROL BEAMS DOWN A\n" + + "MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n" + + "THE SOURCE OF THE ALIEN INVASION. IF YOU\n" + + "GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n" + + "ENTRY. THE ALIEN BASE IS IN THE HEART OF\n" + + "YOUR OWN HOME CITY, NOT FAR FROM THE\n" + + "STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n" + + "UP AND RETURN TO THE FRAY."); + +// After level 20, put this: + public final static String C3TEXT = ("YOU ARE AT THE CORRUPT HEART OF THE CITY,\n" + + "SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n" + + "YOU SEE NO WAY TO DESTROY THE CREATURES'\n" + + "ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n" + + "TEETH AND PLUNGE THROUGH IT.\n" + + "\n" + + "THERE MUST BE A WAY TO CLOSE IT ON THE\n" + + "OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n" + + "GOT TO GO THROUGH HELL TO GET TO IT?"); + +// After level 30, put this: + public final static String C4TEXT = ("THE HORRENDOUS VISAGE OF THE BIGGEST\n" + + "DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n" + + "YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n" + + "HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n" + + "UP AND DIES, ITS THRASHING LIMBS\n" + + "DEVASTATING UNTOLD MILES OF HELL'S\n" + + "SURFACE.\n" + + "\n" + + "YOU'VE DONE IT. THE INVASION IS OVER.\n" + + "EARTH IS SAVED. HELL IS A WRECK. YOU\n" + + "WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n" + + "DIE, NOW. WIPING THE SWEAT FROM YOUR\n" + + "FOREHEAD YOU BEGIN THE LONG TREK BACK\n" + + "HOME. REBUILDING EARTH OUGHT TO BE A\n" + + "LOT MORE FUN THAN RUINING IT WAS.\n"); + +// Before level 31, put this: + public final static String C5TEXT = ("CONGRATULATIONS, YOU'VE FOUND THE SECRET\n" + + "LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n" + + "HUMANS, RATHER THAN DEMONS. YOU WONDER\n" + + "WHO THE INMATES OF THIS CORNER OF HELL\n" + + "WILL BE."); + +// Before level 32, put this: + public final static String C6TEXT = ("CONGRATULATIONS, YOU'VE FOUND THE\n" + + "SUPER SECRET LEVEL! YOU'D BETTER\n" + + "BLAZE THROUGH THIS ONE!\n"); + +// after map 06 + public final static String P1TEXT = ("You gloat over the steaming carcass of the\n" + + "Guardian. With its death, you've wrested\n" + + "the Accelerator from the stinking claws\n" + + "of Hell. You relax and glance around the\n" + + "room. Damn! There was supposed to be at\n" + + "least one working prototype, but you can't\n" + + "see it. The demons must have taken it.\n" + + "\n" + + "You must find the prototype, or all your\n" + + "struggles will have been wasted. Keep\n" + + "moving, keep fighting, keep killing.\n" + + "Oh yes, keep living, too."); + +// after map 11 + public final static String P2TEXT = ("Even the deadly Arch-Vile labyrinth could\n" + + "not stop you, and you've gotten to the\n" + + "prototype Accelerator which is soon\n" + + "efficiently and permanently deactivated.\n" + + "\n" + + "You're good at that kind of thing."); + +// after map 20 + public final static String P3TEXT = ("You've bashed and battered your way into\n" + + "the heart of the devil-hive. Time for a\n" + + "Search-and-Destroy mission, aimed at the\n" + + "Gatekeeper, whose foul offspring is\n" + + "cascading to Earth. Yeah, he's bad. But\n" + + "you know who's worse!\n" + + "\n" + + "Grinning evilly, you check your gear, and\n" + + "get ready to give the bastard a little Hell\n" + + "of your own making!"); + +// after map 30 + public final static String P4TEXT = ("The Gatekeeper's evil face is splattered\n" + + "all over the place. As its tattered corpse\n" + + "collapses, an inverted Gate forms and\n" + + "sucks down the shards of the last\n" + + "prototype Accelerator, not to mention the\n" + + "few remaining demons. You're done. Hell\n" + + "has gone back to pounding bad dead folks \n" + + "instead of good live ones. Remember to\n" + + "tell your grandkids to put a rocket\n" + + "launcher in your coffin. If you go to Hell\n" + + "when you die, you'll need it for some\n" + + "final cleaning-up ..."); + +// before map 31 + public final static String P5TEXT = ("You've found the second-hardest level we\n" + + "got. Hope you have a saved game a level or\n" + + "two previous. If not, be prepared to die\n" + + "aplenty. For master marines only."); + +// before map 32 + public final static String P6TEXT = ("Betcha wondered just what WAS the hardest\n" + + "level we had ready for ya? Now you know.\n" + + "No one gets out alive."); + + public final static String T1TEXT = ("You've fought your way out of the infested\n" + + "experimental labs. It seems that UAC has\n" + + "once again gulped it down. With their\n" + + "high turnover, it must be hard for poor\n" + + "old UAC to buy corporate health insurance\n" + + "nowadays..\n" + + "\n" + + "Ahead lies the military complex, now\n" + + "swarming with diseased horrors hot to get\n" + + "their teeth into you. With luck, the\n" + + "complex still has some warlike ordnance\n" + + "laying around."); + + public final static String T2TEXT = ("You hear the grinding of heavy machinery\n" + + "ahead. You sure hope they're not stamping\n" + + "out new hellspawn, but you're ready to\n" + + "ream out a whole herd if you have to.\n" + + "They might be planning a blood feast, but\n" + + "you feel about as mean as two thousand\n" + + "maniacs packed into one mad killer.\n" + + "\n" + + "You don't plan to go down easy."); + + public final static String T3TEXT = ("The vista opening ahead looks real damn\n" + + "familiar. Smells familiar, too -- like\n" + + "fried excrement. You didn't like this\n" + + "place before, and you sure as hell ain't\n" + + "planning to like it now. The more you\n" + + "brood on it, the madder you get.\n" + + "Hefting your gun, an evil grin trickles\n" + + "onto your face. Time to take some names."); + + public final static String T4TEXT = ("Suddenly, all is silent, from one horizon\n" + + "to the other. The agonizing echo of Hell\n" + + "fades away, the nightmare sky turns to\n" + + "blue, the heaps of monster corpses start \n" + + "to evaporate along with the evil stench \n" + + "that filled the air. Jeeze, maybe you've\n" + + "done it. Have you really won?\n" + + "\n" + + "Something rumbles in the distance.\n" + + "A blue light begins to glow inside the\n" + + "ruined skull of the demon-spitter."); + + public final static String T5TEXT = ("What now? Looks totally different. Kind\n" + + "of like King Tut's condo. Well,\n" + + "whatever's here can't be any worse\n" + + "than usual. Can it? Or maybe it's best\n" + + "to let sleeping gods lie.."); + + public final static String T6TEXT = ("Time for a vacation. You've burst the\n" + + "bowels of hell and by golly you're ready\n" + + "for a break. You mutter to yourself,\n" + + "Maybe someone else can kick Hell's ass\n" + + "next time around. Ahead lies a quiet town,\n" + + "with peaceful flowing water, quaint\n" + + "buildings, and presumably no Hellspawn.\n" + + "\n" + + "As you step off the transport, you hear\n" + + "the stomp of a cyberdemon's iron shoe."); + +// +// Character cast strings F_FINALE.C +// + public final static String CC_ZOMBIE = "ZOMBIEMAN"; + public final static String CC_SHOTGUN = "SHOTGUN GUY"; + public final static String CC_HEAVY = "HEAVY WEAPON DUDE"; + public final static String CC_IMP = "IMP"; + public final static String CC_DEMON = "DEMON"; + public final static String CC_LOST = "LOST SOUL"; + public final static String CC_CACO = "CACODEMON"; + public final static String CC_HELL = "HELL KNIGHT"; + public final static String CC_BARON = "BARON OF HELL"; + public final static String CC_ARACH = "ARACHNOTRON"; + public final static String CC_PAIN = "PAIN ELEMENTAL"; + public final static String CC_REVEN = "REVENANT"; + public final static String CC_MANCU = "MANCUBUS"; + public final static String CC_ARCH = "ARCH-VILE"; + public final static String CC_SPIDER = "THE SPIDER MASTERMIND"; + public final static String CC_CYBER = "THE CYBERDEMON"; + public final static String CC_NAZI = "WAFFEN SS. SIEG HEIL!"; + public final static String CC_KEEN = "COMMANDER KEEN"; + public final static String CC_BARREL = "EXPLODING BARREL"; + public final static String CC_HERO = "OUR HERO"; + +} \ No newline at end of file diff --git a/doom/src/doom/event_t.java b/doom/src/doom/event_t.java new file mode 100644 index 0000000..526735f --- /dev/null +++ b/doom/src/doom/event_t.java @@ -0,0 +1,562 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package doom; + +// Event structure. +import g.Signals.ScanCode; +import java.awt.Point; +import java.awt.Robot; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntPredicate; +import java.util.function.Predicate; +import utils.C2JUtils; + +@FunctionalInterface +public interface event_t { + + int MOUSE_LEFT = 1; + int MOUSE_RIGHT = 2; + int MOUSE_MID = 4; + + int JOY_1 = 1; + int JOY_2 = 2; + int JOY_3 = 4; + int JOY_4 = 8; + + // Special FORCED and PAINFUL key and mouse cancel event. + event_t EMPTY_EVENT = () -> evtype_t.ev_null; + event_t CANCEL_KEYS = () -> evtype_t.ev_clear; + event_t CANCEL_MOUSE = new event_t.mouseevent_t(evtype_t.ev_mouse, 0, 0, 0); + + default boolean hasData() { + return false; + } + + default boolean isKey() { + return false; + } + + default boolean isKey(ScanCode sc) { + return false; + } + + default T mapByKey(Function scMapper) { + return scMapper.apply(null); + } + + default boolean withKey(Consumer scConsumer) { + return false; + } + + default boolean ifKey(Predicate scCondition) { + return false; + } + + default boolean withKeyChar(IntConsumer scCharConsumer) { + return false; + } + + default boolean ifKeyChar(IntPredicate scCharCondition) { + return false; + } + + default boolean withKeyAsciiChar(IntConsumer scAsciiCharConsumer) { + return false; + } + + default boolean ifKeyAsciiChar(IntPredicate scCharCondition) { + return false; + } + + default boolean withKey(Consumer scConsumer, Function extractor) { + return false; + } + + default boolean ifKey(Predicate scCondition, Function extractor) { + return false; + } + + default ScanCode getSC() { + return ScanCode.SC_NULL; + } + + default boolean isMouse() { + return false; + } + + default boolean isMouse(int button) { + return false; + } + + default T mapByMouse(Function mouseMapper) { + return mouseMapper.apply(null); + } + + default boolean withMouse(Consumer mouseConsumer) { + return false; + } + + default boolean ifMouse(Predicate mouseCondition) { + return false; + } + + default boolean withMouse(Consumer mouseConsumer, Function extractor) { + return false; + } + + default boolean ifMouse(Predicate mouseCondition, Function extractor) { + return false; + } + + default boolean isJoy() { + return false; + } + + default boolean isJoy(int button) { + return false; + } + + default T mapByJoy(Function joyMapper) { + return joyMapper.apply(null); + } + + default boolean withJoy(Consumer joyConsumer) { + return false; + } + + default boolean ifJoy(Predicate joyCondition) { + return false; + } + + default boolean withJoy(Consumer joyConsumer, Function extractor) { + return false; + } + + default boolean ifJoy(Predicate joyCondition, Function extractor) { + return false; + } + + evtype_t type(); + + default boolean isType(evtype_t type) { + return type() == type; + } + + default boolean isKey(ScanCode sc, evtype_t type) { + return type() == type && isKey(sc); + } + + default boolean ifKey(evtype_t type, Predicate scCondition) { + if (type() == type) { + return ifKey(scCondition); + } + + return false; + } + + default boolean withKey(evtype_t type, Consumer scConsumer) { + if (type() == type) { + return event_t.this.withKey(scConsumer); + } + + return false; + } + + default boolean withKey(ScanCode sc, evtype_t type, Runnable runnable) { + if (type() == type) { + return withKey(sc, runnable); + } + + return false; + } + + default boolean withKey(ScanCode sc, Runnable runnable) { + if (isKey(sc)) { + runnable.run(); + return true; + } + + return false; + } + + default boolean isMouse(int button, evtype_t type) { + return type() == type && isMouse(button); + } + + default boolean ifMouse(evtype_t type, Predicate mouseCondition) { + if (type() == type) { + return ifMouse(mouseCondition); + } + + return false; + } + + default boolean withMouse(evtype_t type, Consumer mouseConsumer) { + if (type() == type) { + return event_t.this.withMouse(mouseConsumer); + } + + return false; + } + + default boolean withMouse(int button, evtype_t type, Runnable runnable) { + if (type() == type) { + return withMouse(button, runnable); + } + + return false; + } + + default boolean withMouse(int button, Runnable runnable) { + if (isMouse(button)) { + runnable.run(); + return true; + } + + return false; + } + + default boolean isJoy(int button, evtype_t type) { + return type() == type && isJoy(button); + } + + default boolean ifJoy(evtype_t type, Predicate joyCondition) { + if (type() == type) { + return ifJoy(joyCondition); + } + + return false; + } + + default boolean withJoy(evtype_t type, Consumer joyConsumer) { + if (type() == type) { + return event_t.this.withJoy(joyConsumer); + } + + return false; + } + + default boolean withJoy(int button, evtype_t type, Runnable runnable) { + if (type() == type) { + return withJoy(button, runnable); + } + + return false; + } + + default boolean withJoy(int button, Runnable runnable) { + if (isJoy(button)) { + runnable.run(); + return true; + } + + return false; + } + + static int mouseBits(int button) { + switch (button) { + case MouseEvent.BUTTON1: + return MOUSE_LEFT; + case MouseEvent.BUTTON2: + return MOUSE_MID; + case MouseEvent.BUTTON3: + return MOUSE_RIGHT; + } + + return 0; + } + + final class keyevent_t implements event_t { + + public evtype_t type; + public ScanCode sc; + + public keyevent_t(evtype_t type, ScanCode sc) { + this.type = type; + this.sc = sc; + } + + @Override + public boolean hasData() { + return sc != ScanCode.SC_NULL; + } + + @Override + public evtype_t type() { + return type; + } + + @Override + public boolean isKey() { + return true; + } + + @Override + public boolean isKey(ScanCode sc) { + return this.sc == sc; + } + + @Override + public boolean ifKey(Predicate scCondition) { + return scCondition.test(sc); + } + + @Override + public boolean withKey(Consumer scConsumer) { + scConsumer.accept(sc); + return true; + } + + @Override + public boolean ifKeyChar(IntPredicate scCharCondition) { + return scCharCondition.test(sc.c); + } + + @Override + public boolean withKeyChar(IntConsumer scCharConsumer) { + scCharConsumer.accept(sc.c); + return true; + } + + @Override + public boolean ifKeyAsciiChar(IntPredicate scAsciiCharCondition) { + return sc.c > 255 ? false : ifKeyChar(scAsciiCharCondition); + } + + @Override + public boolean withKeyAsciiChar(IntConsumer scAsciiCharConsumer) { + return sc.c > 255 ? false : withKeyChar(scAsciiCharConsumer); + } + + @Override + public boolean ifKey(Predicate scCondition, Function extractor) { + return scCondition.test(extractor.apply(sc)); + } + + @Override + public boolean withKey(Consumer scConsumer, Function extractor) { + scConsumer.accept(extractor.apply(sc)); + return true; + } + + @Override + public T mapByKey(Function scMapper) { + return scMapper.apply(sc); + } + + @Override + public ScanCode getSC() { + return sc; + } + } + + final class mouseevent_t implements event_t { + + public volatile evtype_t type; + public volatile boolean robotMove; + public volatile boolean processed = true; + public volatile int buttons; + public volatile int x, y; + + public mouseevent_t(evtype_t type, int buttons, int x, int y) { + this.type = type; + this.buttons = buttons; + this.x = x; + this.y = y; + } + + @Override + public boolean hasData() { + return buttons != 0; + } + + public void buttonOn(MouseEvent ev) { + buttons |= mouseBits(ev.getButton()); + } + + public void buttonOff(MouseEvent ev) { + buttons ^= mouseBits(ev.getButton()); + } + + public void processedNotify() { + this.processed = true; + } + + public void resetNotify() { + this.processed = false; + } + + public void moveIn(MouseEvent ev, int centreX, int centreY, boolean drag) { + final int mouseX = ev.getX(), mouseY = ev.getY(); + + // Mouse haven't left centre of the window + if (mouseX == centreX && mouseY == centreY) { + return; + } + + // A pure move has no buttons. + if (!drag) { + buttons = 0; + } + + /** + * Now also fix for -fasttic mode + * - Good Sign 2017/05/07 + * + * Fix bug with processing mouse: the DOOM underlying engine does not + * react on the event as fast as it came, they are processed in constant time instead. + * + * In Mocha Doom, mouse events are not generated in bulks and sent to underlying DOOM engine, + * instead the one only mouse event reused and resend modified if was consumed. + * + * So, if we have event system reacting faster then DOOM underlying engine, + * mouse will be harder to move because the new move is forgotten earlier then processed. + * + * As a workaround, do not replace value in moveIn, and increment it instead, + * and only when the underlying engine gives signal it has processed event, we clear x and y + * + * - Good Sign 2017/05/06 + */ + if (processed) { + this.x = (mouseX - centreX) << 2; + this.y = (centreY - mouseY) << 2; + } else { + this.x += (mouseX - centreX) << 2; + this.y += (centreY - mouseY) << 2; + } + } + + public void moveIn(MouseEvent ev, Robot robot, Point windowOffset, int centreX, int centreY, boolean drag) { + moveIn(ev, centreX, centreY, drag); + resetIn(robot, windowOffset, centreX, centreY); + } + + public void resetIn(Robot robot, Point windowOffset, int centreX, int centreY) { + // Mark that the next event will be from robot + robotMove = true; + + // Move the mouse to the window center + robot.mouseMove(windowOffset.x + centreX, windowOffset.y + centreY); + } + + @Override + public evtype_t type() { + return type; + } + + @Override + public boolean isMouse() { + return true; + } + + @Override + public boolean isMouse(int button) { + return C2JUtils.flags(buttons, button); + } + + @Override + public boolean ifMouse(Predicate mouseCondition) { + return mouseCondition.test(this); + } + + @Override + public boolean withMouse(Consumer mouseConsumer) { + mouseConsumer.accept(this); + return true; + } + + @Override + public boolean ifMouse(Predicate mouseCondition, Function extractor) { + return mouseCondition.test(extractor.apply(this)); + } + + @Override + public boolean withMouse(Consumer mouseConsumer, Function extractor) { + mouseConsumer.accept(extractor.apply(this)); + return true; + } + + @Override + public T mapByMouse(Function mouseMapper) { + return mouseMapper.apply(this); + } + } + + final class joyevent_t implements event_t { + + public evtype_t type; + public int buttons; + public int x, y; + + public joyevent_t(evtype_t type, int buttons, int x, int y) { + this.type = type; + this.buttons = buttons; + this.x = x; + this.y = y; + } + + @Override + public boolean hasData() { + return buttons != 0; + } + + @Override + public evtype_t type() { + return type; + } + + @Override + public boolean isJoy() { + return true; + } + + @Override + public boolean isJoy(int button) { + return C2JUtils.flags(buttons, button); + } + + @Override + public boolean ifJoy(Predicate joyCondition) { + return joyCondition.test(this); + } + + @Override + public boolean withJoy(Consumer joyConsumer) { + joyConsumer.accept(this); + return true; + } + + @Override + public boolean ifJoy(Predicate joyCondition, Function extractor) { + return joyCondition.test(extractor.apply(this)); + } + + @Override + public boolean withJoy(Consumer joyConsumer, Function extractor) { + joyConsumer.accept(extractor.apply(this)); + return true; + } + + @Override + public T mapByJoy(Function mouseMapper) { + return mouseMapper.apply(this); + } + } +}; \ No newline at end of file diff --git a/doom/src/doom/evtype_t.java b/doom/src/doom/evtype_t.java new file mode 100644 index 0000000..8227628 --- /dev/null +++ b/doom/src/doom/evtype_t.java @@ -0,0 +1,12 @@ +package doom; + +/** The possible events according to Doom */ +public enum evtype_t { + ev_null, + ev_keydown, + ev_keyup, + ev_mouse, + ev_joystick, + ev_mousewheel, // extension + ev_clear // Forcibly clear all button input (e.g. when losing focus) +}; \ No newline at end of file diff --git a/doom/src/doom/gameaction_t.java b/doom/src/doom/gameaction_t.java new file mode 100644 index 0000000..8590911 --- /dev/null +++ b/doom/src/doom/gameaction_t.java @@ -0,0 +1,15 @@ +package doom; + +public enum gameaction_t { + ga_nothing, + ga_loadlevel, + ga_newgame, + ga_loadgame, + ga_savegame, + ga_playdemo, + ga_completed, + ga_victory, + ga_worlddone, + ga_screenshot, + ga_failure // HACK: communicate failures silently +} \ No newline at end of file diff --git a/doom/src/doom/items.java b/doom/src/doom/items.java new file mode 100644 index 0000000..7843e9a --- /dev/null +++ b/doom/src/doom/items.java @@ -0,0 +1,132 @@ +package doom; +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: items.java,v 1.3 2010/12/20 17:15:08 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: items.java,v $ +// Revision 1.3 2010/12/20 17:15:08 velktron +// Made the renderer more OO -> TextureManager and other changes as well. +// +// Revision 1.2 2010/08/19 23:14:49 velktron +// Automap +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// +//----------------------------------------------------------------------------- + +import defines.ammotype_t; +import defines.statenum_t; + +public class items { + + public static weaponinfo_t[] weaponinfo + = { + new weaponinfo_t( + // fist + ammotype_t.am_noammo, + statenum_t.S_PUNCHUP, + statenum_t.S_PUNCHDOWN, + statenum_t.S_PUNCH, + statenum_t.S_PUNCH1, + statenum_t.S_NULL + ), + new weaponinfo_t( + // pistol + ammotype_t.am_clip, + statenum_t.S_PISTOLUP, + statenum_t.S_PISTOLDOWN, + statenum_t.S_PISTOL, + statenum_t.S_PISTOL1, + statenum_t.S_PISTOLFLASH + ), new weaponinfo_t( + // shotgun + ammotype_t.am_shell, + statenum_t.S_SGUNUP, + statenum_t.S_SGUNDOWN, + statenum_t.S_SGUN, + statenum_t.S_SGUN1, + statenum_t.S_SGUNFLASH1 + ), + new weaponinfo_t( + // chaingun + ammotype_t.am_clip, + statenum_t.S_CHAINUP, + statenum_t.S_CHAINDOWN, + statenum_t.S_CHAIN, + statenum_t.S_CHAIN1, + statenum_t.S_CHAINFLASH1 + ), + new weaponinfo_t( + // missile launcher + ammotype_t.am_misl, + statenum_t.S_MISSILEUP, + statenum_t.S_MISSILEDOWN, + statenum_t.S_MISSILE, + statenum_t.S_MISSILE1, + statenum_t.S_MISSILEFLASH1 + ), + new weaponinfo_t( + // plasma rifle + ammotype_t.am_cell, + statenum_t.S_PLASMAUP, + statenum_t.S_PLASMADOWN, + statenum_t.S_PLASMA, + statenum_t.S_PLASMA1, + statenum_t.S_PLASMAFLASH1 + ), + new weaponinfo_t( + // bfg 9000 + ammotype_t.am_cell, + statenum_t.S_BFGUP, + statenum_t.S_BFGDOWN, + statenum_t.S_BFG, + statenum_t.S_BFG1, + statenum_t.S_BFGFLASH1 + ), + new weaponinfo_t( + // chainsaw + ammotype_t.am_noammo, + statenum_t.S_SAWUP, + statenum_t.S_SAWDOWN, + statenum_t.S_SAW, + statenum_t.S_SAW1, + statenum_t.S_NULL + ), + new weaponinfo_t( + // super shotgun + ammotype_t.am_shell, + statenum_t.S_DSGUNUP, + statenum_t.S_DSGUNDOWN, + statenum_t.S_DSGUN, + statenum_t.S_DSGUN1, + statenum_t.S_DSGUNFLASH1 + ) + }; +} \ No newline at end of file diff --git a/doom/src/doom/net.java b/doom/src/doom/net.java new file mode 100644 index 0000000..994546a --- /dev/null +++ b/doom/src/doom/net.java @@ -0,0 +1,803 @@ +package doom; +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: net.java,v 1.5 2011/02/11 00:11:13 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: net.java,v $ +// Revision 1.5 2011/02/11 00:11:13 velktron +// A MUCH needed update to v1.3. +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// DOOM Network game communication and protocol, +// all OS independent parts. +// +//----------------------------------------------------------------------------- + +//static const char rcsid[] = "$Id: net.java,v 1.5 2011/02/11 00:11:13 velktron Exp $"; +//#include "m_menu.h" +//#include "i_system.h" +//#include "i_video.h" +//#include "i_net.h" +//#include "g_game.h" +// +//Network play related stuff. +//There is a data struct that stores network +//communication related stuff, and another +//one that defines the actual packets to +//be transmitted. +// +public class net { + + protected static int NCMD_EXIT = 0x80000000; + protected static int NCMD_RETRANSMIT = 0x40000000; + protected static int NCMD_SETUP = 0x20000000; + protected static int NCMD_KILL = 0x10000000; // kill game + protected static int NCMD_CHECKSUM = 0x0fffffff; + + protected static int DOOMCOM_ID = 0x12345678; + +//Max computers/players in a game. + protected static int MAXNETNODES = 8; + +//Networking and tick handling related. + protected static int BACKUPTICS = 12; + +// commant_t + protected static int CMD_SEND = 1; + protected static int CMD_GET = 2; + + doomcom_t doomcom; + doomdata_t netbuffer; // points inside doomcom + +// +// NETWORKING +// +// gametic is the tic about to (or currently being) run +// maketic is the tick that hasn't had control made for it yet +// nettics[] has the maketics for all players +// +// a gametic cannot be run until nettics[] > gametic for all players +// + public static int RESENDCOUNT = 10; + public static int PL_DRONE = 0x80; // bit flag in doomdata->player + + ticcmd_t[] localcmds = new ticcmd_t[BACKUPTICS]; + + final int MAXPLAYERS = 4; + + ticcmd_t[][] netcmds = new ticcmd_t[MAXPLAYERS][BACKUPTICS]; + int[] nettics = new int[MAXNETNODES]; + boolean[] nodeingame = new boolean[MAXNETNODES]; // set false as nodes leave game + boolean[] remoteresend = new boolean[MAXNETNODES]; // set when local needs tics + int[] resendto = new int[MAXNETNODES]; // set when remote needs tics + int[] resendcount = new int[MAXNETNODES]; + + int[] nodeforplayer = new int[MAXPLAYERS]; + + int maketic; + int lastnettic; + int skiptics; + int ticdup; + int maxsend; // BACKUPTICS/(2*ticdup)-1 + +//void D_ProcessEvents (void); +//void G_BuildTiccmd (ticcmd_t *cmd); +//void D_DoAdvanceDemo (void); + boolean reboundpacket; + doomdata_t reboundstore; + +// +// +//123 + /** MAES: interesting. After testing it was found to return the following size: + * + */ + int NetbufferSize() { +// return (int)(((doomdata_t)0).cmds[netbuffer.numtics]); + return (8 * (netbuffer.numtics + 1)); + } + +} +/* + +//1 +// Checksum +// +unsigned NetbufferChecksum (void) +{ + unsigned c; + int i,l; + + c = 0x1234567; + + // FIXME -endianess? +#ifdef NORMALUNIX + return 0; // byte order problems +#endif + + l = (NetbufferSize () - (int)&(((doomdata_t *)0)->retransmitfrom))/4; + for (i=0 ; iretransmitfrom)[i] * (i+1); + + return c & NCMD_CHECKSUM; +} + +// +// +// +int ExpandTics (int low) +{ + int delta; + + delta = low - (maketic&0xff); + + if (delta >= -64 && delta <= 64) + return (maketic&~0xff) + low; + if (delta > 64) + return (maketic&~0xff) - 256 + low; + if (delta < -64) + return (maketic&~0xff) + 256 + low; + + I_Error ("ExpandTics: strange value %i at maketic %i",low,maketic); + return 0; +} + + + +// +// HSendPacket +// +void +HSendPacket + (int node, + int flags ) +{ + netbuffer->checksum = NetbufferChecksum () | flags; + + if (!node) + { + reboundstore = *netbuffer; + reboundpacket = true; + return; + } + + if (demoplayback) + return; + + if (!netgame) + I_Error ("Tried to transmit to another node"); + + doomcom->command = CMD_SEND; + doomcom->remotenode = node; + doomcom->datalength = NetbufferSize (); + + if (debugfile) + { + int i; + int realretrans; + if (netbuffer->checksum & NCMD_RETRANSMIT) + realretrans = ExpandTics (netbuffer->retransmitfrom); + else + realretrans = -1; + + fprintf (debugfile,"send (%i + %i, R %i) [%i] ", + ExpandTics(netbuffer->starttic), + netbuffer->numtics, realretrans, doomcom->datalength); + + for (i=0 ; idatalength ; i++) + fprintf (debugfile,"%i ",((byte *)netbuffer)[i]); + + fprintf (debugfile,"\n"); + } + + I_NetCmd (); +} + +// +// HGetPacket +// Returns false if no packet is waiting +// +boolean HGetPacket (void) +{ + if (reboundpacket) + { + *netbuffer = reboundstore; + doomcom->remotenode = 0; + reboundpacket = false; + return true; + } + + if (!netgame) + return false; + + if (demoplayback) + return false; + + doomcom->command = CMD_GET; + I_NetCmd (); + + if (doomcom->remotenode == -1) + return false; + + if (doomcom->datalength != NetbufferSize ()) + { + if (debugfile) + fprintf (debugfile,"bad packet length %i\n",doomcom->datalength); + return false; + } + + if (NetbufferChecksum () != (netbuffer->checksum&NCMD_CHECKSUM) ) + { + if (debugfile) + fprintf (debugfile,"bad packet checksum\n"); + return false; + } + + if (debugfile) + { + int realretrans; + int i; + + if (netbuffer->checksum & NCMD_SETUP) + fprintf (debugfile,"setup packet\n"); + else + { + if (netbuffer->checksum & NCMD_RETRANSMIT) + realretrans = ExpandTics (netbuffer->retransmitfrom); + else + realretrans = -1; + + fprintf (debugfile,"get %i = (%i + %i, R %i)[%i] ", + doomcom->remotenode, + ExpandTics(netbuffer->starttic), + netbuffer->numtics, realretrans, doomcom->datalength); + + for (i=0 ; idatalength ; i++) + fprintf (debugfile,"%i ",((byte *)netbuffer)[i]); + fprintf (debugfile,"\n"); + } + } + return true; +} + + +// +// GetPackets +// +char exitmsg[80]; + +void GetPackets (void) +{ + int netconsole; + int netnode; + ticcmd_t *src, *dest; + int realend; + int realstart; + + while ( HGetPacket() ) + { + if (netbuffer->checksum & NCMD_SETUP) + continue; // extra setup packet + + netconsole = netbuffer->player & ~PL_DRONE; + netnode = doomcom->remotenode; + + // to save bytes, only the low byte of tic numbers are sent + // Figure out what the rest of the bytes are + realstart = ExpandTics (netbuffer->starttic); + realend = (realstart+netbuffer->numtics); + + // check for exiting the game + if (netbuffer->checksum & NCMD_EXIT) + { + if (!nodeingame[netnode]) + continue; + nodeingame[netnode] = false; + playeringame[netconsole] = false; + strcpy (exitmsg, "Player 1 left the game"); + exitmsg[7] += netconsole; + players[consoleplayer].message = exitmsg; + if (demorecording) + G_CheckDemoStatus (); + continue; + } + + // check for a remote game kill + if (netbuffer->checksum & NCMD_KILL) + I_Error ("Killed by network driver"); + + nodeforplayer[netconsole] = netnode; + + // check for retransmit request + if ( resendcount[netnode] <= 0 + && (netbuffer->checksum & NCMD_RETRANSMIT) ) + { + resendto[netnode] = ExpandTics(netbuffer->retransmitfrom); + if (debugfile) + fprintf (debugfile,"retransmit from %i\n", resendto[netnode]); + resendcount[netnode] = RESENDCOUNT; + } + else + resendcount[netnode]--; + + // check for out of order / duplicated packet + if (realend == nettics[netnode]) + continue; + + if (realend < nettics[netnode]) + { + if (debugfile) + fprintf (debugfile, + "out of order packet (%i + %i)\n" , + realstart,netbuffer->numtics); + continue; + } + + // check for a missed packet + if (realstart > nettics[netnode]) + { + // stop processing until the other system resends the missed tics + if (debugfile) + fprintf (debugfile, + "missed tics from %i (%i - %i)\n", + netnode, realstart, nettics[netnode]); + remoteresend[netnode] = true; + continue; + } + + // update command store from the packet + { + int start; + + remoteresend[netnode] = false; + + start = nettics[netnode] - realstart; + src = &netbuffer->cmds[start]; + + while (nettics[netnode] < realend) + { + dest = &netcmds[netconsole][nettics[netnode]%BACKUPTICS]; + nettics[netnode]++; + *dest = *src; + src++; + } + } + } +} + + +// +// NetUpdate +// Builds ticcmds for console player, +// sends out a packet +// +int gametime; + +void NetUpdate (void) +{ + int nowtime; + int newtics; + int i,j; + int realstart; + int gameticdiv; + + // check time + nowtime = I_GetTime ()/ticdup; + newtics = nowtime - gametime; + gametime = nowtime; + + if (newtics <= 0) // nothing new to update + goto listen; + + if (skiptics <= newtics) + { + newtics -= skiptics; + skiptics = 0; + } + else + { + skiptics -= newtics; + newtics = 0; + } + + + netbuffer->player = consoleplayer; + + // build new ticcmds for console player + gameticdiv = gametic/ticdup; + for (i=0 ; i= BACKUPTICS/2-1) + break; // can't hold any more + + //printf ("mk:%i ",maketic); + G_BuildTiccmd (&localcmds[maketic%BACKUPTICS]); + maketic++; + } + + + if (singletics) + return; // singletic update is syncronous + + // send the packet to the other nodes + for (i=0 ; inumnodes ; i++) + if (nodeingame[i]) + { + netbuffer->starttic = realstart = resendto[i]; + netbuffer->numtics = maketic - realstart; + if (netbuffer->numtics > BACKUPTICS) + I_Error ("NetUpdate: netbuffer->numtics > BACKUPTICS"); + + resendto[i] = maketic - doomcom->extratics; + + for (j=0 ; j< netbuffer->numtics ; j++) + netbuffer->cmds[j] = + localcmds[(realstart+j)%BACKUPTICS]; + + if (remoteresend[i]) + { + netbuffer->retransmitfrom = nettics[i]; + HSendPacket (i, NCMD_RETRANSMIT); + } + else + { + netbuffer->retransmitfrom = 0; + HSendPacket (i, 0); + } + } + + // listen for other packets + listen: + GetPackets (); +} + + + +// +// CheckAbort +// +void CheckAbort (void) +{ + event_t *ev; + int stoptic; + + stoptic = I_GetTime () + 2; + while (I_GetTime() < stoptic) + I_StartTic (); + + I_StartTic (); + for ( ; eventtail != eventhead + ; eventtail = (++eventtail)&(MAXEVENTS-1) ) + { + ev = &events[eventtail]; + if (ev->type == ev_keydown && ev->data1 == KEY_ESCAPE) + I_Error ("Network game synchronization aborted."); + } +} + + +// +// D_ArbitrateNetStart +// +void D_ArbitrateNetStart (void) +{ + int i; + boolean gotinfo[MAXNETNODES]; + + autostart = true; + memset (gotinfo,0,sizeof(gotinfo)); + + if (doomcom->consoleplayer) + { + // listen for setup info from key player + printf ("listening for network start info...\n"); + while (1) + { + CheckAbort (); + if (!HGetPacket ()) + continue; + if (netbuffer->checksum & NCMD_SETUP) + { + if (netbuffer->player != VERSION) + I_Error ("Different DOOM versions cannot play a net game!"); + startskill = netbuffer->retransmitfrom & 15; + deathmatch = (netbuffer->retransmitfrom & 0xc0) >> 6; + nomonsters = (netbuffer->retransmitfrom & 0x20) > 0; + respawnparm = (netbuffer->retransmitfrom & 0x10) > 0; + startmap = netbuffer->starttic & 0x3f; + startepisode = netbuffer->starttic >> 6; + return; + } + } + } + else + { + // key player, send the setup info + printf ("sending network start info...\n"); + do + { + CheckAbort (); + for (i=0 ; inumnodes ; i++) + { + netbuffer->retransmitfrom = startskill; + if (deathmatch) + netbuffer->retransmitfrom |= (deathmatch<<6); + if (nomonsters) + netbuffer->retransmitfrom |= 0x20; + if (respawnparm) + netbuffer->retransmitfrom |= 0x10; + netbuffer->starttic = startepisode * 64 + startmap; + netbuffer->player = VERSION; + netbuffer->numtics = 0; + HSendPacket (i, NCMD_SETUP); + } + +#if 1 + for(i = 10 ; i && HGetPacket(); --i) + { + if((netbuffer->player&0x7f) < MAXNETNODES) + gotinfo[netbuffer->player&0x7f] = true; + } +#else + while (HGetPacket ()) + { + gotinfo[netbuffer->player&0x7f] = true; + } +#endif + + for (i=1 ; inumnodes ; i++) + if (!gotinfo[i]) + break; + } while (i < doomcom->numnodes); + } +} + +// +// D_CheckNetGame +// Works out player numbers among the net participants +// +extern int viewangleoffset; + +void D_CheckNetGame (void) +{ + int i; + + for (i=0 ; iid != DOOMCOM_ID) + I_Error ("Doomcom buffer invalid!"); + + netbuffer = &doomcom->data; + consoleplayer = displayplayer = doomcom->consoleplayer; + if (netgame) + D_ArbitrateNetStart (); + + printf ("startskill %i deathmatch: %i startmap: %i startepisode: %i\n", + startskill, deathmatch, startmap, startepisode); + + // read values out of doomcom + ticdup = doomcom->ticdup; + maxsend = BACKUPTICS/(2*ticdup)-1; + if (maxsend<1) + maxsend = 1; + + for (i=0 ; inumplayers ; i++) + playeringame[i] = true; + for (i=0 ; inumnodes ; i++) + nodeingame[i] = true; + + printf ("player %i of %i (%i nodes)\n", + consoleplayer+1, doomcom->numplayers, doomcom->numnodes); + +} + + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other players +// +void D_QuitNetGame (void) +{ + int i, j; + + if (debugfile) + fclose (debugfile); + + if (!netgame || !usergame || consoleplayer == -1 || demoplayback) + return; + + // send a bunch of packets for security + netbuffer->player = consoleplayer; + netbuffer->numtics = 0; + for (i=0 ; i<4 ; i++) + { + for (j=1 ; jnumnodes ; j++) + if (nodeingame[j]) + HSendPacket (j, NCMD_EXIT); + I_WaitVBL (1); + } +} + + + +// +// TryRunTics +// +int frametics[4]; +int frameon; +int frameskip[4]; +int oldnettics; + +extern boolean advancedemo; + +public static void TryRunTics () +{ + int i; + int lowtic; + int entertic; + static int oldentertics; + int realtics; + int availabletics; + int counts; + int numplaying; + + // get real tics + entertic = I_GetTime ()/ticdup; + realtics = entertic - oldentertics; + oldentertics = entertic; + + // get available tics + NetUpdate (); + + lowtic = MAXINT; + numplaying = 0; + for (i=0 ; inumnodes ; i++) + { + if (nodeingame[i]) + { + numplaying++; + if (nettics[i] < lowtic) + lowtic = nettics[i]; + } + } + availabletics = lowtic - gametic/ticdup; + + // decide how many tics to run + if (realtics < availabletics-1) + counts = realtics+1; + else if (realtics < availabletics) + counts = realtics; + else + counts = availabletics; + + if (counts < 1) + counts = 1; + + frameon++; + + if (debugfile) + fprintf (debugfile, + "=======real: %i avail: %i game: %i\n", + realtics, availabletics,counts); + + if (!demoplayback) + { + // ideally nettics[0] should be 1 - 3 tics above lowtic + // if we are consistantly slower, speed up time + for (i=0 ; i nettics[nodeforplayer[i]]); + oldnettics = nettics[0]; + if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) + { + skiptics = 1; + // printf ("+"); + } + } + }// demoplayback + + // wait for new tics if needed + while (lowtic < gametic/ticdup + counts) + { + NetUpdate (); + lowtic = MAXINT; + + for (i=0 ; inumnodes ; i++) + if (nodeingame[i] && nettics[i] < lowtic) + lowtic = nettics[i]; + + if (lowtic < gametic/ticdup) + I_Error ("TryRunTics: lowtic < gametic"); + + // don't stay in here forever -- give the menu a chance to work + if (I_GetTime ()/ticdup - entertic >= 20) + { + M_Ticker (); + return; + } + } + + // run the count * ticdup dics + while (counts--) + { + for (i=0 ; i lowtic) + I_Error ("gametic>lowtic"); + if (advancedemo) + D_DoAdvanceDemo (); + M_Ticker (); + G_Ticker (); + gametic++; + + // modify command for duplicated tics + if (i != ticdup-1) + { + ticcmd_t *cmd; + int buf; + int j; + + buf = (gametic/ticdup)%BACKUPTICS; + for (j=0 ; jchatchar = 0; + if (cmd->buttons & BT_SPECIAL) + cmd->buttons = 0; + } + } + } + NetUpdate (); // check for new console commands + } +} +}*/ \ No newline at end of file diff --git a/doom/src/doom/pic_t.java b/doom/src/doom/pic_t.java new file mode 100644 index 0000000..6787188 --- /dev/null +++ b/doom/src/doom/pic_t.java @@ -0,0 +1,15 @@ +package doom; + +public class pic_t { + + public pic_t(byte width, byte height, byte data) { + super(); + this.width = width; + this.height = height; + this.data = data; + } + + public byte width; + public byte height; + public byte data; +} \ No newline at end of file diff --git a/doom/src/doom/player_t.java b/doom/src/doom/player_t.java new file mode 100644 index 0000000..2dfed4c --- /dev/null +++ b/doom/src/doom/player_t.java @@ -0,0 +1,1565 @@ +package doom; + +import static data.Defines.BT_CHANGE; +import static data.Defines.BT_SPECIAL; +import static data.Defines.BT_USE; +import static data.Defines.BT_WEAPONMASK; +import static data.Defines.BT_WEAPONSHIFT; +import static data.Defines.INFRATICS; +import static data.Defines.INVISTICS; +import static data.Defines.INVULNTICS; +import static data.Defines.IRONTICS; +import static data.Defines.NUMAMMO; +import static data.Defines.NUMPOWERS; +import static data.Defines.NUMWEAPONS; +import static data.Defines.PST_DEAD; +import static data.Defines.PST_LIVE; +import static data.Defines.PST_REBORN; +import static data.Defines.TIC_MUL; +import static data.Defines.TOCENTER; +import static data.Defines.VIEWHEIGHT; +import static data.Defines.pw_infrared; +import static data.Defines.pw_invisibility; +import static data.Defines.pw_invulnerability; +import static data.Defines.pw_ironfeet; +import static data.Defines.pw_strength; +import static data.Limits.MAXHEALTH; +import static data.Limits.MAXPLAYERS; +import data.Tables; +import static data.Tables.ANG180; +import static data.Tables.ANG90; +import static data.Tables.BITS32; +import static data.Tables.FINEANGLES; +import static data.Tables.FINEMASK; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.states; +import data.sounds.sfxenum_t; +import data.state_t; +import defines.ammotype_t; +import defines.card_t; +import defines.skill_t; +import defines.statenum_t; +import doom.SourceCode.G_Game; +import static doom.SourceCode.G_Game.G_PlayerFinishLevel; +import static doom.SourceCode.G_Game.G_PlayerReborn; +import doom.SourceCode.P_Pspr; +import static doom.SourceCode.P_Pspr.P_BringUpWeapon; +import static doom.SourceCode.P_Pspr.P_SetPsprite; +import static doom.SourceCode.P_Pspr.P_SetupPsprites; +import static doom.items.weaponinfo; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static m.fixed_t.MAPFRACUNIT; +import p.ActiveStates.PlayerSpriteConsumer; +import p.mobj_t; +import static p.mobj_t.MF_JUSTATTACKED; +import static p.mobj_t.MF_NOCLIP; +import static p.mobj_t.MF_SHADOW; +import p.pspdef_t; +import rr.sector_t; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.flags; +import static utils.C2JUtils.memset; +import static utils.C2JUtils.pointer; +import static utils.GenericCopy.malloc; +import v.graphics.Palettes; +import w.DoomBuffer; +import w.DoomIO; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +/** + * Extended player object info: player_t The player data structure depends on a + * number of other structs: items (internal inventory), animation states + * (closely tied to the sprites used to represent them, unfortunately). + * + * #include "d_items.h" + * #include "p_pspr.h" + * + * In addition, the player is just a special + * case of the generic moving object/actor. + * NOTE: this doesn't mean it needs to extend it, although it would be + * possible. + * + * #include "p_mobj.h" + * + * Finally, for odd reasons, the player input is buffered within + * the player data struct, as commands per game tick. + * + * #include "d_ticcmd.h" + */ +public class player_t /*extends mobj_t */ implements Cloneable, IReadableDoomObject, IPackableDoomObject { + + /** + * Probably doomguy needs to know what the fuck is going on + */ + private final DoomMain DOOM; + + /* Fugly hack to "reset" the player. Not worth the fugliness. + public static player_t nullplayer; + static { + nullplayer = new player_t(); + } + */ + public player_t(DoomMain DOOM) { + this.DOOM = DOOM; + powers = new int[NUMPOWERS]; + frags = new int[MAXPLAYERS]; + ammo = new int[NUMAMMO]; + //maxammo = new int[NUMAMMO]; + maxammo = new int[NUMAMMO]; + cards = new boolean[card_t.NUMCARDS.ordinal()]; + weaponowned = new boolean[NUMWEAPONS]; + psprites = malloc(pspdef_t::new, pspdef_t[]::new, NUMPSPRITES); + this.mo = mobj_t.createOn(DOOM); + // If a player doesn't reference himself through his object, he will have an existential crisis. + this.mo.player = this; + readyweapon = weapontype_t.wp_fist; + this.cmd = new ticcmd_t(); + //weaponinfo=new weaponinfo_t(); + } + + public final static int CF_NOCLIP = 1; // No damage, no health loss. + + public final static int CF_GODMODE = 2; + + public final static int CF_NOMOMENTUM = 4; // Not really a cheat, just a debug aid. + + /** + * The "mobj state" of the player is stored here, even though he "inherits" + * all mobj_t properties (except being a thinker). However, for good or bad, + * his mobj properties are modified by accessing player.mo + */ + public mobj_t mo; + + /** + * playerstate_t + */ + public int playerstate; + + public ticcmd_t cmd; + + /** + * Determine POV, including viewpoint bobbing during movement. (fixed_t) + * Focal origin above r.z + */ + public int viewz; + + /** + * (fixed_t) Base height above floor for viewz. + */ + public int viewheight; + + /** + * (fixed_t) Bob/squat speed. + */ + public int deltaviewheight; + + /** + * (fixed_t) bounded/scaled total momentum. + */ + public int bob; + + // Heretic stuff + public int flyheight; + public int lookdir; + public boolean centering; + + /** + * This is only used between levels, mo->health is used during levels. + * CORRECTION: this is also used by the automap widget. + * MAES: fugly hax, as even passing "Integers" won't work, as they are immutable. + * Fuck that, I'm doing it the fugly MPI Java way! + */ + public int[] health = new int[1]; + + /** + * has to be passed around :-( + */ + public int[] armorpoints = new int[1]; + + /** + * Armor type is 0-2. + */ + public int armortype; + + /** + * Power ups. invinc and invis are tic counters. + */ + public int[] powers; + + public boolean[] cards; + + public boolean backpack; + + // Frags, kills of other players. + public int[] frags; + + public weapontype_t readyweapon; + + // Is wp_nochange if not changing. + public weapontype_t pendingweapon; + + public boolean[] weaponowned; + + public int[] ammo; + + public int[] maxammo; + + /** + * True if button down last tic. + */ + public boolean attackdown; + + public boolean usedown; + + // Bit flags, for cheats and debug. + // See cheat_t, above. + public int cheats; + + // Refired shots are less accurate. + public int refire; + + // For intermission stats. + public int killcount; + + public int itemcount; + + public int secretcount; + + // Hint messages. + public String message; + + // For screen flashing (red or bright). + public int damagecount; + + public int bonuscount; + + // Who did damage (NULL for floors/ceilings). + public mobj_t attacker; + + // So gun flashes light up areas. + public int extralight; + + /** + * Current PLAYPAL, ??? can be set to REDCOLORMAP for pain, etc. MAES: "int" + * my ass. It's yet another pointer alias into colormaps. Ergo, array and + * pointer. + */ + // public byte[] fixedcolormap; + /** + * *NOT* preshifted index of colormap in light color maps. + * It could be written when the player_t object is packed. Dont shift this value, + * do shifts after retrieving this. + */ + public int fixedcolormap; + + // Player skin colorshift, + // 0-3 for which color to draw player. + public int colormap; + + // TODO: Overlay view sprites (gun, etc). + public pspdef_t[] psprites; + + // True if secret level has been done. + public boolean didsecret; + + /** + * It's probably faster to clone the null player + */ + public void reset() { + memset(ammo, 0, ammo.length); + memset(armorpoints, 0, armorpoints.length); + memset(cards, false, cards.length); + memset(frags, 0, frags.length); + memset(health, 0, health.length); + memset(maxammo, 0, maxammo.length); + memset(powers, 0, powers.length); + memset(weaponowned, false, weaponowned.length); + //memset(psprites, null, psprites.length); + this.cheats = 0; // Forgot to clear up cheats flag... + this.armortype = 0; + this.attackdown = false; + this.attacker = null; + this.backpack = false; + this.bob = 0; + } + + @Override + public player_t clone() + throws CloneNotSupportedException { + return (player_t) super.clone(); + } + + /** + * 16 pixels of bob + */ + private static int MAXBOB = 0x100000; + + /** + * P_Thrust Moves the given origin along a given angle. + * + * @param angle + * (angle_t) + * @param move + * (fixed_t) + */ + public void Thrust(long angle, int move) { + mo.momx += FixedMul(move, finecosine(angle)); + mo.momy += FixedMul(move, finesine(angle)); + } + + protected final static int PLAYERTHRUST = 2048 / TIC_MUL; + + /** + * P_MovePlayer + */ + public void MovePlayer() { + ticcmd_t cmd = this.cmd; + + mo.angle += (cmd.angleturn << 16); + mo.angle &= BITS32; + + // Do not let the player control movement + // if not onground. + onground = (mo.z <= mo.floorz); + + if (cmd.forwardmove != 0 && onground) { + Thrust(mo.angle, cmd.forwardmove * PLAYERTHRUST); + } + + if (cmd.sidemove != 0 && onground) { + Thrust((mo.angle - ANG90) & BITS32, cmd.sidemove * PLAYERTHRUST); + } + + if ((cmd.forwardmove != 0 || cmd.sidemove != 0) + && mo.mobj_state == states[statenum_t.S_PLAY.ordinal()]) { + this.mo.SetMobjState(statenum_t.S_PLAY_RUN1); + } + + // Freelook code ripped off Heretic. Sieg heil! + int look = cmd.lookfly & 15; + + if (look > 7) { + look -= 16; + } + if (look != 0) { + if (look == TOCENTER) { + centering = true; + } else { + lookdir += 5 * look; + if (lookdir > 90 || lookdir < -110) { + lookdir -= 5 * look; + } + } + } + + // Centering is done over several tics + if (centering) { + if (lookdir > 0) { + lookdir -= 8; + } else if (lookdir < 0) { + lookdir += 8; + } + if (Math.abs(lookdir) < 8) { + lookdir = 0; + centering = false; + } + } + /* Flight stuff from Heretic + fly = cmd.lookfly>>4; + + if(fly > 7) + { + fly -= 16; + } + if(fly && player->powers[pw_flight]) + { + if(fly != TOCENTER) + { + player->flyheight = fly*2; + if(!(player->mo->flags2&MF2_FLY)) + { + player->mo->flags2 |= MF2_FLY; + player->mo->flags |= MF_NOGRAVITY; + } + } + else + { + player->mo->flags2 &= ~MF2_FLY; + player->mo->flags &= ~MF_NOGRAVITY; + } + } + else if(fly > 0) + { + P_PlayerUseArtifact(player, arti_fly); + } + if(player->mo->flags2&MF2_FLY) + { + player->mo->momz = player->flyheight*FRACUNIT; + if(player->flyheight) + { + player->flyheight /= 2; + } + } */ + } + + // + // GET STUFF + // + // a weapon is found with two clip loads, + // a big item has five clip loads + public static final int[] clipammo = {10, 4, 20, 1}; + + /** + * P_GiveAmmo Num is the number of clip loads, not the individual count (0= + * 1/2 clip). + * + * @return false if the ammo can't be picked up at all + * @param ammo + * intended to be ammotype_t. + */ + public boolean GiveAmmo(ammotype_t amm, int num) { + int oldammo; + int ammo = amm.ordinal(); + if (ammo == ammotype_t.am_noammo.ordinal()) { + return false; + } + + if (ammo < 0 || ammo > NUMAMMO) { + DOOM.doomSystem.Error("P_GiveAmmo: bad type %d", ammo); + } + + if (this.ammo[ammo] == maxammo[ammo]) { + return false; + } + + if (num != 0) { + num *= clipammo[ammo]; + } else { + num = clipammo[ammo] / 2; + } + + if (DOOM.gameskill == skill_t.sk_baby + || DOOM.gameskill == skill_t.sk_nightmare) { + // give double ammo in trainer mode, + // you'll need in nightmare + num <<= 1; + } + + oldammo = this.ammo[ammo]; + this.ammo[ammo] += num; + + if (this.ammo[ammo] > maxammo[ammo]) { + this.ammo[ammo] = maxammo[ammo]; + } + + // If non zero ammo, + // don't change up weapons, + // player was lower on purpose. + if (oldammo != 0) { + return true; + } + + // We were down to zero, + // so select a new weapon. + // Preferences are not user selectable. + switch (ammotype_t.values()[ammo]) { + case am_clip: + if (readyweapon == weapontype_t.wp_fist) { + if (weaponowned[weapontype_t.wp_chaingun.ordinal()]) { + pendingweapon = weapontype_t.wp_chaingun; + } else { + pendingweapon = weapontype_t.wp_pistol; + } + } + break; + + case am_shell: + if (readyweapon == weapontype_t.wp_fist + || readyweapon == weapontype_t.wp_pistol) { + if (weaponowned[weapontype_t.wp_shotgun.ordinal()]) { + pendingweapon = weapontype_t.wp_shotgun; + } + } + break; + + case am_cell: + if (readyweapon == weapontype_t.wp_fist + || readyweapon == weapontype_t.wp_pistol) { + if (weaponowned[weapontype_t.wp_plasma.ordinal()]) { + pendingweapon = weapontype_t.wp_plasma; + } + } + break; + + case am_misl: + if (readyweapon == weapontype_t.wp_fist) { + if (weaponowned[weapontype_t.wp_missile.ordinal()]) { + pendingweapon = weapontype_t.wp_missile; + } + } + default: + break; + } + + return true; + } + + public static final int BONUSADD = 6; + + /** + * P_GiveWeapon + * The weapon name may have a MF_DROPPED flag ored in. + */ + public boolean GiveWeapon(weapontype_t weapn, boolean dropped) { + boolean gaveammo; + boolean gaveweapon; + int weapon = weapn.ordinal(); + + if (DOOM.netgame && (DOOM.deathmatch != true) // ???? was "2" + && !dropped) { + // leave placed weapons forever on net games + if (weaponowned[weapon]) { + return false; + } + + bonuscount += BONUSADD; + weaponowned[weapon] = true; + + if (DOOM.deathmatch) { + GiveAmmo(weaponinfo[weapon].ammo, 5); + } else { + GiveAmmo(weaponinfo[weapon].ammo, 2); + } + pendingweapon = weapn; + + if (this == DOOM.players[DOOM.consoleplayer]) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_wpnup); + } + return false; + } + + if (weaponinfo[weapon].ammo != ammotype_t.am_noammo) { + // give one clip with a dropped weapon, + // two clips with a found weapon + if (dropped) { + gaveammo = GiveAmmo(weaponinfo[weapon].ammo, 1); + } else { + gaveammo = GiveAmmo(weaponinfo[weapon].ammo, 2); + } + } else { + gaveammo = false; + } + + if (weaponowned[weapon]) { + gaveweapon = false; + } else { + gaveweapon = true; + weaponowned[weapon] = true; + pendingweapon = weapn; + } + + return (gaveweapon || gaveammo); + } + + /** + * P_GiveBody Returns false if the body isn't needed at all + */ + public boolean GiveBody(int num) { + if (this.health[0] >= MAXHEALTH) { + return false; + } + + health[0] += num; + if (health[0] > MAXHEALTH) { + health[0] = MAXHEALTH; + } + mo.health = health[0]; + + return true; + } + + /** + * P_GiveArmor Returns false if the armor is worse than the current armor. + */ + public boolean GiveArmor(int armortype) { + int hits; + + hits = armortype * 100; + if (armorpoints[0] >= hits) { + return false; // don't pick up + } + this.armortype = armortype; + armorpoints[0] = hits; + + return true; + } + + /** + * P_GiveCard + */ + public void GiveCard(card_t crd) { + int card = crd.ordinal(); + if (cards[card]) { + return; + } + + bonuscount = BONUSADD; + cards[card] = true; + } + + // + // P_GivePower + // + public boolean GivePower(int /* powertype_t */ power) // MAES: + // I + // didn't + // change + // this! + { + if (power == pw_invulnerability) { + powers[power] = INVULNTICS; + return true; + } + + if (power == pw_invisibility) { + powers[power] = INVISTICS; + mo.flags |= MF_SHADOW; + return true; + } + + if (power == pw_infrared) { + powers[power] = INFRATICS; + return true; + } + + if (power == pw_ironfeet) { + powers[power] = IRONTICS; + return true; + } + + if (power == pw_strength) { + GiveBody(100); + powers[power] = 1; + return true; + } + + if (powers[power] != 0) { + return false; // already got it + } + powers[power] = 1; + return true; + } + + /** + * G_PlayerFinishLevel + * Called when a player completes a level. + */ + @SourceCode.Compatible + @G_Game.C(G_PlayerFinishLevel) + public final void PlayerFinishLevel() { + memset(powers, 0, powers.length); + memset(cards, false, cards.length); + mo.flags &= ~mobj_t.MF_SHADOW; // cancel invisibility + extralight = 0; // cancel gun flashes + fixedcolormap = Palettes.COLORMAP_FIXED; // cancel ir gogles + damagecount = 0; // no palette changes + bonuscount = 0; + lookdir = 0; // From heretic + } + + /** + * P_PlayerInSpecialSector + * Called every tic frame + * that the player origin is in a special sector + */ + protected void PlayerInSpecialSector() { + sector_t sector; + + sector = mo.subsector.sector; + + // Falling, not all the way down yet? + if (mo.z != sector.floorheight) { + return; + } + + // Has hitten ground. + switch (sector.special) { + case 5: + // HELLSLIME DAMAGE + if (powers[pw_ironfeet] == 0) { + if (!flags(DOOM.leveltime, 0x1f)) { + DOOM.actions.DamageMobj(mo, null, null, 10); + } + } + break; + + case 7: + // NUKAGE DAMAGE + if (powers[pw_ironfeet] == 0) { + if (!flags(DOOM.leveltime, 0x1f)) { + DOOM.actions.DamageMobj(mo, null, null, 5); + } + } + break; + + case 16: + // SUPER HELLSLIME DAMAGE + case 4: + // STROBE HURT + if (!eval(powers[pw_ironfeet]) + || (DOOM.random.P_Random() < 5)) { + if (!flags(DOOM.leveltime, 0x1f)) { + DOOM.actions.DamageMobj(mo, null, null, 20); + } + } + break; + + case 9: + // SECRET SECTOR + secretcount++; + sector.special = 0; + break; + + case 11: + // EXIT SUPER DAMAGE! (for E1M8 finale) + cheats &= ~CF_GODMODE; + + if (!flags(DOOM.leveltime, 0x1f)) { + DOOM.actions.DamageMobj(mo, null, null, 20); + } + + if (health[0] <= 10) { + DOOM.ExitLevel(); + } + break; + + default: + DOOM.doomSystem.Error("P_PlayerInSpecialSector: unknown special %d", sector.special); + break; + } + } + + // +//P_CalcHeight +//Calculate the walking / running height adjustment +// + public void CalcHeight() { + int angle; + int bob; // fixed + + // Regular movement bobbing + // (needs to be calculated for gun swing + // even if not on ground) + // OPTIMIZE: tablify angle + // Note: a LUT allows for effects + // like a ramp with low health. + this.bob + = FixedMul(mo.momx, mo.momx) + + FixedMul(mo.momy, mo.momy); + + this.bob >>= 2; + + if (this.bob > MAXBOB) { + this.bob = MAXBOB; + } + + if (flags(cheats, CF_NOMOMENTUM) || !onground) { + viewz = mo.z + VIEWHEIGHT; + + if (viewz > mo.ceilingz - 4 * FRACUNIT) { + viewz = mo.ceilingz - 4 * FRACUNIT; + } + + viewz = mo.z + viewheight; + return; + } + + angle = (FINEANGLES / 20 * DOOM.leveltime) & FINEMASK; + bob = FixedMul(this.bob / 2, finesine[angle]); + + // move viewheight + if (playerstate == PST_LIVE) { + viewheight += deltaviewheight; + + if (viewheight > VIEWHEIGHT) { + viewheight = VIEWHEIGHT; + deltaviewheight = 0; + } + + if (viewheight < VIEWHEIGHT / 2) { + viewheight = VIEWHEIGHT / 2; + if (deltaviewheight <= 0) { + deltaviewheight = 1; + } + } + + if (deltaviewheight != 0) { + deltaviewheight += FRACUNIT / 4; + if (deltaviewheight == 0) { + deltaviewheight = 1; + } + } + } + viewz = mo.z + viewheight + bob; + + if (viewz > mo.ceilingz - 4 * FRACUNIT) { + viewz = mo.ceilingz - 4 * FRACUNIT; + } + } + + private static final long ANG5 = (ANG90 / 18); + + /** + * P_DeathThink + * Fall on your face when dying. + * Decrease POV height to floor height. + * + * DOOMGUY IS SO AWESOME THAT HE THINKS EVEN WHEN DEAD!!! + * + */ + public void DeathThink() { + long angle; //angle_t + long delta; + + MovePsprites(); + + // fall to the ground + if (viewheight > 6 * FRACUNIT) { + viewheight -= FRACUNIT; + } + + if (viewheight < 6 * FRACUNIT) { + viewheight = 6 * FRACUNIT; + } + + deltaviewheight = 0; + onground = (mo.z <= mo.floorz); + CalcHeight(); + + if (attacker != null && attacker != mo) { + angle = DOOM.sceneRenderer.PointToAngle2(mo.x, + mo.y, + attacker.x, + attacker.y); + + delta = Tables.addAngles(angle, -mo.angle); + + if (delta < ANG5 || delta > -ANG5) { + // Looking at killer, + // so fade damage flash down. + mo.angle = angle; + + if (damagecount != 0) { + damagecount--; + } + } else if (delta < ANG180) { + mo.angle += ANG5; + } else { + mo.angle -= ANG5; + } + } else if (damagecount != 0) { + damagecount--; + } + + if (flags(cmd.buttons, BT_USE)) { + playerstate = PST_REBORN; + } + } + +// +// P_MovePsprites +// Called every tic by player thinking routine. +// + public void MovePsprites() { + + pspdef_t psp; + @SuppressWarnings("unused") // Shut up compiler + state_t state = null; + + for (int i = 0; i < NUMPSPRITES; i++) { + psp = psprites[i]; + // a null state means not active + if ((state = psp.state) != null) { + // drop tic count and possibly change state + + // a -1 tic count never changes + if (psp.tics != -1) { + psp.tics--; + if (!eval(psp.tics)) { + this.SetPsprite(i, psp.state.nextstate); + } + } + } + } + + psprites[ps_flash].sx = psprites[ps_weapon].sx; + psprites[ps_flash].sy = psprites[ps_weapon].sy; + } + + /** + * P_SetPsprite + */ + @SourceCode.Exact + @P_Pspr.C(P_SetPsprite) + public void SetPsprite(int position, statenum_t newstate) { + final pspdef_t psp; + state_t state; + + psp = psprites[position]; + + do { + if (!eval(newstate)) { + // object removed itself + psp.state = null; + break; + } + + state = states[newstate.ordinal()]; + psp.state = state; + psp.tics = state.tics; // could be 0 + + if (eval(state.misc1)) { + // coordinate set + psp.sx = state.misc1 << FRACBITS; + psp.sy = state.misc2 << FRACBITS; + } + + // Call action routine. + // Modified handling. + if (state.action.isParamType(PlayerSpriteConsumer.class)) { + state.action.fun(PlayerSpriteConsumer.class).accept(DOOM.actions, this, psp); + if (!eval(psp.state)) { + break; + } + } + + newstate = psp.state.nextstate; + + } while (!eval(psp.tics)); + // an initial state of 0 could cycle through + } + + /** + * Accessory method to identify which "doomguy" we are. + * Because we can't use the [target.player-players] syntax + * in order to get an array index, in Java. + * + * If -1 is returned, then we have existential problems. + * + */ + public int identify() { + + if (id >= 0) { + return id; + } + int i; + // Let's assume that we know jack. + for (i = 0; i < DOOM.players.length; i++) { + if (this == DOOM.players[i]) { + break; + } + } + + return id = i; + + } + + private int id = -1; + + private boolean onground; + + /* psprnum_t enum */ + public static int ps_weapon = 0, + ps_flash = 1, + NUMPSPRITES = 2; + + public static int LOWERSPEED = MAPFRACUNIT * 6; + public static int RAISESPEED = MAPFRACUNIT * 6; + + public static int WEAPONBOTTOM = 128 * FRACUNIT; + public static int WEAPONTOP = 32 * FRACUNIT; + + // plasma cells for a bfg attack + private static int BFGCELLS = 40; + + + /* + P_SetPsprite + + + public void + SetPsprite + ( player_t player, + int position, + statenum_t newstate ) + { + pspdef_t psp; + state_t state; + + psp = psprites[position]; + + do + { + if (newstate==null) + { + // object removed itself + psp.state = null; + break; + } + + state = states[newstate.ordinal()]; + psp.state = state; + psp.tics = (int) state.tics; // could be 0 + + if (state.misc1!=0) + { + // coordinate set + psp.sx = (int) (state.misc1 << FRACBITS); + psp.sy = (int) (state.misc2 << FRACBITS); + } + + // Call action routine. + // Modified handling. + if (state.action.getType()==acp2) + { + P.A.dispatch(state.action,this, psp); + if (psp.state==null) + break; + } + + newstate = psp.state.nextstate; + + } while (psp.tics==0); + // an initial state of 0 could cycle through + } + */ + /** + * fixed_t + */ + int swingx, swingy; + + /** + * P_CalcSwing + * + * @param player + */ + public void CalcSwing(player_t player) { + int swing; // fixed_t + int angle; + + // OPTIMIZE: tablify this. + // A LUT would allow for different modes, + // and add flexibility. + swing = this.bob; + + angle = (FINEANGLES / 70 * DOOM.leveltime) & FINEMASK; + swingx = FixedMul(swing, finesine[angle]); + + angle = (FINEANGLES / 70 * DOOM.leveltime + FINEANGLES / 2) & FINEMASK; + swingy = -FixedMul(swingx, finesine[angle]); + } + + // + // P_BringUpWeapon + // Starts bringing the pending weapon up + // from the bottom of the screen. + // Uses player + // + @SourceCode.Exact + @P_Pspr.C(P_BringUpWeapon) + public void BringUpWeapon() { + statenum_t newstate; + + if (pendingweapon == weapontype_t.wp_nochange) { + pendingweapon = readyweapon; + } + + if (pendingweapon == weapontype_t.wp_chainsaw) { + S_StartSound: + { + DOOM.doomSound.StartSound(mo, sfxenum_t.sfx_sawup); + } + } + + newstate = weaponinfo[pendingweapon.ordinal()].upstate; + + pendingweapon = weapontype_t.wp_nochange; + psprites[ps_weapon].sy = WEAPONBOTTOM; + + P_SetPsprite: + { + this.SetPsprite(ps_weapon, newstate); + } + } + + /** + * P_CheckAmmo + * Returns true if there is enough ammo to shoot. + * If not, selects the next weapon to use. + */ + public boolean CheckAmmo() { + ammotype_t ammo; + int count; + + ammo = weaponinfo[readyweapon.ordinal()].ammo; + + // Minimal amount for one shot varies. + if (readyweapon == weapontype_t.wp_bfg) { + count = BFGCELLS; + } else if (readyweapon == weapontype_t.wp_supershotgun) { + count = 2; // Double barrel. + } else { + count = 1; // Regular. + } + // Some do not need ammunition anyway. + // Return if current ammunition sufficient. + if (ammo == ammotype_t.am_noammo || this.ammo[ammo.ordinal()] >= count) { + return true; + } + + // Out of ammo, pick a weapon to change to. + // Preferences are set here. + do { + if (weaponowned[weapontype_t.wp_plasma.ordinal()] + && (this.ammo[ammotype_t.am_cell.ordinal()] != 0) + && !DOOM.isShareware()) { + pendingweapon = weapontype_t.wp_plasma; + } else if (weaponowned[weapontype_t.wp_supershotgun.ordinal()] + && this.ammo[ammotype_t.am_shell.ordinal()] > 2 + && DOOM.isCommercial()) { + pendingweapon = weapontype_t.wp_supershotgun; + } else if (weaponowned[weapontype_t.wp_chaingun.ordinal()] + && this.ammo[ammotype_t.am_clip.ordinal()] != 0) { + pendingweapon = weapontype_t.wp_chaingun; + } else if (weaponowned[weapontype_t.wp_shotgun.ordinal()] + && this.ammo[ammotype_t.am_shell.ordinal()] != 0) { + pendingweapon = weapontype_t.wp_shotgun; + } else if (this.ammo[ammotype_t.am_clip.ordinal()] != 0) { + pendingweapon = weapontype_t.wp_pistol; + } else if (weaponowned[weapontype_t.wp_chainsaw.ordinal()]) { + pendingweapon = weapontype_t.wp_chainsaw; + } else if (weaponowned[weapontype_t.wp_missile.ordinal()] + && this.ammo[ammotype_t.am_misl.ordinal()] != 0) { + pendingweapon = weapontype_t.wp_missile; + } else if (weaponowned[weapontype_t.wp_bfg.ordinal()] + && this.ammo[ammotype_t.am_cell.ordinal()] > 40 + && !DOOM.isShareware()) { + pendingweapon = weapontype_t.wp_bfg; + } else { + // If everything fails. + pendingweapon = weapontype_t.wp_fist; + } + + } while (pendingweapon == weapontype_t.wp_nochange); + + // Now set appropriate weapon overlay. + this.SetPsprite( + ps_weapon, + weaponinfo[readyweapon.ordinal()].downstate); + + return false; + } + + /** + * P_DropWeapon + * Player died, so put the weapon away. + */ + public void DropWeapon() { + this.SetPsprite( + ps_weapon, + weaponinfo[readyweapon.ordinal()].downstate); + } + + /** + * P_SetupPsprites + * Called at start of level for each + */ + @SourceCode.Exact + @P_Pspr.C(P_SetupPsprites) + public void SetupPsprites() { + // remove all psprites + for (int i = 0; i < NUMPSPRITES; i++) { + psprites[i].state = null; + } + + // spawn the gun + pendingweapon = readyweapon; + BringUpWeapon(); + } + + /** + * P_PlayerThink + */ + public void PlayerThink(player_t player) { + ticcmd_t cmd; + weapontype_t newweapon; + + // fixme: do this in the cheat code + if (flags(player.cheats, player_t.CF_NOCLIP)) { + player.mo.flags |= MF_NOCLIP; + } else { + player.mo.flags &= ~MF_NOCLIP; + } + + // chain saw run forward + cmd = player.cmd; + if (flags(player.mo.flags, MF_JUSTATTACKED)) { + cmd.angleturn = 0; + cmd.forwardmove = (0xc800 / 512); + cmd.sidemove = 0; + player.mo.flags &= ~MF_JUSTATTACKED; + } + + if (player.playerstate == PST_DEAD) { + player.DeathThink(); + return; + } + + // Move around. + // Reactiontime is used to prevent movement + // for a bit after a teleport. + if (eval(player.mo.reactiontime)) { + player.mo.reactiontime--; + } else { + player.MovePlayer(); + } + + player.CalcHeight(); + + if (eval(player.mo.subsector.sector.special)) { + player.PlayerInSpecialSector(); + } + + // Check for weapon change. + // A special event has no other buttons. + if (flags(cmd.buttons, BT_SPECIAL)) { + cmd.buttons = 0; + } + + if (flags(cmd.buttons, BT_CHANGE)) { + // The actual changing of the weapon is done + // when the weapon psprite can do it + // (read: not in the middle of an attack). + // System.out.println("Weapon change detected, attempting to perform"); + + newweapon = weapontype_t.values()[(cmd.buttons & BT_WEAPONMASK) >> BT_WEAPONSHIFT]; + + // If chainsaw is available, it won't change back to the fist + // unless player also has berserk. + if (newweapon == weapontype_t.wp_fist + && player.weaponowned[weapontype_t.wp_chainsaw.ordinal()] + && !(player.readyweapon == weapontype_t.wp_chainsaw + && eval(player.powers[pw_strength]))) { + newweapon = weapontype_t.wp_chainsaw; + } + + // Will switch between SG and SSG in Doom 2. + if (DOOM.isCommercial() + && newweapon == weapontype_t.wp_shotgun + && player.weaponowned[weapontype_t.wp_supershotgun.ordinal()] + && player.readyweapon != weapontype_t.wp_supershotgun) { + newweapon = weapontype_t.wp_supershotgun; + } + + if (player.weaponowned[newweapon.ordinal()] + && newweapon != player.readyweapon) { + // Do not go to plasma or BFG in shareware, + // even if cheated. + if ((newweapon != weapontype_t.wp_plasma + && newweapon != weapontype_t.wp_bfg) + || !DOOM.isShareware()) { + player.pendingweapon = newweapon; + } + } + } + + // check for use + if (flags(cmd.buttons, BT_USE)) { + if (!player.usedown) { + DOOM.actions.UseLines(player); + player.usedown = true; + } + } else { + player.usedown = false; + } + + // cycle psprites + player.MovePsprites(); + + // Counters, time dependent power ups. + // Strength counts up to diminish fade. + if (eval(player.powers[pw_strength])) { + player.powers[pw_strength]++; + } + + if (eval(player.powers[pw_invulnerability])) { + player.powers[pw_invulnerability]--; + } + + if (eval(player.powers[pw_invisibility])) { + if (!eval(--player.powers[pw_invisibility])) { + player.mo.flags &= ~MF_SHADOW; + } + } + + if (eval(player.powers[pw_infrared])) { + player.powers[pw_infrared]--; + } + + if (eval(player.powers[pw_ironfeet])) { + player.powers[pw_ironfeet]--; + } + + if (eval(player.damagecount)) { + player.damagecount--; + } + + if (eval(player.bonuscount)) { + player.bonuscount--; + } + + // Handling colormaps. + if (eval(player.powers[pw_invulnerability])) { + if (player.powers[pw_invulnerability] > 4 * 32 || flags(player.powers[pw_invulnerability], 8)) { + player.fixedcolormap = Palettes.COLORMAP_INVERSE; + } else { + player.fixedcolormap = Palettes.COLORMAP_FIXED; + } + } else if (eval(player.powers[pw_infrared])) { + if (player.powers[pw_infrared] > 4 * 32 + || flags(player.powers[pw_infrared], 8)) { + // almost full bright + player.fixedcolormap = Palettes.COLORMAP_BULLBRIGHT; + } else { + player.fixedcolormap = Palettes.COLORMAP_FIXED; + } + } else { + player.fixedcolormap = Palettes.COLORMAP_FIXED; + } + } + + /** + * G_PlayerReborn + * Called after a player dies + * almost everything is cleared and initialized + * + * + */ + @G_Game.C(G_PlayerReborn) + public void PlayerReborn() { + final int[] localFrags = new int[MAXPLAYERS]; + final int localKillCount, localItemCount, localSecretCount; + + // System.arraycopy(players[player].frags, 0, frags, 0, frags.length); + // We save the player's frags here... + C2JUtils.memcpy(localFrags, this.frags, localFrags.length); + localKillCount = this.killcount; + localItemCount = this.itemcount; + localSecretCount = this.secretcount; + + //MAES: we need to simulate an erasure, possibly without making + // a new object.memset (p, 0, sizeof(*p)); + //players[player]=(player_t) player_t.nullplayer.clone(); + // players[player]=new player_t(); + this.reset(); + + // And we copy the old frags into the "new" player. + C2JUtils.memcpy(this.frags, localFrags, this.frags.length); + + this.killcount = localKillCount; + this.itemcount = localItemCount; + this.secretcount = localSecretCount; + + usedown = attackdown = true; // don't do anything immediately + playerstate = PST_LIVE; + health[0] = MAXHEALTH; + readyweapon = pendingweapon = weapontype_t.wp_pistol; + weaponowned[weapontype_t.wp_fist.ordinal()] = true; + weaponowned[weapontype_t.wp_pistol.ordinal()] = true; + ammo[ammotype_t.am_clip.ordinal()] = 50; + lookdir = 0; // From Heretic + + System.arraycopy(DoomStatus.maxammo, 0, this.maxammo, 0, NUMAMMO); + } + + /** + * Called by Actions ticker + */ + public void PlayerThink() { + PlayerThink(this); + } + + public String toString() { + sb.setLength(0); + sb.append("player"); + sb.append(" momx "); + sb.append(this.mo.momx); + sb.append(" momy "); + sb.append(this.mo.momy); + sb.append(" x "); + sb.append(this.mo.x); + sb.append(" y "); + sb.append(this.mo.y); + return sb.toString(); + } + + private static StringBuilder sb = new StringBuilder(); + + public void read(DataInputStream f) throws IOException { + + // Careful when loading/saving: + // A player only carries a pointer to a mobj, which is "saved" + // but later discarded at load time, at least in vanilla. In any case, + // it has the size of a 32-bit integer, so make sure you skip it. + // TODO: OK, so vanilla's monsters lost "state" when saved, including non-Doomguy + // infighting. Did they "remember" Doomguy too? + // ANSWER: they didn't. + // The player is special in that it unambigously allows identifying + // its own map object in an absolute way. Once we identify + // at least one (e.g. object #45 is pointer 0x43545345) then, since + // map objects are stored in a nice serialized order. + this.p_mobj = DoomIO.readLEInt(f); // player mobj pointer + + this.playerstate = DoomIO.readLEInt(f); + this.cmd.read(f); + this.viewz = DoomIO.readLEInt(f); + this.viewheight = DoomIO.readLEInt(f); + this.deltaviewheight = DoomIO.readLEInt(f); + this.bob = DoomIO.readLEInt(f); + this.health[0] = DoomIO.readLEInt(f); + this.armorpoints[0] = DoomIO.readLEInt(f); + this.armortype = DoomIO.readLEInt(f); + DoomIO.readIntArray(f, this.powers, ByteOrder.LITTLE_ENDIAN); + DoomIO.readBooleanIntArray(f, this.cards); + this.backpack = DoomIO.readIntBoolean(f); + DoomIO.readIntArray(f, frags, ByteOrder.LITTLE_ENDIAN); + this.readyweapon = weapontype_t.values()[DoomIO.readLEInt(f)]; + this.pendingweapon = weapontype_t.values()[DoomIO.readLEInt(f)]; + DoomIO.readBooleanIntArray(f, this.weaponowned); + DoomIO.readIntArray(f, ammo, ByteOrder.LITTLE_ENDIAN); + DoomIO.readIntArray(f, maxammo, ByteOrder.LITTLE_ENDIAN); + // Read these as "int booleans" + this.attackdown = DoomIO.readIntBoolean(f); + this.usedown = DoomIO.readIntBoolean(f); + this.cheats = DoomIO.readLEInt(f); + this.refire = DoomIO.readLEInt(f); + // For intermission stats. + this.killcount = DoomIO.readLEInt(f); + this.itemcount = DoomIO.readLEInt(f); + this.secretcount = DoomIO.readLEInt(f); + // Hint messages. + f.skipBytes(4); + // For screen flashing (red or bright). + this.damagecount = DoomIO.readLEInt(f); + this.bonuscount = DoomIO.readLEInt(f); + // Who did damage (NULL for floors/ceilings). + // TODO: must be properly denormalized before saving/loading + f.skipBytes(4); // TODO: waste a read for attacker mobj. + // So gun flashes light up areas. + this.extralight = DoomIO.readLEInt(f); + // Current PLAYPAL, ??? + // can be set to REDCOLORMAP for pain, etc. + this.fixedcolormap = DoomIO.readLEInt(f); + this.colormap = DoomIO.readLEInt(f); + // PSPDEF _is_ readable. + for (pspdef_t p : this.psprites) { + p.read(f); + } + this.didsecret = DoomIO.readIntBoolean(f); + // Total size should be 280 bytes. + } + + public void write(DataOutputStream f) throws IOException { + + // It's much more convenient to pre-buffer, since + // we'll be writing all Little Endian stuff. + ByteBuffer b = ByteBuffer.allocate(280); + this.pack(b); + // Total size should be 280 bytes. + // Write everything nicely and at once. + f.write(b.array()); + } + + // Used to disambiguate between objects + public int p_mobj; + + @Override + public void pack(ByteBuffer buf) + throws IOException { + + ByteOrder bo = ByteOrder.LITTLE_ENDIAN; + buf.order(bo); + // The player is special in that it unambiguously allows identifying + // its own map object in an absolute way. Once we identify + // at least one (e.g. object #45 is pointer 0x43545345) then, since + // map objects are stored in a nice serialized order by using + // their next/prev pointers, you can reconstruct their + // relationships a posteriori. + // Store our own hashcode or "pointer" if you wish. + buf.putInt(pointer(mo)); + buf.putInt(playerstate); + cmd.pack(buf); + buf.putInt(viewz); + buf.putInt(viewheight); + buf.putInt(deltaviewheight); + buf.putInt(bob); + buf.putInt(health[0]); + buf.putInt(armorpoints[0]); + buf.putInt(armortype); + DoomBuffer.putIntArray(buf, this.powers, this.powers.length, bo); + DoomBuffer.putBooleanIntArray(buf, this.cards, this.cards.length, bo); + DoomBuffer.putBooleanInt(buf, backpack, bo); + DoomBuffer.putIntArray(buf, this.frags, this.frags.length, bo); + buf.putInt(readyweapon.ordinal()); + buf.putInt(pendingweapon.ordinal()); + DoomBuffer.putBooleanIntArray(buf, this.weaponowned, this.weaponowned.length, bo); + DoomBuffer.putIntArray(buf, this.ammo, this.ammo.length, bo); + DoomBuffer.putIntArray(buf, this.maxammo, this.maxammo.length, bo); + // Read these as "int booleans" + DoomBuffer.putBooleanInt(buf, attackdown, bo); + DoomBuffer.putBooleanInt(buf, usedown, bo); + buf.putInt(cheats); + buf.putInt(refire); + // For intermission stats. + buf.putInt(this.killcount); + buf.putInt(this.itemcount); + buf.putInt(this.secretcount); + // Hint messages. + buf.putInt(0); + // For screen flashing (red or bright). + buf.putInt(this.damagecount); + buf.putInt(this.bonuscount); + // Who did damage (NULL for floors/ceilings). + // TODO: must be properly denormalized before saving/loading + buf.putInt(pointer(attacker)); + // So gun flashes light up areas. + buf.putInt(this.extralight); + // Current PLAYPAL, ??? + // can be set to REDCOLORMAP for pain, etc. + + /** + * Here the fixed color map of player is written when player_t object is packed. + * Make sure not to write any preshifted value there! Do not scale player_r.fixedcolormap, + * scale dependent array accesses. + * - Good Sign 2017/04/15 + */ + buf.putInt(this.fixedcolormap); + buf.putInt(this.colormap); + // PSPDEF _is_ readable. + for (pspdef_t p : this.psprites) { + p.pack(buf); + } + buf.putInt(this.didsecret ? 1 : 0); + + } +} \ No newline at end of file diff --git a/doom/src/doom/playerstate_t.java b/doom/src/doom/playerstate_t.java new file mode 100644 index 0000000..a7abf4b --- /dev/null +++ b/doom/src/doom/playerstate_t.java @@ -0,0 +1,13 @@ +package doom; + +// Player states. +// +public enum playerstate_t { + // Playing or camping. + PST_LIVE, + // Dead on the ground, view follows killer. + PST_DEAD, + // Ready to restart/respawn??? + PST_REBORN + +}; \ No newline at end of file diff --git a/doom/src/doom/th_class.java b/doom/src/doom/th_class.java new file mode 100644 index 0000000..daab682 --- /dev/null +++ b/doom/src/doom/th_class.java @@ -0,0 +1,15 @@ +package doom; + +/** killough 8/29/98: threads of thinkers, for more efficient searches + * cph 2002/01/13: for consistency with the main thinker list, keep objects + * pending deletion on a class list too + */ +public enum th_class { + th_delete, + th_misc, + th_friends, + th_enemies, + th_all; + + public static final int NUMTHCLASS = th_class.values().length; +} \ No newline at end of file diff --git a/doom/src/doom/thinker_t.java b/doom/src/doom/thinker_t.java new file mode 100644 index 0000000..35815e0 --- /dev/null +++ b/doom/src/doom/thinker_t.java @@ -0,0 +1,77 @@ +package doom; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import p.ActiveStates; +import p.ThinkerStates; +import static utils.C2JUtils.pointer; +import w.CacheableDoomObject; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +public class thinker_t implements CacheableDoomObject, IReadableDoomObject, IPackableDoomObject { + + public thinker_t prev; + public thinker_t next; + public ThinkerStates thinkerFunction = ActiveStates.NOP; + + /** + * killough's code for thinkers seems to be totally broken in M.D, + * so commented it out and will not probably restore, but may invent + * something new in future + * - Good Sign 2017/05/1 + * + * killough 8/29/98: we maintain thinkers in several equivalence classes, + * according to various criteria, so as to allow quicker searches. + */ + /** + * Next, previous thinkers in same class + */ + //public thinker_t cnext, cprev; + /** + * extra fields, to use when archiving/unarchiving for + * identification. Also in blocklinks, etc. + */ + public int id, previd, nextid, functionid; + + @Override + public void read(DataInputStream f) + throws IOException { + readbuffer.position(0); + readbuffer.order(ByteOrder.LITTLE_ENDIAN); + f.read(readbuffer.array()); + unpack(readbuffer); + } + + /** + * This adds 12 bytes + */ + @Override + public void pack(ByteBuffer b) + throws IOException { + // It's possible to reconstruct even by hashcodes. + // As for the function, that should be implied by the mobj_t type. + b.order(ByteOrder.LITTLE_ENDIAN); + b.putInt(pointer(prev)); + b.putInt(pointer(next)); + b.putInt(pointer(thinkerFunction.ordinal())); + //System.out.printf("Packed thinker %d %d %d\n",pointer(prev),pointer(next),pointer(function)); + } + + @Override + public void unpack(ByteBuffer b) + throws IOException { + // We are supposed to archive pointers to other thinkers, + // but they are rather useless once on disk. + b.order(ByteOrder.LITTLE_ENDIAN); + previd = b.getInt(); + nextid = b.getInt(); + functionid = b.getInt(); + //System.out.printf("Unpacked thinker %d %d %d\n",pointer(previd),pointer(nextid),pointer(functionid)); + } + + private static final ByteBuffer readbuffer = ByteBuffer.allocate(12); + +} \ No newline at end of file diff --git a/doom/src/doom/ticcmd_t.java b/doom/src/doom/ticcmd_t.java new file mode 100644 index 0000000..cf1cb00 --- /dev/null +++ b/doom/src/doom/ticcmd_t.java @@ -0,0 +1,190 @@ +package doom; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; +import w.IReadableDoomObject; + +public class ticcmd_t implements IDatagramSerializable, IReadableDoomObject, CacheableDoomObject { + + // The length datagrams are supposed to have, for full compatibility. + public static final int TICCMDLEN = 8; + + // Initializes ticcmd buffer, too. + public ticcmd_t() { + this.buffer = new byte[TICCMDLEN]; + } + + /** *2048 for move */ + public byte forwardmove; + + /** *2048 for move */ + public byte sidemove; + + /** <<16 for angle delta */ + public short angleturn; + + /** checks for net game */ + public short consistancy; + + /** MAES: these are unsigned bytes :-( + * However over networks, if we wish for vanilla compatibility, + * these must be reduced to 8-bit "chars" + * */ + public char chatchar, buttons; + + /** HERETIC look/fly up/down/centering */ + public char lookfly; + + // TODO: will be ignored during vanilla demos. Consider using specialized + // per-demotype readers instead of Cacheable/Unpackage. + /** replaces G_CmdChecksum (ticcmd_t cmd) */ + ///////////////////////////////////////////// + // For datagram serialization + private byte[] buffer; + + public int getChecksum(ticcmd_t cmd) { + int sum = 0; + sum += forwardmove; + sum += sidemove; + sum += angleturn; + sum += consistancy; + sum += chatchar; + sum += buttons; + return sum; + } + + /** because Cloneable is bullshit */ + public void copyTo(ticcmd_t dest) { + dest.forwardmove = forwardmove; + dest.sidemove = sidemove; + dest.angleturn = angleturn; + dest.consistancy = consistancy; + dest.chatchar = chatchar; + dest.buttons = buttons; + dest.lookfly = lookfly; + } + + private static StringBuilder sb = new StringBuilder(); + + public String toString() { + sb.setLength(0); + sb.append(" forwardmove "); + sb.append(Integer.toHexString(this.forwardmove)); + sb.append(" sidemove "); + sb.append(Integer.toHexString(this.sidemove)); + sb.append(" angleturn "); + sb.append(Integer.toHexString(this.angleturn)); + sb.append(" consistancy "); + sb.append(Integer.toHexString(this.consistancy)); + sb.append(" chatchar "); + sb.append(chatchar); + sb.append(" buttons "); + sb.append(Integer.toHexString(this.buttons)); + return sb.toString(); + } + + @Override + public byte[] pack() { + buffer[0] = forwardmove; + buffer[1] = sidemove; + buffer[2] = (byte) (angleturn >>> 8); + buffer[3] = (byte) (angleturn & 0x00FF); + buffer[4] = (byte) (consistancy >>> 8); + buffer[5] = (byte) (consistancy & 0x00FF); + + // We only send 8 bytes because the original length was 8 bytes. + buffer[6] = (byte) (chatchar & 0x00FF); + buffer[7] = (byte) (buttons & 0x00FF); + + return buffer; + } + + @Override + public void pack(byte[] buf, int offset) { + buf[0 + offset] = forwardmove; + buf[1 + offset] = sidemove; + buf[2 + offset] = (byte) (angleturn >>> 8); + buf[3 + offset] = (byte) (angleturn & 0x00FF); + buf[4 + offset] = (byte) (consistancy >>> 8); + buf[5 + offset] = (byte) (consistancy & 0x00FF); + + // We only send 8 bytes because the original length was 8 bytes. + buf[6 + offset] = (byte) (chatchar & 0x00FF); + buf[7 + offset] = (byte) (buttons & 0x00FF); + + } + + @Override + public void unpack(byte[] buf) { + unpack(buf, 0); + } + + @Override + public void unpack(byte[] buf, int offset) { + forwardmove = buf[0 + offset]; + sidemove = buf[1 + offset]; + angleturn = (short) (buf[2 + offset] << 8 | buf[3 + offset]); + consistancy = (short) (buf[4 + offset] << 8 | buf[5 + offset]); + // We blow these up to full chars. + chatchar = (char) (0x00FF & buf[6 + offset]); + buttons = (char) (0x00FF & buf[7 + offset]); + + } + + @Override + public byte[] cached() { + return this.buffer; + } + + @Override + public void read(DataInputStream f) + throws IOException { + iobuffer.position(0); + iobuffer.order(ByteOrder.LITTLE_ENDIAN); + f.read(iobuffer.array()); + unpack(iobuffer); + } + + /** This is useful only when loading/saving players from savegames. + * It's NOT interchangeable with datagram methods, because it + * does not use the network byte order. + */ + @Override + public void unpack(ByteBuffer f) + throws IOException { + f.order(ByteOrder.LITTLE_ENDIAN); + forwardmove = f.get(); + sidemove = f.get(); + // Even if they use the "unsigned char" syntax, angleturn is signed. + angleturn = f.getShort(); + consistancy = f.getShort(); + // We blow these up to full chars. + chatchar = (char) f.get(); + buttons = (char) f.get(); + + } + + /** Ditto, we only pack some of the fields. + * + * @param f + * @throws IOException + */ + public void pack(ByteBuffer f) + throws IOException { + f.order(ByteOrder.LITTLE_ENDIAN); + f.put(forwardmove); + f.put(sidemove); + // LE order on disk for vanilla compatibility. + f.putShort(angleturn); + f.putShort(consistancy); + // We crimp these to bytes :-( + f.put((byte) chatchar); + f.put((byte) buttons); + } + + private static ByteBuffer iobuffer = ByteBuffer.allocate(8); + +}; \ No newline at end of file diff --git a/doom/src/doom/wbplayerstruct_t.java b/doom/src/doom/wbplayerstruct_t.java new file mode 100644 index 0000000..39bf433 --- /dev/null +++ b/doom/src/doom/wbplayerstruct_t.java @@ -0,0 +1,48 @@ +package doom; + +// +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +// INTERMISSION +// Structure passed e.g. to WI_Start(wb) +// +public class wbplayerstruct_t implements Cloneable { + + private static final Logger LOGGER = Loggers.getLogger(wbplayerstruct_t.class.getName()); + + public wbplayerstruct_t() { + frags = new int[4]; + } + public boolean in; // whether the player is in game + + /** Player stats, kills, collected items etc. */ + public int skills; + public int sitems; + public int ssecret; + public int stime; + public int[] frags; + /** current score on entry, modified on return */ + public int score; + + public wbplayerstruct_t clone() { + wbplayerstruct_t r = null; + try { + r = (wbplayerstruct_t) super.clone(); + } catch (CloneNotSupportedException e) { + LOGGER.log(Level.SEVERE, "wbplayerstruct_t: clone failure", e); + } + /*r.in=this.in; + r.skills=this.skills; + r.sitems=this.sitems; + r.ssecret=this.ssecret; + r.stime=this.stime; */ + System.arraycopy(this.frags, 0, r.frags, 0, r.frags.length); + // r.score=this.score; + + return r; + + } + +} \ No newline at end of file diff --git a/doom/src/doom/wbstartstruct_t.java b/doom/src/doom/wbstartstruct_t.java new file mode 100644 index 0000000..8ecab4f --- /dev/null +++ b/doom/src/doom/wbstartstruct_t.java @@ -0,0 +1,65 @@ +package doom; + +import static data.Limits.MAXPLAYERS; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import static utils.GenericCopy.malloc; + +public class wbstartstruct_t implements Cloneable { + + private static final Logger LOGGER = Loggers.getLogger(wbstartstruct_t.class.getName()); + + public wbstartstruct_t() { + plyr = malloc(wbplayerstruct_t::new, wbplayerstruct_t[]::new, MAXPLAYERS); + } + + public int epsd; // episode # (0-2) + + // if true, splash the secret level + public boolean didsecret; + + // previous and next levels, origin 0 + public int last; + public int next; + + public int maxkills; + public int maxitems; + public int maxsecret; + public int maxfrags; + + /** the par time */ + public int partime; + + /** index of this player in game */ + public int pnum; + /** meant to be treated as a "struct", therefore assignments should be deep copies */ + public wbplayerstruct_t[] plyr; + + public wbstartstruct_t clone() { + wbstartstruct_t cl = null; + try { + cl = (wbstartstruct_t) super.clone(); + } catch (CloneNotSupportedException e) { + LOGGER.log(Level.SEVERE, "wbstartstruct_t: clone failure", e); + } + /*cl.epsd=this.epsd; + cl.didsecret=this.didsecret; + cl.last=this.last; + cl.next=this.next; + cl.maxfrags=this.maxfrags; + cl.maxitems=this.maxitems; + cl.maxsecret=this.maxsecret; + cl.maxkills=this.maxkills; + cl.partime=this.partime; + cl.pnum=this.pnum;*/ + for (int i = 0; i < cl.plyr.length; i++) { + cl.plyr[i] = this.plyr[i].clone(); + } + //cl.plyr=this.plyr.clone(); + + return cl; + + } + +} \ No newline at end of file diff --git a/doom/src/doom/weaponinfo_t.java b/doom/src/doom/weaponinfo_t.java new file mode 100644 index 0000000..0975b0b --- /dev/null +++ b/doom/src/doom/weaponinfo_t.java @@ -0,0 +1,58 @@ +package doom; + +import defines.ammotype_t; +import defines.statenum_t; + +// +// PSPRITE ACTIONS for waepons. +// This struct controls the weapon animations. +// +// Each entry is: +// ammo/amunition type +// upstate +// downstate +// readystate +// atkstate, i.e. attack/fire/hit frame +// flashstate, muzzle flash +// +public class weaponinfo_t { + + /* + public weaponinfo_t(ammotype_t ammo, int upstate, int downstate, + int readystate, int atkstate, int flashstate) { + super(); + this.ammo = ammo; + this.upstate = upstate; + this.downstate = downstate; + this.readystate = readystate; + this.atkstate = atkstate; + this.flashstate = flashstate; + }*/ + public ammotype_t ammo; + + public weaponinfo_t(ammotype_t ammo, statenum_t upstate, + statenum_t downstate, statenum_t readystate, + statenum_t atkstate, statenum_t flashstate) { + super(); + this.ammo = ammo; + this.upstate = upstate; + this.downstate = downstate; + this.readystate = readystate; + this.atkstate = atkstate; + this.flashstate = flashstate; + } + + public statenum_t upstate; + public statenum_t downstate; + public statenum_t readystate; + public statenum_t atkstate; + public statenum_t flashstate; + + /* + public int upstate; + public int downstate; + public int readystate; + public int atkstate; + public int flashstate; + */ +} \ No newline at end of file diff --git a/doom/src/doom/weapontype_t.java b/doom/src/doom/weapontype_t.java new file mode 100644 index 0000000..983f65f --- /dev/null +++ b/doom/src/doom/weapontype_t.java @@ -0,0 +1,25 @@ +package doom; + +/** The defined weapons, + * including a marker indicating + * user has not changed weapon. + */ +public enum weapontype_t { + wp_fist, + wp_pistol, + wp_shotgun, + wp_chaingun, + wp_missile, + wp_plasma, + wp_bfg, + wp_chainsaw, + wp_supershotgun, + NUMWEAPONS, + // No pending weapon change. + wp_nochange; + + public String toString() { + return this.name(); + } + +} \ No newline at end of file diff --git a/doom/src/f/AbstractEndLevel.java b/doom/src/f/AbstractEndLevel.java new file mode 100644 index 0000000..1c6fb17 --- /dev/null +++ b/doom/src/f/AbstractEndLevel.java @@ -0,0 +1,126 @@ +package f; + +import static data.Defines.TICRATE; +import w.animenum_t; + +public abstract class AbstractEndLevel { + + //NET GAME STUFF + public static final int NG_STATSY = 50; + public static final int NG_SPACINGX = 64; + + //DEATHMATCH STUFF + public static final int DM_MATRIXX = 42; + public static final int DM_MATRIXY = 68; + + public static final int DM_SPACINGX = 40; + + public static final int DM_TOTALSX = 269; + + public static final int DM_KILLERSX = 10; + public static final int DM_KILLERSY = 100; + public static final int DM_VICTIMSX = 5; + public static final int DM_VICTIMSY = 50; + + // static point_t lnodes[NUMEPISODES][NUMMAPS] + final static public point_t[][] lnodes + = { + // Episode 0 World Map + { + new point_t(185, 164), // location of level 0 (CJ) + new point_t(148, 143), // location of level 1 new point_t(CJ) + new point_t(69, 122), // location of level 2 new point_t(CJ) + new point_t(209, 102), // location of level 3 new point_t(CJ) + new point_t(116, 89), // location of level 4 new point_t(CJ) + new point_t(166, 55), // location of level 5 new point_t(CJ) + new point_t(71, 56), // location of level 6 new point_t(CJ) + new point_t(135, 29), // location of level 7 new point_t(CJ) + new point_t(71, 24) // location of level 8 new point_t(CJ) + }, + // Episode 1 World Map should go here + { + new point_t(254, 25), // location of level 0 new point_t(CJ) + new point_t(97, 50), // location of level 1 new point_t(CJ) + new point_t(188, 64), // location of level 2 new point_t(CJ) + new point_t(128, 78), // location of level 3 new point_t(CJ) + new point_t(214, 92), // location of level 4 new point_t(CJ) + new point_t(133, 130), // location of level 5 new point_t(CJ) + new point_t(208, 136), // location of level 6 new point_t(CJ) + new point_t(148, 140), // location of level 7 new point_t(CJ) + new point_t(235, 158) // location of level 8 new point_t(CJ) + }, + // Episode 2 World Map should go here + { + new point_t(156, 168), // location of level 0 new point_t(CJ) + new point_t(48, 154), // location of level 1 new point_t(CJ) + new point_t(174, 95), // location of level 2 new point_t(CJ) + new point_t(265, 75), // location of level 3 new point_t(CJ) + new point_t(130, 48), // location of level 4 new point_t(CJ) + new point_t(279, 23), // location of level 5 new point_t(CJ) + new point_t(198, 48), // location of level 6 new point_t(CJ) + new point_t(140, 25), // location of level 7 new point_t(CJ) + new point_t(281, 136) // location of level 8 new point_t(CJ) + } + + }; + + // + //Animation locations for episode 0 (1). + //Using patches saves a lot of space, + //as they replace 320x200 full screen frames. + // + public static final anim_t[] epsd0animinfo + = { + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(224, 104)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(184, 160)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(112, 136)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(72, 112)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(88, 96)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(64, 48)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(192, 40)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(136, 16)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(80, 16)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(64, 24)) + }; + + public static final anim_t[] epsd1animinfo + = { + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 1), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 2), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 3), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 4), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 5), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 6), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 7), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 3, new point_t(192, 144), 8), + new anim_t(animenum_t.ANIM_LEVEL, TICRATE / 3, 1, new point_t(128, 136), 8) + }; + + public static final anim_t[] epsd2animinfo + = { + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(104, 168)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(40, 136)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(160, 96)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(104, 80)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 3, 3, new point_t(120, 32)), + new anim_t(animenum_t.ANIM_ALWAYS, TICRATE / 4, 3, new point_t(40, 0)) + }; + + /*static int NUMANIMS[NUMEPISODES] = + { + sizeof(epsd0animinfo)/sizeof(anim_t), + sizeof(epsd1animinfo)/sizeof(anim_t), + sizeof(epsd2animinfo)/sizeof(anim_t) + };*/ + // MAES: cute, but we can do it in a more Java-friendly way :-p + public static final int[] NUMANIMS = {epsd0animinfo.length, epsd1animinfo.length, epsd2animinfo.length}; + + /** ATTENTION: there's a difference between these "anims" and those used in p_spec.c */ + public static final anim_t[][] anims + = { + epsd0animinfo, + epsd1animinfo, + epsd2animinfo + }; + +} \ No newline at end of file diff --git a/doom/src/f/EndLevel.java b/doom/src/f/EndLevel.java new file mode 100644 index 0000000..0c937f8 --- /dev/null +++ b/doom/src/f/EndLevel.java @@ -0,0 +1,1826 @@ +package f; + +/* Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: EndLevel.java,v 1.11 2012/09/24 17:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: EndLevel.java,v $ +// Revision 1.11 2012/09/24 17:16:23 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.8.2.2 2012/09/24 16:57:43 velktron +// Addressed generics warnings. +// +// Revision 1.8.2.1 2011/11/27 18:18:34 velktron +// Use cacheClear() on deactivation. +// +// Revision 1.8 2011/11/01 19:02:57 velktron +// Using screen number constants +// +// Revision 1.7 2011/10/23 18:11:32 velktron +// Generic compliance for DoomVideoInterface +// +// Revision 1.6 2011/08/23 16:13:53 velktron +// Got rid of Z remnants. +// +// Revision 1.5 2011/07/31 21:49:38 velktron +// Changed endlevel drawer's behavior to be closer to prBoom+'s. Allows using 1994TU.WAD while backwards compatible. +// +// Revision 1.4 2011/06/02 14:56:48 velktron +// imports +// +// Revision 1.3 2011/06/02 14:53:21 velktron +// Moved Endlevel constants to AbstractEndLevel +// +// Revision 1.2 2011/06/02 14:14:28 velktron +// Implemented endlevel unloading of graphics, changed state enum. +// +// Revision 1.1 2011/06/02 14:00:48 velktron +// Moved Endlevel stuff to f, where it makes more sense. +// +// Revision 1.18 2011/05/31 12:25:14 velktron +// Endlevel -mostly- scaled correctly. +// +// Revision 1.17 2011/05/29 22:15:32 velktron +// Introduced IRandom interface. +// +// Revision 1.16 2011/05/24 17:54:02 velktron +// Defaults tester +// +// Revision 1.15 2011/05/23 17:00:39 velktron +// Got rid of verbosity +// +// Revision 1.14 2011/05/21 16:53:24 velktron +// Adapted to use new gamemode system. +// +// Revision 1.13 2011/05/18 16:58:04 velktron +// Changed to DoomStatus +// +// Revision 1.12 2011/05/17 16:52:19 velktron +// Switched to DoomStatus +// +// Revision 1.11 2011/05/11 14:12:08 velktron +// Interfaced with DoomGame +// +// Revision 1.10 2011/05/10 10:39:18 velktron +// Semi-playable Techdemo v1.3 milestone +// +// Revision 1.9 2011/05/06 14:00:54 velktron +// More of _D_'s changes committed. +// +// Revision 1.8 2011/02/11 00:11:13 velktron +// A MUCH needed update to v1.3. +// +// Revision 1.7 2010/12/20 17:15:08 velktron +// Made the renderer more OO -> TextureManager and other changes as well. +// +// Revision 1.6 2010/11/12 13:37:25 velktron +// Rationalized the LUT system - now it's 100% procedurally generated. +// +// Revision 1.5 2010/09/23 07:31:11 velktron +// fuck +// +// Revision 1.4 2010/09/02 15:56:54 velktron +// Bulk of unified renderer copyediting done. +// +// Some changes like e.g. global separate limits class and instance methods for seg_t and node_t introduced. +// +// Revision 1.3 2010/08/23 14:36:08 velktron +// Menu mostly working, implemented Killough's fast hash-based GetNumForName, although it can probably be finetuned even more. +// +// Revision 1.2 2010/08/13 14:06:36 velktron +// Endlevel screen fully functional! +// +// Revision 1.1 2010/07/06 16:32:38 velktron +// Threw some work in WI, now EndLevel. YEAH THERE'S GONNA BE A SEPARATE EndLevel OBJECT THAT'S HOW PIMP THE PROJECT IS!!!!11!!! +// +// Revision 1.1 2010/06/30 08:58:51 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// Intermission screens. +// +//-----------------------------------------------------------------------------*/ +import static data.Defines.BT_ATTACK; +import static data.Defines.BT_USE; +import static data.Defines.NUMMAPS; +import static data.Defines.PU_CACHE; +import static data.Defines.PU_STATIC; +import static data.Defines.TICRATE; +import static data.Limits.MAXPLAYERS; +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import defines.Language_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.WI_Stuff; +import static doom.SourceCode.WI_Stuff.WI_Start; +import static doom.SourceCode.WI_Stuff.WI_initAnimatedBack; +import static doom.SourceCode.WI_Stuff.WI_initDeathmatchStats; +import static doom.SourceCode.WI_Stuff.WI_initNetgameStats; +import static doom.SourceCode.WI_Stuff.WI_initStats; +import static doom.SourceCode.WI_Stuff.WI_initVariables; +import static doom.SourceCode.WI_Stuff.WI_loadData; +import doom.event_t; +import doom.player_t; +import doom.wbplayerstruct_t; +import doom.wbstartstruct_t; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.patch_t; +import static v.DoomGraphicSystem.V_NOSCALESTART; +import static v.DoomGraphicSystem.V_SAFESCALE; +import static v.renderers.DoomScreen.BG; +import static v.renderers.DoomScreen.FG; + +/** + * This class (stuff.c) seems to implement the endlevel screens. + * + * @author Maes + * + */ +public class EndLevel extends AbstractEndLevel { + + private static final Logger LOGGER = Loggers.getLogger(EndLevel.class.getName()); + + ////////////////// STATUS /////////////////// + private final DoomMain DOOM; + + private static final int COUNT_KILLS = 2; + private static final int COUNT_ITEMS = 4; + private static final int COUNT_SECRETS = 6; + private static final int COUNT_TIME = 8; + private static final int COUNT_DONE = 10; + + static enum endlevel_state { + NoState, + StatCount, + ShowNextLoc, + JustShutOff + } + + //GLOBAL LOCATIONS + private static final int WI_TITLEY = 2; + private static final int WI_SPACINGY = 3; + + // + // GENERAL DATA + // + // + // Locally used stuff. + // + private static final boolean RANGECHECKING = true; + + // Where to draw some stuff. To be scaled up, so they + // are not final. + public static int SP_STATSX; + public static int SP_STATSY; + + public static int SP_TIMEX; + public static int SP_TIMEY; + + // States for single-player + protected static int SP_KILLS = 0; + protected static int SP_ITEMS = 2; + protected static int SP_SECRET = 4; + protected static int SP_FRAGS = 6; + protected static int SP_TIME = 8; + protected static int SP_PAR = SP_TIME; + + protected int SP_PAUSE = 1; + + // in seconds + protected int SHOWNEXTLOCDELAY = 4; + protected int SHOWLASTLOCDELAY = SHOWNEXTLOCDELAY; + + // used to accelerate or skip a stage + int acceleratestage; + + // wbs->pnum + int me; + + // specifies current state ) + endlevel_state state; + + // contains information passed into intermission + public wbstartstruct_t wbs; + + wbplayerstruct_t[] plrs; // wbs->plyr[] + + // used for general timing + int cnt; + + // used for timing of background animation + int bcnt; + + // signals to refresh everything for one frame + int firstrefresh; + + int[] cnt_kills = new int[MAXPLAYERS]; + int[] cnt_items = new int[MAXPLAYERS]; + int[] cnt_secret = new int[MAXPLAYERS]; + int cnt_time; + int cnt_par; + int cnt_pause; + + // # of commercial levels + int NUMCMAPS; + + // + // GRAPHICS + // + // background (map of levels). + patch_t bg; + + // You Are Here graphic + patch_t[] yah = new patch_t[3]; + + // splat + patch_t[] splat; + + /** + * %, : graphics + */ + patch_t percent, colon; + + /** + * 0-9 graphic + */ + patch_t[] num = new patch_t[10]; + + /** + * minus sign + */ + patch_t wiminus; + + // "Finished!" graphics + patch_t finished; + + // "Entering" graphic + patch_t entering; + + // "secret" + patch_t sp_secret; + + /** + * "Kills", "Scrt", "Items", "Frags" + */ + patch_t kills, secret, items, frags; + + /** + * Time sucks. + */ + patch_t time, par, sucks; + + /** + * "killers", "victims" + */ + patch_t killers, victims; + + /** + * "Total", your face, your dead face + */ + patch_t total, star, bstar; + + /** + * "red P[1..MAXPLAYERS]" + */ + patch_t[] p = new patch_t[MAXPLAYERS]; + + /** + * "gray P[1..MAXPLAYERS]" + */ + patch_t[] bp = new patch_t[MAXPLAYERS]; + + /** + * Name graphics of each level (centered) + */ + patch_t[] lnames; + + // + // CODE + // + // slam background + // UNUSED unsigned char *background=0; + public EndLevel(DoomMain DOOM) { + this.DOOM = DOOM; + + // Pre-scale stuff. + SP_STATSX = 50 * DOOM.vs.getSafeScaling(); + SP_STATSY = 50 * DOOM.vs.getSafeScaling(); + + SP_TIMEX = 16 * DOOM.vs.getSafeScaling(); + SP_TIMEY = (DOOM.vs.getScreenHeight() - DOOM.statusBar.getHeight()); + // _D_: commented this, otherwise something didn't work + //this.Start(DS.wminfo); + } + + protected void slamBackground() { + // memcpy(screens[0], screens[1], DOOM.vs.getScreenWidth() * DOOM.vs.getScreenHeight()); + // Remember, the second arg is the source! + DOOM.graphicSystem.screenCopy(BG, FG); + //System.arraycopy(V.getScreen(SCREEN_BG), 0 ,V.getScreen(SCREEN_FG),0, DOOM.vs.getScreenWidth() * DOOM.vs.getScreenHeight()); + //V.MarkRect (0, 0, DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight()); + } + +// The ticker is used to detect keys +// because of timing issues in netgames. + public boolean Responder(event_t ev) { + return false; + } + + /** + * Draws " Finished!" + */ + protected void drawLF() { + int y = WI_TITLEY; + + // draw + DOOM.graphicSystem.DrawPatchScaled(FG, lnames[wbs.last], DOOM.vs, (320 - lnames[wbs.last].width) / 2, y); + + // draw "Finished!" + y += (5 * lnames[wbs.last].height) / 4; + + DOOM.graphicSystem.DrawPatchScaled(FG, finished, DOOM.vs, (320 - finished.width) / 2, y); + } + + /** + * Draws "Entering " + */ + protected void drawEL() { + int y = WI_TITLEY; // This is in 320 x 200 coords! + + // draw "Entering" + DOOM.graphicSystem.DrawPatchScaled(FG, entering, DOOM.vs, (320 - entering.width) / 2, y); + + // HACK: if lnames[wbs.next] DOES have a defined nonzero topoffset, use it. + // implicitly in DrawScaledPatch, and trump the normal behavior. + // FIXME: this is only useful in a handful of prBoom+ maps, which use + // a modified endlevel screen. The reason it works there is the behavior of the + // unified patch drawing function, which is approximated with this hack. + if (lnames[wbs.next].topoffset == 0) { + y += (5 * lnames[wbs.next].height) / 4; + } + // draw level. + + DOOM.graphicSystem.DrawPatchScaled(FG, lnames[wbs.next], DOOM.vs, (320 - lnames[wbs.next].width) / 2, y); + + } + + /** + * Fixed the issue with splat patch_t[] - a crash caused by null in array - by importing fix from prboom-plus. The + * issue was: developers intended to be able to pass one patch_t or two at once, when they pass one, they use a + * pointer expecting an array but without a real array, producing UB. At first, I've bring back an array by redoing + * splat as patch_t[] instead of single patch_t. Secondly, I've emulated UB by allowing null to be found in splat + * array Finally, I've 'fixed' this imaginary UB by testing against null, as it is done in prboom-plus. + * + * So at the moment it should work exactly as in vanilla if it would not crash. However, additional testing may + * apply to revert this fix. - Good Sign 2017/04/04 + * + * For whatever fucked-up reason, it expects c to be an array of patches, and may choose to draw from alternative + * ones...which however are never loaded, or are supposed to be "next" in memory or whatever. I kept this behavior, + * however in Java it will NOT work as intended, if ever. + * + * @param n + * @param c + */ + protected void + drawOnLnode(int n, + patch_t[] c) { + + int i; + int left; + int top; + int right; + int bottom; + boolean fits = false; + + i = 0; + do { + left = lnodes[wbs.epsd][n].x - c[i].leftoffset; + top = lnodes[wbs.epsd][n].y - c[i].topoffset; + right = left + c[i].width; + bottom = top + c[i].height; + + if (left >= 0 + && right < DOOM.vs.getScreenWidth() + && top >= 0 + && bottom < DOOM.vs.getScreenHeight()) { + fits = true; + } else { + i++; + } + } while (!fits && i != 2 && c[i] != null); + + if (fits && i < 2) { + //V.DrawPatch(lnodes[wbs.epsd][n].x, lnodes[wbs.epsd][n].y, + // FB, c[i]); + DOOM.graphicSystem.DrawPatchScaled(FG, c[i], DOOM.vs, lnodes[wbs.epsd][n].x, lnodes[wbs.epsd][n].y); + } else { + // DEBUG + LOGGER.log(Level.FINE, String.format("Could not place patch on level %d", n + 1)); + } + } + + @SourceCode.Exact + @WI_Stuff.C(WI_initAnimatedBack) + protected void initAnimatedBack() { + anim_t a; + + if (DOOM.isCommercial()) { + return; + } + + if (wbs.epsd > 2) { + return; + } + + for (int i = 0; i < NUMANIMS[wbs.epsd]; i++) { + a = anims[wbs.epsd][i]; + + // init variables + a.ctr = -1; + + if (null != a.type) // specify the next time to draw it + { + switch (a.type) { + case ANIM_ALWAYS: + a.nexttic = bcnt + 1 + (DOOM.random.M_Random() % a.period); + break; + case ANIM_RANDOM: + a.nexttic = bcnt + 1 + a.data2 + (DOOM.random.M_Random() % a.data1); + break; + case ANIM_LEVEL: + a.nexttic = bcnt + 1; + break; + default: + break; + } + } + } + + } + + protected void updateAnimatedBack() { + int i; + anim_t a; + + if (DOOM.isCommercial()) { + return; + } + + if (wbs.epsd > 2) { + return; + } + + int aaptr = wbs.epsd; + + for (i = 0; i < NUMANIMS[wbs.epsd]; i++) { + a = anims[aaptr][i]; + + if (bcnt == a.nexttic) { + switch (a.type) { + case ANIM_ALWAYS: + if (++anims[aaptr][i].ctr >= a.nanims) { + a.ctr = 0; + } + a.nexttic = bcnt + a.period; + break; + + case ANIM_RANDOM: + a.ctr++; + if (a.ctr == a.nanims) { + a.ctr = -1; + a.nexttic = bcnt + a.data2 + (DOOM.random.M_Random() % a.data1); + } else { + a.nexttic = bcnt + a.period; + } + break; + + case ANIM_LEVEL: + // gawd-awful hack for level anims + if (!(state == endlevel_state.StatCount && i == 7) + && wbs.next == a.data1) { + a.ctr++; + if (a.ctr == a.nanims) { + a.ctr--; + } + a.nexttic = bcnt + a.period; + } + break; + } + } + + } + + } + + protected void drawAnimatedBack() { + int i; + anim_t a; + + if (DOOM.isCommercial()) { + return; + } + + if (wbs.epsd > 2) { + return; + } + + for (i = 0; i < NUMANIMS[wbs.epsd]; i++) { + a = anims[wbs.epsd][i]; + + if (a.ctr >= 0) { + DOOM.graphicSystem.DrawPatchScaled(FG, a.p[a.ctr], DOOM.vs, a.loc.x, a.loc.y); + } + } + + } + + /** + * Draws a number. If digits > 0, then use that many digits minimum, otherwise only use as many as necessary. + * Returns new x position. + */ + protected int drawNum(int x, int y, int n, int digits) { + + int fontwidth = num[0].width; + boolean neg; + int temp; + + if (digits < 0) { + if (n == 0) { + // make variable-length zeros 1 digit long + digits = 1; + } else { + // figure out # of digits in # + digits = 0; + temp = n; + + while (temp != 0) { + temp /= 10; + digits++; + } + } + } + + neg = (n < 0); + if (neg) { + n = -n; + } + + // if non-number, do not draw it + if (n == 1994) { + return 0; + } + + // draw the new number + while ((digits--) != 0) { + x -= fontwidth * DOOM.vs.getScalingX(); + DOOM.graphicSystem.DrawPatchScaled(FG, num[n % 10], DOOM.vs, x, y, V_NOSCALESTART); + n /= 10; + } + + // draw a minus sign if necessary + if (neg) { + DOOM.graphicSystem.DrawPatchScaled(FG, wiminus, DOOM.vs, x -= 8 * DOOM.vs.getScalingX(), y, V_NOSCALESTART); + } + + return x; + + } + + protected void drawPercent(int x, int y, int p) { + if (p < 0) { + return; + } + + DOOM.graphicSystem.DrawPatchScaled(FG, percent, DOOM.vs, x, y, V_NOSCALESTART); + drawNum(x, y, p, -1); + } + +// +// Display level completion time and par, +// or "sucks" message if overflow. +// + protected void drawTime(int x, + int y, + int t) { + + int div; + int n; + + if (t < 0) { + return; + } + + if (t <= 61 * 59) { + div = 1; + + do { + n = (t / div) % 60; + x = drawNum(x, y, n, 2) - colon.width * DOOM.vs.getScalingX(); + div *= 60; + + // draw + if ((div == 60) || (t / div) > 0) { + DOOM.graphicSystem.DrawPatchScaled(FG, colon, DOOM.vs, x, y, V_NOSCALESTART); + } + + } while ((t / div) > 0); + } else { + // "sucks" + DOOM.graphicSystem.DrawPatchScaled(FG, sucks, DOOM.vs, x - sucks.width * DOOM.vs.getScalingX(), y, V_NOSCALESTART); + } + } + + protected void End() { + state = endlevel_state.JustShutOff; + DOOM.graphicSystem.forcePalette(); + unloadData(); + } + + protected void unloadData() { + int i; + int j; + + DOOM.wadLoader.UnlockLumpNum(wiminus); + wiminus = null; + + for (i = 0; i < 10; i++) { + DOOM.wadLoader.UnlockLumpNum(num[i]); + num[i] = null; + } + + if (DOOM.isCommercial()) { + for (i = 0; i < NUMCMAPS; i++) { + DOOM.wadLoader.UnlockLumpNum(lnames[i]); + lnames[i] = null; + } + } else { + DOOM.wadLoader.UnlockLumpNum(yah[0]); + yah[0] = null; + DOOM.wadLoader.UnlockLumpNum(yah[1]); + yah[1] = null; + + DOOM.wadLoader.UnlockLumpNum(splat[0]); + splat[0] = null; + + for (i = 0; i < NUMMAPS; i++) { + DOOM.wadLoader.UnlockLumpNum(lnames[i]); + lnames[i] = null; + + } + if (wbs.epsd < 3) { + for (j = 0; j < NUMANIMS[wbs.epsd]; j++) { + if (wbs.epsd != 1 || j != 8) { + for (i = 0; i < anims[wbs.epsd][j].nanims; i++) { + DOOM.wadLoader.UnlockLumpNum(anims[wbs.epsd][j].p[i]); + anims[wbs.epsd][j].p[i] = null; + } + } + } + } + } + DOOM.wadLoader.UnlockLumpNum(percent); + percent = null; + DOOM.wadLoader.UnlockLumpNum(colon); + colon = null; + DOOM.wadLoader.UnlockLumpNum(finished); + finished = null; + DOOM.wadLoader.UnlockLumpNum(entering); + entering = null; + DOOM.wadLoader.UnlockLumpNum(kills); + kills = null; + DOOM.wadLoader.UnlockLumpNum(secret); + secret = null; + DOOM.wadLoader.UnlockLumpNum(sp_secret); + sp_secret = null; + DOOM.wadLoader.UnlockLumpNum(items); + items = null; + DOOM.wadLoader.UnlockLumpNum(frags); + frags = null; + DOOM.wadLoader.UnlockLumpNum(time); + time = null; + DOOM.wadLoader.UnlockLumpNum(sucks); + sucks = null; + DOOM.wadLoader.UnlockLumpNum(par); + par = null; + DOOM.wadLoader.UnlockLumpNum(victims); + victims = null; + DOOM.wadLoader.UnlockLumpNum(killers); + killers = null; + DOOM.wadLoader.UnlockLumpNum(total); + total = null; + for (i = 0; i < MAXPLAYERS; i++) { + DOOM.wadLoader.UnlockLumpNum(p[i]); + DOOM.wadLoader.UnlockLumpNum(bp[i]); + p[i] = null; + bp[i] = null; + } + } + + protected void initNoState() { + state = endlevel_state.NoState; + acceleratestage = 0; + cnt = 10; + } + + protected void updateNoState() { + + updateAnimatedBack(); + + if (--cnt == 00) { + End(); + DOOM.WorldDone(); + } + + } + + boolean snl_pointeron = false; + + protected void initShowNextLoc() { + state = endlevel_state.ShowNextLoc; + acceleratestage = 0; + cnt = SHOWNEXTLOCDELAY * TICRATE; + + initAnimatedBack(); + } + + protected void updateShowNextLoc() { + updateAnimatedBack(); + + if ((--cnt == 0) || (acceleratestage != 0)) { + initNoState(); + } else { + snl_pointeron = (cnt & 31) < 20; + } + } + + protected void drawShowNextLoc() { + + int i; + int last; + + slamBackground(); + + // draw animated background + drawAnimatedBack(); + + if (!DOOM.isCommercial()) { + if (wbs.epsd > 2) { + drawEL(); + return; + } + + last = (wbs.last == 8) ? wbs.next - 1 : wbs.last; + + // draw a splat on taken cities. + for (i = 0; i <= last; i++) { + drawOnLnode(i, splat); + } + + // splat the secret level? + if (wbs.didsecret) { + drawOnLnode(8, splat); + } + + // draw flashing ptr + if (snl_pointeron) { + drawOnLnode(wbs.next, yah); + } + } + + // draws which level you are entering.. + if ((!DOOM.isCommercial()) + || wbs.next != 30) { + drawEL(); + } + + } + + protected void drawNoState() { + snl_pointeron = true; + drawShowNextLoc(); + } + + protected int fragSum(int playernum) { + int i; + int frags = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i] + && i != playernum) { + frags += plrs[playernum].frags[i]; + } + } + + // JDC hack - negative frags. + frags -= plrs[playernum].frags[playernum]; + // UNUSED if (frags < 0) + // frags = 0; + + return frags; + } + + int dm_state; + int[][] dm_frags = new int[MAXPLAYERS][MAXPLAYERS]; + int[] dm_totals = new int[MAXPLAYERS]; + + @SourceCode.Exact + @WI_Stuff.C(WI_initDeathmatchStats) + protected void initDeathmatchStats() { + state = endlevel_state.StatCount; + acceleratestage = 0; + dm_state = 1; + + cnt_pause = TICRATE; + + for (int i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + for (int j = 0; j < MAXPLAYERS; j++) { + if (DOOM.playeringame[j]) { + dm_frags[i][j] = 0; + } + } + + dm_totals[i] = 0; + } + } + + WI_initAnimatedBack: + { + initAnimatedBack(); + } + } + + protected void updateDeathmatchStats() { + + int i; + int j; + + boolean stillticking; + + updateAnimatedBack(); + + if ((acceleratestage != 0) && (dm_state != 4)) { + acceleratestage = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) { + if (DOOM.playeringame[j]) { + dm_frags[i][j] = plrs[i].frags[j]; + } + } + + dm_totals[i] = fragSum(i); + } + } + + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + dm_state = 4; + } + + if (dm_state == 2) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) { + if (DOOM.playeringame[j] + && dm_frags[i][j] != plrs[i].frags[j]) { + if (plrs[i].frags[j] < 0) { + dm_frags[i][j]--; + } else { + dm_frags[i][j]++; + } + + if (dm_frags[i][j] > 99) { + dm_frags[i][j] = 99; + } + + if (dm_frags[i][j] < -99) { + dm_frags[i][j] = -99; + } + + stillticking = true; + } + } + dm_totals[i] = fragSum(i); + + if (dm_totals[i] > 99) { + dm_totals[i] = 99; + } + + if (dm_totals[i] < -99) { + dm_totals[i] = -99; + } + } + + } + if (!stillticking) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + dm_state++; + } + + } else if (dm_state == 4) { + if (acceleratestage != 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_slop); + + if (DOOM.isCommercial()) { + initNoState(); + } else { + initShowNextLoc(); + } + } + } else if ((dm_state & 1) != 0) { + if (--cnt_pause == 0) { + dm_state++; + cnt_pause = TICRATE; + } + } + } + + protected void drawDeathmatchStats() { + + int i; + int j; + int x; + int y; + int w; + + int lh = WI_SPACINGY; // line height + + slamBackground(); + + // draw animated background + drawAnimatedBack(); + drawLF(); + + // draw stat titles (top line) + DOOM.graphicSystem.DrawPatch(FG, total, DM_TOTALSX - total.width / 2, DM_MATRIXY - WI_SPACINGY + 10); + DOOM.graphicSystem.DrawPatch(FG, killers, DM_KILLERSX, DM_KILLERSY); + DOOM.graphicSystem.DrawPatch(FG, victims, DM_VICTIMSX, DM_VICTIMSY); + + // draw P? + x = DM_MATRIXX + DM_SPACINGX; + y = DM_MATRIXY; + + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + DOOM.graphicSystem.DrawPatch(FG, p[i], x - p[i].width / 2, DM_MATRIXY - WI_SPACINGY); + DOOM.graphicSystem.DrawPatch(FG, p[i], DM_MATRIXX - p[i].width / 2, y); + + if (i == me) { + DOOM.graphicSystem.DrawPatch(FG, bstar, x - p[i].width / 2, DM_MATRIXY - WI_SPACINGY); + DOOM.graphicSystem.DrawPatch(FG, star, DM_MATRIXX - p[i].width / 2, y); + } + } else { + // V_DrawPatch(x-SHORT(bp[i].width)/2, + // DM_MATRIXY - WI_SPACINGY, FB, bp[i]); + // V_DrawPatch(DM_MATRIXX-SHORT(bp[i].width)/2, + // y, FB, bp[i]); + } + x += DM_SPACINGX; + y += WI_SPACINGY; + } + + // draw stats + y = DM_MATRIXY + 10; + w = num[0].width; + + for (i = 0; i < MAXPLAYERS; i++) { + x = DM_MATRIXX + DM_SPACINGX; + + if (DOOM.playeringame[i]) { + for (j = 0; j < MAXPLAYERS; j++) { + if (DOOM.playeringame[j]) { + drawNum(x + w, y, dm_frags[i][j], 2); + } + + x += DM_SPACINGX; + } + drawNum(DM_TOTALSX + w, y, dm_totals[i], 2); + } + y += WI_SPACINGY; + } + } + + int[] cnt_frags = new int[MAXPLAYERS]; + int dofrags; + int ng_state; + + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @WI_Stuff.C(WI_initNetgameStats) + protected void initNetgameStats() { + state = endlevel_state.StatCount; + acceleratestage = 0; + ng_state = 1; + + cnt_pause = TICRATE; + + for (int i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_kills[i] = cnt_items[i] = cnt_secret[i] = cnt_frags[i] = 0; + + dofrags += fragSum(i); + } + + //Suspicious - Good Sign 2017/05/08 + dofrags = ~ ~dofrags; + + WI_initAnimatedBack: + { + initAnimatedBack(); + } + } + + protected void updateNetgameStats() { + + int i; + int fsum; + + boolean stillticking; + + updateAnimatedBack(); + + if (acceleratestage != 0 && ng_state != 10) { + acceleratestage = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_kills[i] = (plrs[i].skills * 100) / wbs.maxkills; + cnt_items[i] = (plrs[i].sitems * 100) / wbs.maxitems; + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs.maxsecret; + + if (dofrags != 0) { + cnt_frags[i] = fragSum(i); + } + } + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + ng_state = 10; + } + + if (ng_state == 2) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_kills[i] += 2; + + if (cnt_kills[i] >= (plrs[i].skills * 100) / wbs.maxkills) { + cnt_kills[i] = (plrs[i].skills * 100) / wbs.maxkills; + } else { + stillticking = true; + } + } + + if (!stillticking) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + ng_state++; + } + } else if (ng_state == 4) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_items[i] += 2; + if (cnt_items[i] >= (plrs[i].sitems * 100) / wbs.maxitems) { + cnt_items[i] = (plrs[i].sitems * 100) / wbs.maxitems; + } else { + stillticking = true; + } + } + if (!stillticking) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + ng_state++; + } + } else if (ng_state == 6) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_secret[i] += 2; + + if (cnt_secret[i] >= (plrs[i].ssecret * 100) / wbs.maxsecret) { + cnt_secret[i] = (plrs[i].ssecret * 100) / wbs.maxsecret; + } else { + stillticking = true; + } + } + + if (!stillticking) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + ng_state += 1 + 2 * ~dofrags; + } + } else if (ng_state == 8) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + stillticking = false; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + cnt_frags[i] += 1; + + if (cnt_frags[i] >= (fsum = fragSum(i))) { + cnt_frags[i] = fsum; + } else { + stillticking = true; + } + } + + if (!stillticking) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pldeth); + ng_state++; + } + } else if (ng_state == 10) { + if (acceleratestage != 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_sgcock); + if (DOOM.isCommercial()) { + initNoState(); + } else { + initShowNextLoc(); + } + } + } else if ((ng_state & 1) != 0) { + if (--cnt_pause == 0) { + ng_state++; + cnt_pause = TICRATE; + } + } + } + + protected void drawNetgameStats() { + int i; + int x; + int y; + int pwidth = percent.width; + + slamBackground(); + + // draw animated background + drawAnimatedBack(); + + drawLF(); + + // draw stat titles (top line) + DOOM.graphicSystem.DrawPatchScaled(FG, kills, DOOM.vs, NG_STATSX() + NG_SPACINGX - kills.width, NG_STATSY); + DOOM.graphicSystem.DrawPatchScaled(FG, items, DOOM.vs, NG_STATSX() + 2 * NG_SPACINGX - items.width, NG_STATSY); + DOOM.graphicSystem.DrawPatchScaled(FG, secret, DOOM.vs, NG_STATSX() + 3 * NG_SPACINGX - secret.width, NG_STATSY); + + if (dofrags != 0) { + DOOM.graphicSystem.DrawPatchScaled(FG, frags, DOOM.vs, NG_STATSX() + 4 * NG_SPACINGX - frags.width, NG_STATSY); + } + + // draw stats + y = NG_STATSY + kills.height; + + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + + x = NG_STATSX(); + DOOM.graphicSystem.DrawPatchScaled(FG, p[i], DOOM.vs, x - p[i].width, y); + + if (i == me) { + DOOM.graphicSystem.DrawPatchScaled(FG, star, DOOM.vs, x - p[i].width, y); + } + + x += NG_SPACINGX; + drawPercent((x - pwidth) * DOOM.vs.getScalingX(), (y + 10) * DOOM.vs.getScalingY(), cnt_kills[i]); + x += NG_SPACINGX; + drawPercent((x - pwidth) * DOOM.vs.getScalingX(), (y + 10) * DOOM.vs.getScalingY(), cnt_items[i]); + x += NG_SPACINGX; + drawPercent((x - pwidth) * DOOM.vs.getScalingX(), (y + 10) * DOOM.vs.getScalingY(), cnt_secret[i]); + x += NG_SPACINGX; + + if (dofrags != 0) { + drawNum(x * DOOM.vs.getScalingX(), (y + 10) * DOOM.vs.getScalingY(), cnt_frags[i], -1); + } + + y += WI_SPACINGY; + } + + } + + int sp_state; + + @SourceCode.Exact + @WI_Stuff.C(WI_initStats) + protected void initStats() { + state = endlevel_state.StatCount; + acceleratestage = 0; + sp_state = 1; + cnt_kills[0] = cnt_items[0] = cnt_secret[0] = -1; + cnt_time = cnt_par = -1; + cnt_pause = TICRATE; + + WI_initAnimatedBack: + { + initAnimatedBack(); + } + } + + protected void updateStats() { + + updateAnimatedBack(); + + //System.out.println("SP_State "+sp_state); + if ((acceleratestage != 0) && sp_state != COUNT_DONE) { + acceleratestage = 0; + cnt_kills[0] = (plrs[me].skills * 100) / wbs.maxkills; + cnt_items[0] = (plrs[me].sitems * 100) / wbs.maxitems; + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs.maxsecret; + cnt_time = plrs[me].stime / TICRATE; + cnt_par = wbs.partime / TICRATE; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + sp_state = 10; + } + + if (sp_state == COUNT_KILLS) { + cnt_kills[0] += 2; + + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + if (cnt_kills[0] >= (plrs[me].skills * 100) / wbs.maxkills) { + cnt_kills[0] = (plrs[me].skills * 100) / wbs.maxkills; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + sp_state++; + } + } else if (sp_state == COUNT_ITEMS) { + cnt_items[0] += 2; + + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + if (cnt_items[0] >= (plrs[me].sitems * 100) / wbs.maxitems) { + cnt_items[0] = (plrs[me].sitems * 100) / wbs.maxitems; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + sp_state++; + } + } else if (sp_state == COUNT_SECRETS) { + cnt_secret[0] += 2; + + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + if (cnt_secret[0] >= (plrs[me].ssecret * 100) / wbs.maxsecret) { + cnt_secret[0] = (plrs[me].ssecret * 100) / wbs.maxsecret; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + sp_state++; + } + } else if (sp_state == COUNT_TIME) { + if ((bcnt & 3) == 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + + cnt_time += 3; + + if (cnt_time >= plrs[me].stime / TICRATE) { + cnt_time = plrs[me].stime / TICRATE; + } + + cnt_par += 3; + + if (cnt_par >= wbs.partime / TICRATE) { + cnt_par = wbs.partime / TICRATE; + + if (cnt_time >= plrs[me].stime / TICRATE) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_barexp); + sp_state++; + } + } + } else if (sp_state == COUNT_DONE) { + if (acceleratestage != 0) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_sgcock); + + if (DOOM.isCommercial()) { + initNoState(); + } else { + initShowNextLoc(); + } + } + } // Non-drawing, pausing state. Any odd value introduces a 35 tic pause. + else if ((sp_state & 1) > 0) { + if (--cnt_pause == 0) { + sp_state++; + cnt_pause = TICRATE; + } + } + + } + + protected void drawStats() { + // line height + int lh; + + lh = (3 * num[0].height * DOOM.vs.getScalingY()) / 2; + + slamBackground(); + + // draw animated background + drawAnimatedBack(); + + drawLF(); + + DOOM.graphicSystem.DrawPatchScaled(FG, kills, DOOM.vs, SP_STATSX, SP_STATSY, V_NOSCALESTART); + drawPercent(DOOM.vs.getScreenWidth() - SP_STATSX, SP_STATSY, cnt_kills[0]); + + DOOM.graphicSystem.DrawPatchScaled(FG, items, DOOM.vs, SP_STATSX, SP_STATSY + lh, V_NOSCALESTART); + drawPercent(DOOM.vs.getScreenWidth() - SP_STATSX, SP_STATSY + lh, cnt_items[0]); + + DOOM.graphicSystem.DrawPatchScaled(FG, sp_secret, DOOM.vs, SP_STATSX, SP_STATSY + 2 * lh, V_NOSCALESTART); + drawPercent(DOOM.vs.getScreenWidth() - SP_STATSX, SP_STATSY + 2 * lh, cnt_secret[0]); + + DOOM.graphicSystem.DrawPatchScaled(FG, time, DOOM.vs, SP_TIMEX, SP_TIMEY, V_NOSCALESTART); + drawTime(DOOM.vs.getScreenWidth() / 2 - SP_TIMEX, SP_TIMEY, cnt_time); + + if (wbs.epsd < 3) { + DOOM.graphicSystem.DrawPatchScaled(FG, par, DOOM.vs, DOOM.vs.getScreenWidth() / 2 + SP_TIMEX, SP_TIMEY, V_NOSCALESTART); + drawTime(DOOM.vs.getScreenWidth() - SP_TIMEX, SP_TIMEY, cnt_par); + } + + } + + protected void checkForAccelerate() { + + // check for button presses to skip delays + for (int i = 0; i < MAXPLAYERS; i++) { + player_t player = DOOM.players[i]; + if (DOOM.playeringame[i]) { + if ((player.cmd.buttons & BT_ATTACK) != 0) { + if (!player.attackdown) { + acceleratestage = 1; + } + player.attackdown = true; + } else { + player.attackdown = false; + } + if ((player.cmd.buttons & BT_USE) != 0) { + if (!player.usedown) { + acceleratestage = 1; + } + player.usedown = true; + } else { + player.usedown = false; + } + } + } + } + + /** + * Updates stuff each tick + */ + public void Ticker() { + // counter for general background animation + bcnt++; + + if (bcnt == 1) { + // intermission music + if (DOOM.isCommercial()) { + DOOM.doomSound.ChangeMusic(musicenum_t.mus_dm2int.ordinal(), true); + } else { + DOOM.doomSound.ChangeMusic(musicenum_t.mus_inter.ordinal(), true); + } + } + + checkForAccelerate(); +//System.out.println("State "+state); + + switch (state) { + case StatCount: + if (DOOM.deathmatch) { + updateDeathmatchStats(); + } else if (DOOM.netgame) { + updateNetgameStats(); + } else { + updateStats(); + } + break; + + case ShowNextLoc: + updateShowNextLoc(); + break; + + case NoState: + updateNoState(); + break; + case JustShutOff: + // We just finished, and graphics have been unloaded. + // If we don't consume a tick in this way, Doom + // will try to draw unloaded graphics. + state = endlevel_state.NoState; + break; + } + + } + + @SourceCode.Compatible + @WI_Stuff.C(WI_loadData) + protected void loadData() { + String name; + anim_t a; + + if (DOOM.isCommercial()) { + name = "INTERPIC"; + } else { //sprintf(name, "WIMAP%d", wbs.epsd); + name = ("WIMAP" + Integer.toString(wbs.epsd)); + } + + // MAES: For Ultimate Doom + if (DOOM.isRetail()) { + if (wbs.epsd == 3) { + name = "INTERPIC"; + } + } + + // background - draw it to screen 1 for quick redraw. + bg = DOOM.wadLoader.CacheLumpName(name, PU_CACHE, patch_t.class); + DOOM.graphicSystem.DrawPatchScaled(BG, bg, DOOM.vs, 0, 0, V_SAFESCALE); + + // UNUSED unsigned char *pic = screens[1]; + // if (gamemode == commercial) + // { + // darken the background image + // while (pic != screens[1] + DOOM.vs.getScreenHeight()*DOOM.vs.getScreenWidth()) + // { + // *pic = colormaps[256*25 + *pic]; + // pic++; + // } + //} + if (DOOM.isCommercial()) { + NUMCMAPS = 32; + + lnames = new patch_t[NUMCMAPS]; + String xxx = "CWILV%02d"; + //String buffer; + for (int i = 0; i < NUMCMAPS; i++) { + name = String.format(xxx, i); + lnames[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + } + } else { + lnames = new patch_t[NUMMAPS]; + String xxx = "WILV%d%d"; + + for (int i = 0; i < NUMMAPS; i++) { + name = String.format(xxx, wbs.epsd, i); + lnames[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + } + + // you are here + yah[0] = DOOM.wadLoader.CacheLumpName("WIURH0", PU_STATIC, patch_t.class); + + // you are here (alt.) + yah[1] = DOOM.wadLoader.CacheLumpName("WIURH1", PU_STATIC, patch_t.class); + + yah[2] = null; + + // splat + splat = new patch_t[]{DOOM.wadLoader.CacheLumpName("WISPLAT", PU_STATIC, patch_t.class), null}; + + if (wbs.epsd < 3) { + xxx = "WIA%d%02d%02d"; + //xxx=new PrintfFormat("WIA%d%.2d%.2d"); + for (int j = 0; j < NUMANIMS[wbs.epsd]; j++) { + a = anims[wbs.epsd][j]; + for (int i = 0; i < a.nanims; i++) { + // MONDO HACK! + if (wbs.epsd != 1 || j != 8) { + // animations + name = String.format(xxx, wbs.epsd, j, i); + a.p[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + } else { + // HACK ALERT! + a.p[i] = anims[1][4].p[i]; + } + } + } + } + } + + // More hacks on minus sign. + wiminus = DOOM.wadLoader.CacheLumpName("WIMINUS", PU_STATIC, patch_t.class); + + String xxx = "WINUM%d"; + for (int i = 0; i < 10; i++) { + // numbers 0-9 + name = String.format(xxx, i); + num[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + } + + // percent sign + percent = DOOM.wadLoader.CacheLumpName("WIPCNT", PU_STATIC, patch_t.class); + + // "finished" + finished = DOOM.wadLoader.CacheLumpName("WIF", PU_STATIC, patch_t.class); + + // "entering" + entering = DOOM.wadLoader.CacheLumpName("WIENTER", PU_STATIC, patch_t.class); + + // "kills" + kills = DOOM.wadLoader.CacheLumpName("WIOSTK", PU_STATIC, patch_t.class); + + // "scrt" + secret = DOOM.wadLoader.CacheLumpName("WIOSTS", PU_STATIC, patch_t.class); + + // "secret" + sp_secret = DOOM.wadLoader.CacheLumpName("WISCRT2", PU_STATIC, patch_t.class); + + // Yuck. + if (DOOM.language == Language_t.french) { + // "items" + if (DOOM.netgame && !DOOM.deathmatch) { + items = DOOM.wadLoader.CacheLumpName("WIOBJ", PU_STATIC, patch_t.class); + } else { + items = DOOM.wadLoader.CacheLumpName("WIOSTI", PU_STATIC, patch_t.class); + } + } else { + items = DOOM.wadLoader.CacheLumpName("WIOSTI", PU_STATIC, patch_t.class); + } + + // "frgs" + frags = DOOM.wadLoader.CacheLumpName("WIFRGS", PU_STATIC, patch_t.class); + + // ":" + colon = DOOM.wadLoader.CacheLumpName("WICOLON", PU_STATIC, patch_t.class); + + // "time" + time = DOOM.wadLoader.CacheLumpName("WITIME", PU_STATIC, patch_t.class); + + // "sucks" + sucks = DOOM.wadLoader.CacheLumpName("WISUCKS", PU_STATIC, patch_t.class); + + // "par" + par = DOOM.wadLoader.CacheLumpName("WIPAR", PU_STATIC, patch_t.class); + + // "killers" (vertical) + killers = DOOM.wadLoader.CacheLumpName("WIKILRS", PU_STATIC, patch_t.class); + + // "victims" (horiz) + victims = DOOM.wadLoader.CacheLumpName("WIVCTMS", PU_STATIC, patch_t.class); + + // "total" + total = DOOM.wadLoader.CacheLumpName("WIMSTT", PU_STATIC, patch_t.class); + + // your face + star = DOOM.wadLoader.CacheLumpName("STFST01", PU_STATIC, patch_t.class); + + // dead face + bstar = DOOM.wadLoader.CacheLumpName("STFDEAD0", PU_STATIC, patch_t.class); + + String xx1 = "STPB%d"; + String xx2 = "WIBP%d"; + for (int i = 0; i < MAXPLAYERS; i++) { + // "1,2,3,4" + name = String.format(xx1, i); + p[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + + // "1,2,3,4" + name = String.format(xx2, i + 1); + bp[i] = DOOM.wadLoader.CacheLumpName(name, PU_STATIC, patch_t.class); + } + + } + + /* + +public void WI_unloadData() +{ + int i; + int j; + + W.UnlockLumpNum(wiminus, PU_CACHE); + + for (i=0 ; i<10 ; i++) + W.UnlockLumpNum(num[i], PU_CACHE); + + if (gamemode == commercial) + { + for (i=0 ; i 2) { + wbs.epsd -= 3; + } + } + } + + @SourceCode.Exact + @WI_Stuff.C(WI_Start) + public void Start(wbstartstruct_t wbstartstruct) { + WI_initVariables: + { + initVariables(wbstartstruct); + } + WI_loadData: + { + loadData(); + } + + if (DOOM.deathmatch) { + WI_initDeathmatchStats: + { + initDeathmatchStats(); + } + } else if (DOOM.netgame) { + WI_initNetgameStats: + { + initNetgameStats(); + } + } else { + WI_initStats: + { + initStats(); + } + } + } + + protected int NG_STATSX() { + return 32 + star.width / 2 + 32 * (!(dofrags > 0) ? 1 : 0); + } + + protected static boolean RNGCHECK(int what, int min, int max) { + return (what >= min && what <= max); + } +} \ No newline at end of file diff --git a/doom/src/f/Finale.java b/doom/src/f/Finale.java new file mode 100644 index 0000000..f579d23 --- /dev/null +++ b/doom/src/f/Finale.java @@ -0,0 +1,702 @@ +package f; + +import static data.Defines.FF_FRAMEMASK; +import static data.Defines.HU_FONTSIZE; +import static data.Defines.HU_FONTSTART; +import static data.Defines.PU_CACHE; +import static data.Defines.PU_LEVEL; +import static data.Limits.MAXPLAYERS; +import static data.info.mobjinfo; +import static data.info.states; +import data.mobjtype_t; +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import data.state_t; +import defines.gamestate_t; +import defines.statenum_t; +import doom.DoomMain; +import doom.SourceCode.F_Finale; +import static doom.SourceCode.F_Finale.F_Responder; +import static doom.englsh.*; +import doom.event_t; +import doom.evtype_t; +import doom.gameaction_t; +import java.awt.Rectangle; +import java.io.IOException; +import m.Settings; +import mochadoom.Engine; +import rr.flat_t; +import rr.patch_t; +import rr.spritedef_t; +import rr.spriteframe_t; +import static utils.C2JUtils.eval; +import static v.DoomGraphicSystem.V_FLIPPEDPATCH; +import v.graphics.Blocks; +import v.renderers.DoomScreen; +import static v.renderers.DoomScreen.FG; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: Finale.java,v 1.28 2012/09/24 17:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Game completion, final screen animation. +// +//----------------------------------------------------------------------------- +public class Finale { + + final DoomMain DOOM; + int finalestage; + int finalecount; + + private static final int TEXTSPEED = 3; + private static final int TEXTWAIT = 250; + + final static String[] doom_text = {E1TEXT, E2TEXT, E3TEXT, E4TEXT}; + final static String[] doom2_text = {C1TEXT, C2TEXT, C3TEXT, C4TEXT, C5TEXT, C6TEXT}; + final static String[] plut_text = {P1TEXT, P2TEXT, P3TEXT, P4TEXT, P5TEXT, P6TEXT}; + final static String[] tnt_text = {T1TEXT, T2TEXT, T3TEXT, T4TEXT, T5TEXT, T6TEXT}; + String finaletext; + String finaleflat; + + /** + * F_StartFinale + */ + public void StartFinale() { + DOOM.setGameAction(gameaction_t.ga_nothing); + DOOM.gamestate = gamestate_t.GS_FINALE; + DOOM.viewactive = false; + DOOM.automapactive = false; + String[] texts = null; + + // Pick proper text. + switch (DOOM.getGameMode()) { + case commercial: + case pack_xbla: + case freedoom2: + case freedm: + texts = doom2_text; + break; + case pack_tnt: + texts = tnt_text; + break; + case pack_plut: + texts = plut_text; + break; + case shareware: + case registered: + case retail: + case freedoom1: + texts = doom_text; + break; + default: + break; + } + + // Okay - IWAD dependend stuff. + // This has been changed severly, and + // some stuff might have changed in the process. + switch (DOOM.getGameMode()) { + + // DOOM 1 - E1, E3 or E4, but each nine missions + case freedoom1: + case shareware: + case registered: + case retail: { + DOOM.doomSound.ChangeMusic(musicenum_t.mus_victor, true); + + switch (DOOM.gameepisode) { + case 1: + finaleflat = "FLOOR4_8"; + finaletext = texts[0]; + break; + case 2: + finaleflat = "SFLR6_1"; + finaletext = texts[1]; + break; + case 3: + finaleflat = "MFLR8_4"; + finaletext = texts[2]; + break; + case 4: + finaleflat = "MFLR8_3"; + finaletext = texts[3]; + break; + default: + // Ouch. + break; + } + break; + } + + // DOOM II and missions packs with E1, M34 + case freedm: + case freedoom2: + case commercial: + case pack_xbla: + case pack_tnt: + case pack_plut: { + DOOM.doomSound.ChangeMusic(musicenum_t.mus_read_m, true); + + switch (DOOM.gamemap) { + case 6: + finaleflat = "SLIME16"; + finaletext = texts[0]; + break; + case 11: + finaleflat = "RROCK14"; + finaletext = texts[1]; + break; + case 20: + finaleflat = "RROCK07"; + finaletext = texts[2]; + break; + case 30: + finaleflat = "RROCK17"; + finaletext = texts[3]; + break; + case 15: + finaleflat = "RROCK13"; + finaletext = texts[4]; + break; + case 31: + finaleflat = "RROCK19"; + finaletext = texts[5]; + break; + default: + // Ouch. + break; + } + break; + } + + // Indeterminate. + default: + DOOM.doomSound.ChangeMusic(musicenum_t.mus_read_m, true); + finaleflat = "F_SKY1"; // Not used anywhere else. + finaletext = doom2_text[1]; + break; + } + + finalestage = 0; + finalecount = 0; + + } + + @F_Finale.C(F_Responder) + public boolean Responder(event_t event) { + if (finalestage == 2) { + return CastResponder(event); + } + + return false; + } + + /** + * F_Ticker + */ + public void Ticker() { + + // check for skipping + if ((DOOM.isCommercial()) && (finalecount > 50)) { + int i; + // go on to the next level + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.players[i].cmd.buttons != 0) { + break; + } + } + + if (i < MAXPLAYERS) { + if (DOOM.gamemap == 30) { + StartCast(); + } else { + DOOM.setGameAction(gameaction_t.ga_worlddone); + } + } + } + + // advance animation + finalecount++; + + if (finalestage == 2) { + CastTicker(); + return; + } + + if (DOOM.isCommercial()) { + return; + } + + // MAES: this is when we can transition to bunny. + if ((finalestage == 0) && finalecount > finaletext.length() * TEXTSPEED + TEXTWAIT) { + finalecount = 0; + finalestage = 1; + DOOM.wipegamestate = gamestate_t.GS_MINUS_ONE; // force a wipe + + if (DOOM.gameepisode == 3) { + DOOM.doomSound.StartMusic(musicenum_t.mus_bunny); + } + } + } + + // + // F_TextWrite + // + // #include "hu_stuff.h" + patch_t[] hu_font; + + @SuppressWarnings("unchecked") + public void TextWrite() { + // erase the entire screen to a tiled background + byte[] src = DOOM.wadLoader.CacheLumpName(finaleflat, PU_CACHE, flat_t.class).data; + if (Engine.getConfig().equals(Settings.scale_screen_tiles, Boolean.TRUE)) { + final Object scaled = ((Blocks) DOOM.graphicSystem) + .ScaleBlock(DOOM.graphicSystem.convertPalettedBlock(src), 64, 64, + DOOM.graphicSystem.getScalingX(), DOOM.graphicSystem.getScalingY() + ); + + ((Blocks) DOOM.graphicSystem) + .TileScreen(FG, scaled, new Rectangle(0, 0, + 64 * DOOM.graphicSystem.getScalingX(), 64 * DOOM.graphicSystem.getScalingY()) + ); + } else { + ((Blocks) DOOM.graphicSystem) + .TileScreen(FG, DOOM.graphicSystem.convertPalettedBlock(src), + new Rectangle(0, 0, 64, 64) + ); + } + + // draw some of the text onto the screen + int cx = 10, cy = 10; + final char[] ch = finaletext.toCharArray(); + + int count = (finalecount - 10) / TEXTSPEED; + if (count < 0) { + count = 0; + } + + // _D_: added min between count and ch.length, so that the text is not + // written all at once + for (int i = 0; i < Math.min(ch.length, count); i++) { + int c = ch[i]; + if (c == 0) { + break; + } + if (c == '\n') { + cx = 10; + cy += 11; + continue; + } + + c = Character.toUpperCase(c) - HU_FONTSTART; + if (c < 0 || c > HU_FONTSIZE) { + cx += 4; + continue; + } + + if (cx + hu_font[c].width > DOOM.vs.getScreenWidth()) { + break; + } + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, cx, cy); + cx += hu_font[c].width; + } + + } + + private final castinfo_t[] castorder; + + int castnum; + int casttics; + state_t caststate; + boolean castdeath; + int castframes; + int castonmelee; + boolean castattacking; + + // + // F_StartCast + // + // extern gamestate_t wipegamestate; + public void StartCast() { + DOOM.wipegamestate = gamestate_t.GS_MINUS_ONE; // force a screen wipe + castnum = 0; + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].seestate.ordinal()]; + casttics = caststate.tics; + castdeath = false; + finalestage = 2; + castframes = 0; + castonmelee = 0; + castattacking = false; + DOOM.doomSound.ChangeMusic(musicenum_t.mus_evil, true); + } + + // + // F_CastTicker + // + public void CastTicker() { + if (--casttics > 0) { + return; // not time to change state yet + } + if (caststate.tics == -1 || caststate.nextstate == statenum_t.S_NULL || caststate.nextstate == null) { + // switch from deathstate to next monster + castnum++; + castdeath = false; + if (castorder[castnum].name == null) { + castnum = 0; + } + + if (mobjinfo[castorder[castnum].type.ordinal()].seesound.ordinal() != 0) { + DOOM.doomSound.StartSound(null, mobjinfo[castorder[castnum].type.ordinal()].seesound); + } + + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].seestate.ordinal()]; + castframes = 0; + } else { + final sfxenum_t sfx; + + // just advance to next state in animation + if (caststate == states[statenum_t.S_PLAY_ATK1.ordinal()]) { + stopattack(); // Oh, gross hack! + afterstopattack(); + return; // bye ... + } + + final statenum_t st = caststate.nextstate; + caststate = states[st.ordinal()]; + castframes++; + + // sound hacks.... + switch (st) { + case S_PLAY_ATK1: + sfx = sfxenum_t.sfx_dshtgn; + break; + case S_POSS_ATK2: + sfx = sfxenum_t.sfx_pistol; + break; + case S_SPOS_ATK2: + sfx = sfxenum_t.sfx_shotgn; + break; + case S_VILE_ATK2: + sfx = sfxenum_t.sfx_vilatk; + break; + case S_SKEL_FIST2: + sfx = sfxenum_t.sfx_skeswg; + break; + case S_SKEL_FIST4: + sfx = sfxenum_t.sfx_skepch; + break; + case S_SKEL_MISS2: + sfx = sfxenum_t.sfx_skeatk; + break; + case S_FATT_ATK8: + case S_FATT_ATK5: + case S_FATT_ATK2: + sfx = sfxenum_t.sfx_firsht; + break; + case S_CPOS_ATK2: + case S_CPOS_ATK3: + case S_CPOS_ATK4: + sfx = sfxenum_t.sfx_shotgn; + break; + case S_TROO_ATK3: + sfx = sfxenum_t.sfx_claw; + break; + case S_SARG_ATK2: + sfx = sfxenum_t.sfx_sgtatk; + break; + case S_BOSS_ATK2: + case S_BOS2_ATK2: + case S_HEAD_ATK2: + sfx = sfxenum_t.sfx_firsht; + break; + case S_SKULL_ATK2: + sfx = sfxenum_t.sfx_sklatk; + break; + case S_SPID_ATK2: + case S_SPID_ATK3: + sfx = sfxenum_t.sfx_shotgn; + break; + case S_BSPI_ATK2: + sfx = sfxenum_t.sfx_plasma; + break; + case S_CYBER_ATK2: + case S_CYBER_ATK4: + case S_CYBER_ATK6: + sfx = sfxenum_t.sfx_rlaunc; + break; + case S_PAIN_ATK3: + sfx = sfxenum_t.sfx_sklatk; + break; + default: + sfx = null; + break; + } + + if (sfx != null) {// Fixed mute thanks to _D_ 8/6/2011 + DOOM.doomSound.StartSound(null, sfx); + } + } + + if (castframes == 12) { + // go into attack frame + castattacking = true; + if (castonmelee != 0) { + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].meleestate.ordinal()]; + } else { + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].missilestate.ordinal()]; + } + castonmelee ^= 1; + if (caststate == states[statenum_t.S_NULL.ordinal()]) { + if (castonmelee != 0) { + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].meleestate.ordinal()]; + } else { + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].missilestate.ordinal()]; + } + } + } + + if (castattacking) { + if (castframes == 24 || caststate == states[mobjinfo[castorder[castnum].type.ordinal()].seestate.ordinal()]) { + stopattack(); + } + } + + afterstopattack(); + } + + protected void stopattack() { + castattacking = false; + castframes = 0; + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].seestate.ordinal()]; + } + + protected void afterstopattack() { + casttics = caststate.tics; + + if (casttics == -1) { + casttics = 15; + } + } + + /** + * CastResponder + */ + public boolean CastResponder(event_t ev) { + if (!ev.isType(evtype_t.ev_keydown)) { + return false; + } + + if (castdeath) { + return true; // already in dying frames + } + + // go into death frame + castdeath = true; + caststate = states[mobjinfo[castorder[castnum].type.ordinal()].deathstate.ordinal()]; + casttics = caststate.tics; + castframes = 0; + castattacking = false; + + if (mobjinfo[castorder[castnum].type.ordinal()].deathsound != null) { + DOOM.doomSound.StartSound(null, mobjinfo[castorder[castnum].type.ordinal()].deathsound); + } + + return true; + } + + public void CastPrint(String text) { + int c, width = 0; + + // find width + final char[] ch = text.toCharArray(); + + for (int i = 0; i < ch.length; i++) { + c = ch[i]; + if (c == 0) { + break; + } + c = Character.toUpperCase(c) - HU_FONTSTART; + if (c < 0 || c > HU_FONTSIZE) { + width += 4; + continue; + } + + width += hu_font[c].width; + } + + // draw it + int cx = 160 - width / 2; + // ch = text; + for (int i = 0; i < ch.length; i++) { + c = ch[i]; + if (c == 0) { + break; + } + c = Character.toUpperCase(c) - HU_FONTSTART; + if (c < 0 || c > HU_FONTSIZE) { + cx += 4; + continue; + } + + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, cx, 180); + cx += hu_font[c].width; + } + } + + /** + * F_CastDrawer + * + * @throws IOException + */ + // public void V_DrawPatchFlipped (int x, int y, int scrn, patch_t patch); + public void CastDrawer() { + // erase the entire screen to a background + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("BOSSBACK", PU_CACHE), DOOM.vs, 0, 0); + this.CastPrint(castorder[castnum].name); + + // draw the current frame in the middle of the screen + final spritedef_t sprdef = DOOM.spriteManager.getSprite(caststate.sprite.ordinal()); + final spriteframe_t sprframe = sprdef.spriteframes[caststate.frame & FF_FRAMEMASK]; + final int lump = sprframe.lump[0]; + final boolean flip = eval(sprframe.flip[0]); + // flip=false; + // lump=0; + + final patch_t patch = DOOM.wadLoader.CachePatchNum(lump + DOOM.spriteManager.getFirstSpriteLump()); + + if (flip) { + DOOM.graphicSystem.DrawPatchScaled(FG, patch, DOOM.vs, 160, 170, V_FLIPPEDPATCH); + } else { + DOOM.graphicSystem.DrawPatchScaled(FG, patch, DOOM.vs, 160, 170); + } + } + + protected int laststage; + + /** + * F_BunnyScroll + */ + public void BunnyScroll() { + final patch_t p1 = DOOM.wadLoader.CachePatchName("PFUB2", PU_LEVEL); + final patch_t p2 = DOOM.wadLoader.CachePatchName("PFUB1", PU_LEVEL); + + //V.MarkRect(0, 0, DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight()); + int scrolled = 320 - (finalecount - 230) / 2; + + if (scrolled > 320) { + scrolled = 320; + } + + if (scrolled < 0) { + scrolled = 0; + } + + for (int x = 0; x < 320; x++) { + if (x + scrolled < 320) { + DOOM.graphicSystem.DrawPatchColScaled(FG, p1, DOOM.vs, x, x + scrolled); + } else { + DOOM.graphicSystem.DrawPatchColScaled(FG, p2, DOOM.vs, x, x + scrolled - 320); + } + } + + if (finalecount < 1130) { + return; + } else if (finalecount < 1180) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("END0", PU_CACHE), DOOM.vs, (320 - 13 * 8) / 2, ((200 - 8 * 8) / 2)); + laststage = 0; + return; + } + + int stage = (finalecount - 1180) / 5; + + if (stage > 6) { + stage = 6; + } + + if (stage > laststage) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + laststage = stage; + } + + final String name = ("END" + stage); + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(name, PU_CACHE), DOOM.vs, (320 - 13 * 8) / 2, ((200 - 8 * 8) / 2)); + } + + // + // F_Drawer + // + public void Drawer() { + if (finalestage == 2) { + CastDrawer(); + return; + } + + if (finalestage == 0) { + TextWrite(); + } else { + switch (DOOM.gameepisode) { + case 1: + if (DOOM.isCommercial() || DOOM.isRegistered()) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("CREDIT", PU_CACHE), this.DOOM.vs, 0, 0); + } else // Fun fact: Registered/Ultimate Doom has no "HELP2" lump. + { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("HELP2", PU_CACHE), this.DOOM.vs, 0, 0); + } + break; + case 2: + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("VICTORY2", PU_CACHE), this.DOOM.vs, 0, 0); + break; + case 3: + BunnyScroll(); + break; + case 4: + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("ENDPIC", PU_CACHE), this.DOOM.vs, 0, 0); + break; + } + } + + } + + public Finale(DoomMain DOOM) { + this.DOOM = DOOM; + hu_font = DOOM.headsUp.getHUFonts(); + + //castinfo_t shit = new castinfo_t(CC_ZOMBIE, mobjtype_t.MT_POSSESSED); + castorder = new castinfo_t[]{ + new castinfo_t(CC_ZOMBIE, mobjtype_t.MT_POSSESSED), + new castinfo_t(CC_SHOTGUN, mobjtype_t.MT_SHOTGUY), + new castinfo_t(CC_HEAVY, mobjtype_t.MT_CHAINGUY), + new castinfo_t(CC_IMP, mobjtype_t.MT_TROOP), + new castinfo_t(CC_DEMON, mobjtype_t.MT_SERGEANT), + new castinfo_t(CC_LOST, mobjtype_t.MT_SKULL), + new castinfo_t(CC_CACO, mobjtype_t.MT_HEAD), + new castinfo_t(CC_HELL, mobjtype_t.MT_KNIGHT), + new castinfo_t(CC_BARON, mobjtype_t.MT_BRUISER), + new castinfo_t(CC_ARACH, mobjtype_t.MT_BABY), + new castinfo_t(CC_PAIN, mobjtype_t.MT_PAIN), + new castinfo_t(CC_REVEN, mobjtype_t.MT_UNDEAD), + new castinfo_t(CC_MANCU, mobjtype_t.MT_FATSO), + new castinfo_t(CC_ARCH, mobjtype_t.MT_VILE), + new castinfo_t(CC_SPIDER, mobjtype_t.MT_SPIDER), + new castinfo_t(CC_CYBER, mobjtype_t.MT_CYBORG), + new castinfo_t(CC_HERO, mobjtype_t.MT_PLAYER), + new castinfo_t(null, null) + }; + } +} + +// /$Log \ No newline at end of file diff --git a/doom/src/f/Wiper.java b/doom/src/f/Wiper.java new file mode 100644 index 0000000..c246055 --- /dev/null +++ b/doom/src/f/Wiper.java @@ -0,0 +1,59 @@ +package f; + +import v.graphics.Wipers; + +public interface Wiper { + + boolean ScreenWipe(Wipers.WipeType type, int x, int y, int width, int height, int ticks); + + boolean EndScreen(int x, int y, int width, int height); + + boolean StartScreen(int x, int y, int width, int height); + + public enum Wipe implements Wipers.WipeType { + // simple gradual pixel change for 8-bit only + // MAES: this transition isn't guaranteed to always terminate + // see Chocolate Strife develpment. Unused in Doom anyway. + ColorXForm( + Wipers.WipeFunc.initColorXForm, + Wipers.WipeFunc.doColorXForm, + Wipers.WipeFunc.exitColorXForm + ), + // weird screen melt + Melt( + Wipers.WipeFunc.initMelt, + Wipers.WipeFunc.doMelt, + Wipers.WipeFunc.exitMelt + ), + ScaledMelt( + Wipers.WipeFunc.initScaledMelt, + Wipers.WipeFunc.doScaledMelt, + Wipers.WipeFunc.exitMelt + ); + + private final Wipers.WipeFunc initFunc; + private final Wipers.WipeFunc doFunc; + private final Wipers.WipeFunc exitFunc; + + @Override + public Wipers.WipeFunc getDoFunc() { + return doFunc; + } + + @Override + public Wipers.WipeFunc getExitFunc() { + return exitFunc; + } + + @Override + public Wipers.WipeFunc getInitFunc() { + return initFunc; + } + + private Wipe(Wipers.WipeFunc initFunc, Wipers.WipeFunc doFunc, Wipers.WipeFunc exitFunc) { + this.initFunc = initFunc; + this.doFunc = doFunc; + this.exitFunc = exitFunc; + } + } +} \ No newline at end of file diff --git a/doom/src/f/anim_t.java b/doom/src/f/anim_t.java new file mode 100644 index 0000000..7565124 --- /dev/null +++ b/doom/src/f/anim_t.java @@ -0,0 +1,85 @@ +package f; + +import rr.patch_t; +import w.animenum_t; + +// +//Animation. +//There is another anim_t used in p_spec. +// +public class anim_t { + + public anim_t(animenum_t type, int period, int nanims, point_t loc, + int data1, int data2, patch_t[] p, int nexttic, int lastdrawn, + int ctr, int state) { + this.type = type; + this.period = period; + this.nanims = nanims; + this.loc = loc; + this.data1 = data1; + this.data2 = data2; + this.p = p; + this.nexttic = nexttic; + this.lastdrawn = lastdrawn; + this.ctr = ctr; + this.state = state; + } + // Partial constructor, only 4 first fields. + + public anim_t(animenum_t animAlways, int period, int nanims, point_t loc + ) { + this.type = animAlways; + this.period = period; + this.nanims = nanims; + this.loc = loc; + } + + // Partial constructor, only 5 first fields. + public anim_t(animenum_t type, int period, int nanims, point_t loc, int data1 + ) { + this.type = type; + this.period = period; + this.nanims = nanims; + this.loc = loc; + this.data1 = data1; + } + + public animenum_t type; + + // period in tics between animations + public int period; + + // number of animation frames + public int nanims; + + // location of animation + point_t loc; + + // ALWAYS: n/a, + // RANDOM: period deviation (<256), + // LEVEL: level + public int data1; + + // ALWAYS: n/a, + // RANDOM: random base period, + // LEVEL: n/a + public int data2; + + // actual graphics for frames of animations + //Maes: was pointer to array + public patch_t[] p = new patch_t[3]; + + // following must be initialized to zero before use! + // next value of bcnt (used in conjunction with period) + public int nexttic; + + // last drawn animation frame + public int lastdrawn; + + // next frame number to animate + public int ctr; + + // used by RANDOM and LEVEL when animating + public int state; + +} \ No newline at end of file diff --git a/doom/src/f/castinfo_t.java b/doom/src/f/castinfo_t.java new file mode 100644 index 0000000..169cfa9 --- /dev/null +++ b/doom/src/f/castinfo_t.java @@ -0,0 +1,22 @@ +package f; + +import data.mobjtype_t; + +/** + * Final DOOM 2 animation Casting by id Software. in order of appearance + */ +public class castinfo_t { + + String name; + mobjtype_t type; + + public castinfo_t() { + + } + + public castinfo_t(String name, mobjtype_t type) { + this.name = name; + this.type = type; + } + +} \ No newline at end of file diff --git a/doom/src/f/point_t.java b/doom/src/f/point_t.java new file mode 100644 index 0000000..497357f --- /dev/null +++ b/doom/src/f/point_t.java @@ -0,0 +1,12 @@ +package f; + +public class point_t { + + public point_t(int x, int y) { + this.x = x; + this.y = y; + } + public int x; + public int y; + +} \ No newline at end of file diff --git a/doom/src/g/DoomGameInterface.java b/doom/src/g/DoomGameInterface.java new file mode 100644 index 0000000..3dcf2b5 --- /dev/null +++ b/doom/src/g/DoomGameInterface.java @@ -0,0 +1,84 @@ +package g; + +import defines.skill_t; +import doom.event_t; +import doom.gameaction_t; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomGameInterface.java,v 1.4 2010/12/20 17:15:08 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Duh. +// +//----------------------------------------------------------------------------- +public interface DoomGameInterface { + +// +// GAME +// + public void DeathMatchSpawnPlayer(int playernum); + + public void InitNew(skill_t skill, int episode, int map); + + /** Can be called by the startup code or M_Responder. + A normal game starts at map 1, + but a warp test can start elsewhere */ + public void DeferedInitNew(skill_t skill, int episode, int map); + + public void DeferedPlayDemo(String demo); + + /** Can be called by the startup code or M_Responder, + calls P_SetupLevel or W_EnterWorld. */ + public void LoadGame(String name); + + public void DoLoadGame(); + + /** Called by M_Responder. */ + public void SaveGame(int slot, String description); + + /** Only called by startup code. */ + public void RecordDemo(String name); + + public void BeginRecording(); + + public void PlayDemo(String name); + + public void TimeDemo(String name); + + public boolean CheckDemoStatus(); + + public void ExitLevel(); + + public void SecretExitLevel(); + + public void WorldDone(); + + public void Ticker(); + + public boolean Responder(event_t ev); + + public void ScreenShot(); + + public gameaction_t getGameAction(); + + public void setGameAction(gameaction_t ga); + + public boolean getPaused(); + + public void setPaused(boolean on); + +} \ No newline at end of file diff --git a/doom/src/g/DoomSaveGame.java b/doom/src/g/DoomSaveGame.java new file mode 100644 index 0000000..62f5bda --- /dev/null +++ b/doom/src/g/DoomSaveGame.java @@ -0,0 +1,167 @@ +package g; + +import static data.Defines.VERSION; +import static data.Limits.MAXPLAYERS; +import static data.Limits.SAVESTRINGSIZE; +import static data.Limits.VERSIONSIZE; +import defines.skill_t; +import doom.DoomStatus; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import utils.C2JUtils; +import w.CacheableDoomObject; +import w.DoomBuffer; +import w.DoomIO; +import w.IReadableDoomObject; +import w.IWritableDoomObject; + +/** represents the header of Doom savegame, so that basic info can be checked quickly. + * + * To load the whole game and check if there are final mistakes, you must go through it all. + * Savegames need to be aware of ALL status and context, so maybe they should be inner classes? + * + */ +public class DoomSaveGame implements CacheableDoomObject, IReadableDoomObject, IWritableDoomObject { + + public DoomSaveGame() { + playeringame = new boolean[MAXPLAYERS]; + } + + public String name; // max size SAVEGAMENAME + public String vcheck; + // These are for DS + public int gameskill; + public int gameepisode; + public int gamemap; + public boolean[] playeringame; + /** what bullshit, stored as 24-bit integer?! */ + public int leveltime; + // These help checking shit. + public boolean wrongversion; + public boolean properend; + + @Override + public void unpack(ByteBuffer buf) throws IOException { + name = DoomBuffer.getNullTerminatedString(buf, SAVESTRINGSIZE); + vcheck = DoomBuffer.getNullTerminatedString(buf, VERSIONSIZE); + String vcheckb = ("version " + VERSION); + // no more unpacking, and report it. + if (wrongversion = !(vcheckb.equalsIgnoreCase(vcheck))) { + return; + } + gameskill = buf.get(); + gameepisode = buf.get(); + gamemap = buf.get(); + + for (int i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = buf.get() != 0; + } + + // load a base level (this doesn't advance the pointer?) + //G_InitNew (gameskill, gameepisode, gamemap); + // get the times + int a = C2JUtils.toUnsignedByte(buf.get()); + int b = C2JUtils.toUnsignedByte(buf.get()); + int c = C2JUtils.toUnsignedByte(buf.get()); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + leveltime = (a << 16) + (b << 8) + c; + + // Mark this position... + buf.mark(); + buf.position(buf.limit() - 1); + properend = buf.get() == 0x1d; + buf.reset(); + + // We've loaded whatever consistutes "header" info, the rest must be unpacked by proper + // methods in the game engine itself. + } + + @Override + public void write(DataOutputStream f) throws IOException { + DoomIO.writeString(f, name, SAVESTRINGSIZE); + DoomIO.writeString(f, vcheck, VERSIONSIZE); + f.writeByte(gameskill); + f.writeByte(gameepisode); + f.writeByte(gamemap); + for (int i = 0; i < MAXPLAYERS; i++) { + f.writeBoolean(playeringame[i]); + } + + // load a base level (this doesn't advance the pointer?) + //G_InitNew (gameskill, gameepisode, gamemap); + // get the times + byte a = (byte) (0x0000FF & (leveltime >>> 16)); + byte b = (byte) (0x00FF & (leveltime >>> 8)); + byte c = (byte) (0x00FF & (leveltime)); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + f.writeByte(a); + f.writeByte(b); + f.writeByte(c); + + // TODO: after this point, we should probably save some packed buffers representing raw state... + // needs further study. + // The end. + f.writeByte(0x1d); + + } + + @Override + public void read(DataInputStream f) throws IOException { + name = DoomIO.readNullTerminatedString(f, SAVESTRINGSIZE); + vcheck = DoomIO.readNullTerminatedString(f, VERSIONSIZE); + String vcheckb = ("version " + VERSION); + // no more unpacking, and report it. + if (wrongversion = !(vcheckb.equalsIgnoreCase(vcheck))) { + return; + } + gameskill = f.readByte(); + gameepisode = f.readByte(); + gamemap = f.readByte(); + playeringame = new boolean[MAXPLAYERS]; + for (int i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = f.readBoolean(); + } + + // load a base level (this doesn't advance the pointer?) + //G_InitNew (gameskill, gameepisode, gamemap); + // get the times + int a = f.readUnsignedByte(); + int b = f.readUnsignedByte(); + int c = f.readUnsignedByte(); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + leveltime = (a << 16) + (b << 8) + c; + + // Mark this position... + //long mark=f.getFilePointer(); + //f.seek(f.length()-1); + //if (f.readByte() != 0x1d) properend=false; else + // properend=true; + //f.seek(mark); + long available = f.available(); + f.skip(available - 1); + properend = f.readByte() == 0x1d; + // We've loaded whatever consistutes "header" info, the rest must be unpacked by proper + // methods in the game engine itself. + } + + public void toStat(DoomStatus DS) { + System.arraycopy(this.playeringame, 0, DS.playeringame, 0, this.playeringame.length); + DS.gameskill = skill_t.values()[this.gameskill]; + DS.gameepisode = this.gameepisode; + DS.gamemap = this.gamemap; + DS.leveltime = this.leveltime; + + } + + public void fromStat(DoomStatus DS) { + System.arraycopy(DS.playeringame, 0, this.playeringame, 0, DS.playeringame.length); + this.gameskill = DS.gameskill.ordinal(); + this.gameepisode = DS.gameepisode; + this.gamemap = DS.gamemap; + this.leveltime = DS.leveltime; + + } + +} \ No newline at end of file diff --git a/doom/src/g/Overflow.java b/doom/src/g/Overflow.java new file mode 100644 index 0000000..12f526b --- /dev/null +++ b/doom/src/g/Overflow.java @@ -0,0 +1,575 @@ +package g; + +import rr.line_t; + +/* Emacs style mode select -*- Java -*- + *----------------------------------------------------------------------------- + * + * + * PrBoom: a Doom port merged with LxDoom and LSDLDoom + * based on BOOM, a modified and improved DOOM engine + * Copyright (C) 1999 by + * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman + * Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * Copyright 2005, 2006 by + * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * DESCRIPTION: + * + * Overflow emulation code, totally ripped off prBoom+! Sieg Heil! + * + *--------------------------------------------------------------------- + */ +public class Overflow { + + String[] overflow_cfgname + = { + "overrun_spechit_emulate", + "overrun_reject_emulate", + "overrun_intercept_emulate", + "overrun_playeringame_emulate", + "overrun_donut_emulate", + "overrun_missedbackside_emulate" + }; + + class overrun_param_t { + + boolean warn; + boolean emulate; + boolean tmp_emulate; + int promted; + int shit_happens; + } + + class intercepts_overrun_t { + + int len; + long addr; + boolean int16_array; + } + + public static final int OVERFLOW_SPECHIT = 0; + public static final int OVERFLOW_REJECT = 1; + public static final int OVERFLOW_INTERCEPT = 2; + public static final int OVERFLOW_PLYERINGAME = 3; + public static final int OVERFLOW_DONUT = 4; + public static final int OVERFLOW_MISSEDBACKSIDE = 5; + public static final int OVERFLOW_MAX = 6; + + public static final int MAXINTERCEPTS_ORIGINAL = 128; + + public static final boolean EMULATE(int overflow) { + return (overflows[overflow].emulate || overflows[overflow].tmp_emulate); + } + + public static final boolean PROCESS(int overflow) { + return (overflows[overflow].warn || EMULATE(overflow)); + } + + static overrun_param_t[] overflows = new overrun_param_t[OVERFLOW_MAX]; + + public static intercepts_overrun_t[] intercepts_overrun; + + /* + static void ShowOverflowWarning(int overflow, int fatal, String ... params) + { + overflows[overflow].shit_happens = true; + + if (overflows[overflow].warn && !overflows[overflow].promted) + { + va_list argptr; + char buffer[1024]; + + static const char *name[OVERFLOW_MAX] = { + "SPECHIT", "REJECT", "INTERCEPT", "PLYERINGAME", "DONUT", "MISSEDBACKSIDE"}; + + static const char str1[] = + "Too big or not supported %s overflow has been detected. " + "Desync or crash can occur soon " + "or during playback with the vanilla engine in case you're recording demo.%s%s"; + + static const char str2[] = + "%s overflow has been detected.%s%s"; + + static const char str3[] = + "%s overflow has been detected. " + "The option responsible for emulation of this overflow is switched off " + "hence desync or crash can occur soon " + "or during playback with the vanilla engine in case you're recording demo.%s%s"; + + overflows[overflow].promted = true; + + sprintf(buffer, + (fatal ? str1 : (EMULATE(overflow) ? str2 : str3)), + name[overflow], + "\nYou can change PrBoom behaviour for this overflow through in-game menu.", + params); + + va_start(argptr, params); + I_vWarning(buffer, argptr); + va_end(argptr); + } + } */ + // e6y + // + // Intercepts Overrun emulation + // See more information on: + // doomworld.com/vb/doom-speed-demos/35214-spechits-reject-and-intercepts-overflow-lists + // + // Thanks to Simon Howard (fraggle) for refactor the intercepts + // overrun code so that it should work properly on big endian machines + // as well as little endian machines. + // Overwrite a specific memory location with a value. + /* + static void InterceptsMemoryOverrun(int location, int value) + { + int i, offset; + int index; + long addr; + + i = 0; + offset = 0; + + // Search down the array until we find the right entry + + while (intercepts_overrun[i].len != 0) + { + if (offset + intercepts_overrun[i].len > location) + { + addr = intercepts_overrun[i].addr; + + // Write the value to the memory location. + // 16-bit and 32-bit values are written differently. + + if (addr != NULL) + { + if (intercepts_overrun[i].int16_array) + { + index = (location - offset) / 2; + ((short *) addr)[index] = value & 0xffff; + ((short *) addr)[index + 1] = (value >> 16) & 0xffff; + } + else + { + index = (location - offset) / 4; + ((int *) addr)[index] = value; + } + } + + break; + } + + offset += intercepts_overrun[i].len; + ++i; + } + } + */ + + /* + void InterceptsOverrun(int num_intercepts, intercept_t intercept) + { + if (num_intercepts > MAXINTERCEPTS_ORIGINAL && demo_compatibility && PROCESS(OVERFLOW_INTERCEPT)) + { + ShowOverflowWarning(OVERFLOW_INTERCEPT, false, ""); + + if (EMULATE(OVERFLOW_INTERCEPT)) + { + int location = (num_intercepts - MAXINTERCEPTS_ORIGINAL - 1) * 12; + + // Overwrite memory that is overwritten in Vanilla Doom, using + // the values from the intercept structure. + // + // Note: the .d.{thing,line} member should really have its + // address translated into the correct address value for + // Vanilla Doom. + + InterceptsMemoryOverrun(location, intercept.frac); + InterceptsMemoryOverrun(location + 4, intercept.isaline); + InterceptsMemoryOverrun(location + 8, (int) intercept.d.thing); + } + } + } + */ + // e6y + // playeringame overrun emulation + // it detects and emulates overflows on vex6d.wad\bug_wald(toke).lmp, etc. + // http://www.doom2.net/doom2/research/runningbody.zip + + /*static boolean PlayeringameOverrun(final mapthing_t mthing, DoomStatus DS) + { + if (mthing.type == 0 && PROCESS(OVERFLOW_PLYERINGAME)) + { + ShowOverflowWarning(OVERFLOW_PLYERINGAME, DS.players[4].didsecret, ""); + + if (EMULATE(OVERFLOW_PLYERINGAME)) + { + return true; + } + } + return false; + }*/ + // + // spechit overrun emulation + // + // Spechit overrun magic value. + public static final int DEFAULT_SPECHIT_MAGIC = 0x01C09C98; + + class spechit_overrun_param_t { + + line_t line; + + line_t[] spechit; + int numspechit; + + int[] tmbbox; + int[] tmfloorz; + int[] tmceilingz; + + boolean crushchange; + boolean nofit; + } + + long spechit_baseaddr = 0; + + // e6y + // Code to emulate the behavior of Vanilla Doom when encountering an overrun + // of the spechit array. + // No more desyncs on compet-n\hr.wad\hr18*.lmp, all strain.wad\map07 demos etc. + // http://www.doomworld.com/vb/showthread.php?s=&threadid=35214 + // See more information on: + // doomworld.com/vb/doom-speed-demos/35214-spechits-reject-and-intercepts-overflow-lists + /* + static void SpechitOverrun(spechit_overrun_param_t params) + { + int numspechit = params.numspechit; + + if (demo_compatibility && numspechit > 8) + { + line_t[] spechit = params.spechit; + + ShowOverflowWarning(OVERFLOW_SPECHIT, + numspechit > + (compatibility_level == dosdoom_compatibility || + compatibility_level == tasdoom_compatibility ? 10 : 14), + "\n\nThe list of LineID leading to overrun:\n%d, %d, %d, %d, %d, %d, %d, %d, %d.", + spechit[0].iLineID, spechit[1].iLineID, spechit[2].iLineID, + spechit[3].iLineID, spechit[4].iLineID, spechit[5].iLineID, + spechit[6].iLineID, spechit[7].iLineID, spechit[8].iLineID); + + if (EMULATE(OVERFLOW_SPECHIT)) + { + unsigned int addr; + + if (spechit_baseaddr == 0) + { + int p; + + // This is the first time we have had an overrun. Work out + // what base address we are going to use. + // Allow a spechit value to be specified on the command line. + + // + // Use the specified magic value when emulating spechit overruns. + // + + p = M_CheckParm("-spechit"); + + if (p > 0) + { + //baseaddr = atoi(myargv[p+1]); + M_StrToInt(myargv[p+1], (long*)&spechit_baseaddr); + } + else + { + spechit_baseaddr = DEFAULT_SPECHIT_MAGIC; + } + } + + // Calculate address used in doom2.exe + + addr = spechit_baseaddr + (params.line - lines) * 0x3E; + + if (compatibility_level == dosdoom_compatibility || compatibility_level == tasdoom_compatibility) + { + // There are no more desyncs in the following dosdoom demos: + // flsofdth.wad\fod3uv.lmp - http://www.doomworld.com/sda/flsofdth.htm + // hr.wad\hf181430.lmp - http://www.doomworld.com/tas/hf181430.zip + // hr.wad\hr181329.lmp - http://www.doomworld.com/tas/hr181329.zip + // icarus.wad\ic09uv.lmp - http://competn.doom2.net/pub/sda/i-o/icuvlmps.zip + + switch(numspechit) + { + case 9: + *(params.tmfloorz) = addr; + break; + case 10: + *(params.tmceilingz) = addr; + break; + + default: + fprintf(stderr, "SpechitOverrun: Warning: unable to emulate" + "an overrun where numspechit=%i\n", + numspechit); + break; + } + } + else + { + switch(numspechit) + { + case 9: + case 10: + case 11: + case 12: + params.tmbbox[numspechit-9] = addr; + break; + case 13: + *(params.nofit) = addr; + break; + case 14: + *(params.crushchange) = addr; + break; + + default: + lprintf(LO_ERROR, "SpechitOverrun: Warning: unable to emulate" + " an overrun where numspechit=%i\n", + numspechit); + break; + } + } + } + } + } + */ + // + // reject overrun emulation + // + // padding the reject table if it is too short + // totallines must be the number returned by P_GroupLines() + // an underflow will be padded with zeroes, or a doom.exe z_zone header + // + // e6y + // reject overrun emulation code + // It's emulated successfully if the size of overflow no more than 16 bytes. + // No more desync on teeth-32.wad\teeth-32.lmp. + // http://www.doomworld.com/vb/showthread.php?s=&threadid=35214 + + /*public static byte[] RejectOverrun(int rejectlump, final byte[] rejectmatrix, int totallines, int numsectors) + { + int required; + byte []newreject; + byte pad; + int length=rejectmatrix.length; + + required = (numsectors * numsectors + 7) / 8; + + if (length < required) + { + // allocate a new block and copy the reject table into it; zero the rest + // PU_LEVEL => will be freed on level exit + newreject = new byte[required]; + System.arraycopy(rejectmatrix, 0, newreject, 0, length); + + // e6y + // PrBoom 2.2.5 and 2.2.6 padded a short REJECT with 0xff + // This command line switch is needed for all potential demos + // recorded with these versions of PrBoom on maps with too short REJECT + // I don't think there are any demos that will need it but yes that seems sensible + // pad = prboom_comp[PC_REJECT_PAD_WITH_FF].state ? 0xff : 0; + // MAES: obviously we don't need that. + pad=0; + + Array.setByte(newreject,length,pad); + + // unlock the original lump, it is no longer needed + //W_UnlockLumpNum(rejectlump); + rejectlump = -1; + + /*if (demo_compatibility && PROCESS(OVERFLOW_REJECT)) + { + ShowOverflowWarning(OVERFLOW_REJECT, (required - length > 16) || (length%4 != 0), ""); + + if (EMULATE(OVERFLOW_REJECT)) + { + // merged in RejectOverrunAddInt(), and the 4 calls to it, here + unsigned int rejectpad[4] = { + 0, // size, will be filled in using totallines + 0, // part of the header of a doom.exe z_zone block + 50, // DOOM_CONST_PU_LEVEL + 0x1d4a11 // DOOM_CONST_ZONEID + }; + unsigned int i, pad = 0, *src = rejectpad; + byte *dest = newreject + length; + + rejectpad[0] = ((totallines*4+3)&~3)+24; // doom.exe zone header size + + // copy at most 16 bytes from rejectpad + // emulating a 32-bit, little-endian architecture (can't memmove) + for (i = 0; i < (unsigned int)(required - length) && i < 16; i++) { // 16 hard-coded + if (!(i&3)) // get the next 4 bytes to copy when i=0,4,8,12 + pad = *src++; + *dest++ = pad & 0xff; // store lowest-significant byte + pad >>= 8; // rotate the next byte down + } + } + } + + lprintf(LO_WARN, "P_LoadReject: REJECT too short (%u<%u) - padded\n", length, required); + } + }*/ + // + // Read Access Violation emulation. + // + // C:\>debug + // -d 0:0 + // + // DOS 6.22: + // 0000:0000 (57 92 19 00) F4 06 70 00-(16 00) + // DOS 7.1: + // 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) + // Win98: + // 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00) + // DOSBox under XP: + // 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00) + + /*#define DOS_MEM_DUMP_SIZE 10 + + unsigned char mem_dump_dos622[DOS_MEM_DUMP_SIZE] = { + 0x57, 0x92, 0x19, 0x00, 0xF4, 0x06, 0x70, 0x00, 0x16, 0x00}; + unsigned char mem_dump_win98[DOS_MEM_DUMP_SIZE] = { + 0x9E, 0x0F, 0xC9, 0x00, 0x65, 0x04, 0x70, 0x00, 0x16, 0x00}; + unsigned char mem_dump_dosbox[DOS_MEM_DUMP_SIZE] = { + 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00}; + + unsigned char *dos_mem_dump = mem_dump_dos622; + + static int GetMemoryValue(unsigned int offset, void *value, int size) + { + static int firsttime = true; + + if (firsttime) + { + int p, i, val; + + firsttime = false; + i = 0; + + if ((p = M_CheckParm("-setmem")) && (p < myargc-1)) + { + if (!strcasecmp(myargv[p + 1], "dos622")) + dos_mem_dump = mem_dump_dos622; + if (!strcasecmp(myargv[p + 1], "dos71")) + dos_mem_dump = mem_dump_win98; + else if (!strcasecmp(myargv[p + 1], "dosbox")) + dos_mem_dump = mem_dump_dosbox; + else + { + while (++p != myargc && *myargv[p] != '-' && i < DOS_MEM_DUMP_SIZE) + { + M_StrToInt(myargv[p], &val); + dos_mem_dump[i++] = (unsigned char)val; + } + } + } + } + + if (value) + { + switch (size) + { + case 1: + *((unsigned char*)value) = *((unsigned char*)(&dos_mem_dump[offset])); + return true; + case 2: + *((unsigned short*)value) = *((unsigned short*)(&dos_mem_dump[offset])); + return true; + case 4: + *((unsigned int*)value) = *((unsigned int*)(&dos_mem_dump[offset])); + return true; + } + } + + return false; + } + + // + // donut overrun emulation (linedef action #9) + // + + #define DONUT_FLOORPIC_DEFAULT 0x16 + int DonutOverrun(fixed_t *pfloorheight, short *pfloorpic) + { + if (demo_compatibility && PROCESS(OVERFLOW_DONUT)) + { + ShowOverflowWarning(OVERFLOW_DONUT, 0, ""); + + if (EMULATE(OVERFLOW_DONUT)) + { + if (pfloorheight && pfloorpic) + { + GetMemoryValue(0, pfloorheight, 4); + GetMemoryValue(8, pfloorpic, 2); + + // bounds-check floorpic + if ((*pfloorpic) <= 0 || (*pfloorpic) >= numflats) + { + *pfloorpic = MIN(numflats - 1, DONUT_FLOORPIC_DEFAULT); + } + + return true; + } + } + } + + return false; + } + + // + // MissedBackSideOverrun + // + int MissedBackSideOverrun(sector_t *sector, seg_t *seg) + { + if (demo_compatibility && PROCESS(OVERFLOW_MISSEDBACKSIDE)) + { + if (seg) + { + ShowOverflowWarning(OVERFLOW_MISSEDBACKSIDE, 0, + "\n\nLinedef %d has two-sided flag set, but no second sidedef", + seg.linedef.iLineID); + } + else + { + ShowOverflowWarning(OVERFLOW_MISSEDBACKSIDE, 0, ""); + } + + if (EMULATE(OVERFLOW_MISSEDBACKSIDE)) + { + if (sector) + { + GetMemoryValue(0, §or.floorheight, 4); + GetMemoryValue(4, §or.ceilingheight, 4); + + return true; + } + } + } + + return false; + }*/ +} \ No newline at end of file diff --git a/doom/src/g/Signals.java b/doom/src/g/Signals.java new file mode 100644 index 0000000..f90bd5e --- /dev/null +++ b/doom/src/g/Signals.java @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package g; + +// +import doom.event_t; +import doom.evtype_t; +import java.awt.event.KeyEvent; +import static java.awt.event.KeyEvent.*; + +public class Signals { + + // 65535 + 4 bytes in memory, acceptable for speed purpose + private final static byte[] map = new byte[Character.MAX_VALUE]; + // plus 260 bytes in this + private final static byte[] siblings = new byte[Byte.MAX_VALUE << 1]; + + public static ScanCode getScanCode(KeyEvent e) { + final ScanCode ret = ScanCode.v[map[e.getKeyCode()] & 0xFF]; + + if (ret.location == e.getKeyLocation()) { + return ret; + } + + // try sibling + final ScanCode sib = ScanCode.v[siblings[ret.ordinal()] & 0xFF]; + if (sib.location == e.getKeyLocation()) { + return sib; + } + + return ScanCode.SC_NULL; + } + + private Signals() { + } + + @FunctionalInterface + public interface SignalListener { + + void sendEvent(ScanCode sc, int eventType); + } + + /** + * Maps scan codes for whatever crap we use. They should be system dependent, but + * it seems I've invented a "keyboard" instead of passing it to real one. + * This one "keyboard" should be very like old DOS keyboards, and most of the key mappings + * will be compatible with those what can produce vanilla DOOM. But only most, not all. + * The order of these is important! Do not move. + * - Good Sign 2017/04/19 + */ + public enum ScanCode { + /* 0 */ SC_NULL, + /* 1 */ SC_ESCAPE(VK_ESCAPE), + /* 2 */ SC_1(VK_1), + /* 3 */ SC_2(VK_2), + /* 4 */ SC_3(VK_3), + /* 5 */ SC_4(VK_4), + /* 6 */ SC_5(VK_5), + /* 7 */ SC_6(VK_6), + /* 8 */ SC_7(VK_7), + /* 9 */ SC_8(VK_8), + /* 10 */ SC_9(VK_9), + /* 11 */ SC_0(VK_0), + /* 12 */ SC_MINUS(VK_MINUS), + /* 13 */ SC_EQUALS(VK_EQUALS), + /* 14 */ SC_BACKSPACE(VK_BACK_SPACE), + /* 15 */ SC_TAB(VK_TAB), + /* 16 */ SC_Q(VK_Q), + /* 17 */ SC_W(VK_W), + /* 18 */ SC_E(VK_E), + /* 19 */ SC_R(VK_R), + /* 20 */ SC_T(VK_T), + /* 21 */ SC_Y(VK_Y), + /* 22 */ SC_U(VK_U), + /* 23 */ SC_I(VK_I), + /* 24 */ SC_O(VK_O), + /* 25 */ SC_P(VK_P), + /* 26 */ SC_LBRACE(VK_OPEN_BRACKET), + /* 27 */ SC_RBRACE(VK_CLOSE_BRACKET), + /* 28 */ SC_ENTER(VK_ENTER), + /* 29 */ SC_LCTRL(VK_CONTROL, KEY_LOCATION_LEFT, CTRL_DOWN_MASK), + /* 30 */ SC_A(VK_A), + /* 31 */ SC_S(VK_S), + /* 32 */ SC_D(VK_D), + /* 33 */ SC_F(VK_F), + /* 34 */ SC_G(VK_G), + /* 35 */ SC_H(VK_H), + /* 36 */ SC_J(VK_J), + /* 37 */ SC_K(VK_K), + /* 38 */ SC_L(VK_L), + /* 39 */ SC_SEMICOLON(VK_SEMICOLON), + /* 40 */ SC_QUOTE(VK_QUOTE), + /* 41 */ SC_TILDE(VK_BACK_QUOTE), + /* 42 */ SC_LSHIFT(VK_SHIFT, KEY_LOCATION_LEFT, SHIFT_DOWN_MASK), + /* 43 */ SC_BACKSLASH(VK_BACK_SLASH), + /* 44 */ SC_Z(VK_Z), + /* 45 */ SC_X(VK_X), + /* 46 */ SC_C(VK_C), + /* 47 */ SC_V(VK_V), + /* 48 */ SC_B(VK_B), + /* 49 */ SC_N(VK_N), + /* 50 */ SC_M(VK_M), + /* 51 */ SC_COMMA(VK_COMMA), + /* 52 */ SC_PERIOD(VK_PERIOD), + /* 53 */ SC_SLASH(VK_SLASH), + /* 54 */ SC_RSHIFT(VK_SHIFT, KEY_LOCATION_RIGHT, SHIFT_DOWN_MASK), + /* 55 */ SC_NPMULTIPLY(VK_MULTIPLY, KEY_LOCATION_NUMPAD), + /* 56 */ SC_LALT(VK_ALT, KEY_LOCATION_LEFT, ALT_DOWN_MASK), + /* 57 */ SC_SPACE(VK_SPACE), + /* 58 */ SC_CAPSLK(VK_CAPS_LOCK), + /* 59 */ SC_F1(VK_F1), + /* 60 */ SC_F2(VK_F2), + /* 61 */ SC_F3(VK_F3), + /* 62 */ SC_F4(VK_F4), + /* 63 */ SC_F5(VK_F5), + /* 64 */ SC_F6(VK_F6), + /* 65 */ SC_F7(VK_F7), + /* 66 */ SC_F8(VK_F8), + /* 67 */ SC_F9(VK_F9), + /* 68 */ SC_F10(VK_F10), + /* 69 */ SC_NUMLK(VK_NUM_LOCK), + /* 70 */ SC_SCROLLLK(VK_SCROLL_LOCK), + /* 71 */ SC_NUMKEY7(VK_NUMPAD7, KEY_LOCATION_NUMPAD), + /* 72 */ SC_NUMKEY8(VK_NUMPAD8, KEY_LOCATION_NUMPAD), + /* 73 */ SC_NUMKEY9(VK_NUMPAD9, KEY_LOCATION_NUMPAD), + /* 74 */ SC_NPMINUS(VK_MINUS, KEY_LOCATION_NUMPAD), + /* 75 */ SC_NUMKEY4(VK_NUMPAD4, KEY_LOCATION_NUMPAD), + /* 76 */ SC_NUMKEY5(VK_NUMPAD5, KEY_LOCATION_NUMPAD), + /* 77 */ SC_NUMKEY6(VK_NUMPAD6, KEY_LOCATION_NUMPAD), + /* 78 */ SC_NPPLUS(VK_PLUS, KEY_LOCATION_NUMPAD), + /* 79 */ SC_NUMKEY1(VK_NUMPAD1, KEY_LOCATION_NUMPAD), + /* 80 */ SC_NUMKEY2(VK_NUMPAD2, KEY_LOCATION_NUMPAD), + /* 81 */ SC_NUMKEY3(VK_NUMPAD3, KEY_LOCATION_NUMPAD), + /* 82 */ SC_NUMKEY0(VK_NUMPAD0, KEY_LOCATION_NUMPAD), + /* 83 */ SC_NPDOT(VK_PERIOD, KEY_LOCATION_NUMPAD), + /* 84 + - 86 */ SC_54, SC_55, SC_56, + /* 87 */ SC_F11(VK_F11), + /* 88 */ SC_F12(VK_F12), + /** Codes higher are DOS-compatible. Codes below are mapped as in Linux **/ + /* 89 */ SC_ROMAN(VK_ROMAN_CHARACTERS), + /* 90 */ SC_KATAKANA(VK_KATAKANA), + /* 91 */ SC_HIRAGANA(VK_HIRAGANA), + /* 92 + - 95 */ SC_5C, SC_5D, SC_5E, SC_5F, + /* 96 */ SC_NPENTER(VK_ENTER, KEY_LOCATION_NUMPAD), + /* 97 */ SC_RCTRL(VK_CONTROL, KEY_LOCATION_RIGHT, CTRL_DOWN_MASK), + /* 98 */ SC_NPSLASH(VK_SLASH, KEY_LOCATION_NUMPAD), + /* 99 */ SC_PRTSCRN(VK_PRINTSCREEN), + /* 100 */ SC_RALT(VK_ALT, KEY_LOCATION_RIGHT, ALT_DOWN_MASK), + /* 101 */ SC_64, + /* 102 */ SC_HOME(VK_HOME), + /* 103 */ SC_UP(VK_UP), + /* 104 */ SC_PGUP(VK_PAGE_UP), + /* 105 */ SC_LEFT(VK_LEFT), + /* 106 */ SC_RIGHT(VK_RIGHT), + /* 107 */ SC_END(VK_END), + /* 108 */ SC_DOWN(VK_DOWN), + /* 109 */ SC_PGDOWN(VK_PAGE_DOWN), + /* 110 */ SC_INSERT(VK_INSERT), + /* 111 */ SC_DELETE(VK_DELETE), + /* 112 */ SC_MACRO(VK_DEAD_MACRON), + /* 113 + - 116 */ SC_71, SC_72, SC_73, SC_74, + /* 117 */ SC_NPEQUALS(VK_EQUALS, KEY_LOCATION_NUMPAD), + /* 118 */ SC_76, + /* 119 */ SC_PAUSE(VK_PAUSE), + /* 120 */ SC_78, + /* 121 */ SC_NPCOMMA(VK_COMMA, KEY_LOCATION_NUMPAD), + /* 122 + - 124 */ SC_7A, SC_7B, SC_7C, + /* 125 */ SC_LWIN(VK_WINDOWS, KEY_LOCATION_LEFT, META_DOWN_MASK), + /* 126 */ SC_RWIN(VK_WINDOWS, KEY_LOCATION_RIGHT, META_DOWN_MASK), + /* 127 */ SC_COMPOSE(VK_COMPOSE), + /* 128 */ SC_STOP(VK_STOP), + /* 129 */ SC_AGAIN(VK_AGAIN), + /* 130 */ SC_PROPS(VK_PROPS), + /* 131 */ SC_UNDO(VK_UNDO), + /* 132 */ SC_84, + /* 133 */ SC_COPY(VK_COPY), + /* 134 */ SC_86, + /* 135 */ SC_PASTE(VK_PASTE), + /* 136 */ SC_FIND(VK_FIND), + /* 137 */ SC_CUT(VK_CUT), + /* 138 */ SC_HELP(VK_HELP), + /* 139 */ SC_MENU(VK_CONTEXT_MENU), + /** Custom ScanCodes - no keyboard or platform standard **/ + /* 140 */ SC_LMETA(VK_META, KEY_LOCATION_LEFT, META_DOWN_MASK), + /* 141 */ SC_RMETA(VK_META, KEY_LOCATION_RIGHT, META_DOWN_MASK); + + public final char c; + public final event_t doomEventUp; + public final event_t doomEventDown; + + private final int location; + private final char virtualKey; + private final static ScanCode[] v = values(); + + ScanCode() { + this.doomEventUp = this.doomEventDown = event_t.EMPTY_EVENT; + this.location = this.c = this.virtualKey = 0; + } + + ScanCode(int virtualKey, int... properties) { + this.location = properties.length > 0 ? properties[0] : KEY_LOCATION_STANDARD; + this.virtualKey = (char) virtualKey; + this.doomEventUp = new event_t.keyevent_t(evtype_t.ev_keyup, this); + this.doomEventDown = new event_t.keyevent_t(evtype_t.ev_keydown, this); + this.c = Character.toLowerCase(this.virtualKey); + if (map[virtualKey] != 0) { + siblings[ordinal()] = map[virtualKey]; + } + map[virtualKey] = (byte) ordinal(); + } + } +} \ No newline at end of file diff --git a/doom/src/hu/HU.java b/doom/src/hu/HU.java new file mode 100644 index 0000000..a1c0369 --- /dev/null +++ b/doom/src/hu/HU.java @@ -0,0 +1,1341 @@ +package hu; + +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: HU.java,v 1.32 2012/09/24 17:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: Heads-up displays +// +// ----------------------------------------------------------------------------- +import static data.Defines.HU_BROADCAST; +import static data.Defines.HU_FONTSIZE; +import static data.Defines.HU_FONTSTART; +import static data.Defines.HU_MAXLINELENGTH; +import static data.Defines.HU_MAXLINES; +import static data.Defines.HU_MSGHEIGHT; +import static data.Defines.HU_MSGREFRESH; +import static data.Defines.HU_MSGTIMEOUT; +import static data.Defines.HU_MSGX; +import static data.Defines.HU_MSGY; +import static data.Defines.PU_STATIC; +import static data.Limits.MAXPLAYERS; +import data.sounds.sfxenum_t; +import defines.GameMode; +import defines.Language_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.HU_Lib; +import static doom.SourceCode.HU_Lib.HUlib_addCharToTextLine; +import static doom.SourceCode.HU_Lib.HUlib_clearTextLine; +import static doom.SourceCode.HU_Lib.HUlib_delCharFromIText; +import static doom.SourceCode.HU_Lib.HUlib_delCharFromTextLine; +import static doom.SourceCode.HU_Lib.HUlib_keyInIText; +import static doom.SourceCode.HU_Lib.HUlib_resetIText; +import doom.SourceCode.HU_Stuff; +import static doom.SourceCode.HU_Stuff.HU_Responder; +import static doom.SourceCode.HU_Stuff.HU_queueChatChar; +import static doom.englsh.*; +import doom.event_t; +import doom.evtype_t; +import doom.player_t; +import g.Signals.ScanCode; +import java.awt.Rectangle; +import java.util.Arrays; +import rr.ViewVars; +import rr.patch_t; +import utils.C2JUtils; +import static v.renderers.DoomScreen.BG; +import static v.renderers.DoomScreen.FG; + +public class HU implements IHeadsUp { + + // MAES: Status and wad data. + final DoomMain DOOM; + + // + // Locally used constants, shortcuts. + // MAES: Some depend on STATE, so moved into constructor. + String HU_TITLE, HU_TITLE2, HU_TITLEP, HU_TITLET; + + protected final static int HU_TITLEHEIGHT = 1; + + protected final static int HU_TITLEX = 0; + + protected int HU_TITLEY;// = (167 - Swap.SHORT(hu_font[0].height)); + + protected final static ScanCode HU_INPUTTOGGLE = ScanCode.SC_T; + + protected final static int HU_INPUTX = HU_MSGX; + + protected int HU_INPUTY;// = (HU_MSGY + + + // HU_MSGHEIGHT*(Swap.SHORT(hu_font[0].height) +1)); + protected final static int HU_INPUTWIDTH = 64; + + protected final static int HU_INPUTHEIGHT = 1; + + public String[] chat_macros + = {HUSTR_CHATMACRO0, HUSTR_CHATMACRO1, HUSTR_CHATMACRO2, + HUSTR_CHATMACRO3, HUSTR_CHATMACRO4, HUSTR_CHATMACRO5, + HUSTR_CHATMACRO6, HUSTR_CHATMACRO7, HUSTR_CHATMACRO8, + HUSTR_CHATMACRO9}; + + @Override + public void setChatMacro(int i, String s) { + this.chat_macros[i] = s; + } + + /** Needs to be seen by DoomGame */ + public final static String[] player_names + = {HUSTR_PLRGREEN, HUSTR_PLRINDIGO, HUSTR_PLRBROWN, HUSTR_PLRRED}; + + char chat_char; // remove later. + + player_t plr; + + // MAES: a whole lot of "static" stuff which really would be HU instance + // status. + patch_t[] hu_font = new patch_t[HU_FONTSIZE]; + + char[] chat_dest = new char[MAXPLAYERS]; + + // MAES: these used to be defined in hu_lib. We're going 100% OO here... + hu_itext_t[] w_inputbuffer; + + hu_textline_t w_title; + + hu_itext_t w_chat; + + boolean[] always_off = {false}; + + // Needs to be referenced by one of the widgets. + public boolean[] chat_on = new boolean[1]; + + // MAES: Ugly hack which allows it to be passed as reference. Sieg heil! + boolean[] message_on = new boolean[]{true}; + + boolean message_dontfuckwithme; + + boolean message_nottobefuckedwith; + + hu_stext_t w_message; + + int message_counter; + + // This is actually an "extern" pointing inside m_menu (Menu.java). So we + // need to share Menu context. + // int showMessages; + // MAES: I think this is supposed to be visible by the various hu_ crap... + // boolean automapactive; + boolean headsupactive = false; + + // + // Builtin map names. + // The actual names can be found in DStrings.h. + // + protected String[] mapnames + = // DOOM shareware/registered/retail (Ultimate) + // names. + { + HUSTR_E1M1, HUSTR_E1M2, HUSTR_E1M3, HUSTR_E1M4, HUSTR_E1M5, HUSTR_E1M6, + HUSTR_E1M7, HUSTR_E1M8, HUSTR_E1M9, + HUSTR_E2M1, HUSTR_E2M2, HUSTR_E2M3, HUSTR_E2M4, HUSTR_E2M5, + HUSTR_E2M6, HUSTR_E2M7, HUSTR_E2M8, HUSTR_E2M9, + HUSTR_E3M1, HUSTR_E3M2, HUSTR_E3M3, HUSTR_E3M4, HUSTR_E3M5, + HUSTR_E3M6, HUSTR_E3M7, HUSTR_E3M8, HUSTR_E3M9, + HUSTR_E4M1, HUSTR_E4M2, HUSTR_E4M3, HUSTR_E4M4, HUSTR_E4M5, + HUSTR_E4M6, HUSTR_E4M7, HUSTR_E4M8, HUSTR_E4M9, + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", + "NEWLEVEL", "NEWLEVEL", "NEWLEVEL", "NEWLEVEL"}; + + protected String[] mapnames2 + = // DOOM 2 map names. + {HUSTR_1, HUSTR_2, HUSTR_3, HUSTR_4, HUSTR_5, HUSTR_6, HUSTR_7, + HUSTR_8, HUSTR_9, HUSTR_10, HUSTR_11, + HUSTR_12, HUSTR_13, HUSTR_14, HUSTR_15, HUSTR_16, HUSTR_17, + HUSTR_18, HUSTR_19, HUSTR_20, + HUSTR_21, HUSTR_22, HUSTR_23, HUSTR_24, HUSTR_25, HUSTR_26, + HUSTR_27, HUSTR_28, HUSTR_29, HUSTR_30, HUSTR_31, HUSTR_32, HUSTR_33}; + + protected String[] mapnamesp + = // Plutonia WAD map names. + {PHUSTR_1, PHUSTR_2, PHUSTR_3, PHUSTR_4, PHUSTR_5, PHUSTR_6, PHUSTR_7, + PHUSTR_8, PHUSTR_9, PHUSTR_10, PHUSTR_11, + PHUSTR_12, PHUSTR_13, PHUSTR_14, PHUSTR_15, PHUSTR_16, + PHUSTR_17, PHUSTR_18, PHUSTR_19, PHUSTR_20, + PHUSTR_21, PHUSTR_22, PHUSTR_23, PHUSTR_24, PHUSTR_25, + PHUSTR_26, PHUSTR_27, PHUSTR_28, PHUSTR_29, PHUSTR_30, + PHUSTR_31, PHUSTR_32}; + + protected String[] mapnamest + = // TNT WAD map names. + {THUSTR_1, THUSTR_2, THUSTR_3, THUSTR_4, THUSTR_5, THUSTR_6, THUSTR_7, + THUSTR_8, THUSTR_9, THUSTR_10, THUSTR_11, + THUSTR_12, THUSTR_13, THUSTR_14, THUSTR_15, THUSTR_16, + THUSTR_17, THUSTR_18, THUSTR_19, THUSTR_20, + THUSTR_21, THUSTR_22, THUSTR_23, THUSTR_24, THUSTR_25, + THUSTR_26, THUSTR_27, THUSTR_28, THUSTR_29, THUSTR_30, + THUSTR_31, THUSTR_32}; + + char[] shiftxform; + + public static final char[] french_shiftxform + = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + ' ', + '!', + '"', + '#', + '$', + '%', + '&', + '"', // shift-' + '(', + ')', + '*', + '+', + '?', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + '0', // shift-0 + '1', // shift-1 + '2', // shift-2 + '3', // shift-3 + '4', // shift-4 + '5', // shift-5 + '6', // shift-6 + '7', // shift-7 + '8', // shift-8 + '9', // shift-9 + '/', + '.', // shift-; + '<', + '+', // shift-= + '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', + 'X', + 'Y', + 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', + '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '{', '|', '}', '~', 127 + + }; + + public static final char[] english_shiftxform + = { + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + ' ', + '!', + '"', + '#', + '$', + '%', + '&', + '"', // shift-' + '(', + ')', + '*', + '+', + '<', // shift-, + '_', // shift-- + '>', // shift-. + '?', // shift-/ + ')', // shift-0 + '!', // shift-1 + '@', // shift-2 + '#', // shift-3 + '$', // shift-4 + '%', // shift-5 + '^', // shift-6 + '&', // shift-7 + '*', // shift-8 + '(', // shift-9 + ':', + ':', // shift-; + '<', + '+', // shift-= + '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', + 'X', + 'Y', + 'Z', + '[', // shift-[ + '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK + ']', // shift-] + '"', + '_', + '\'', // shift-` + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '{', '|', '}', '~', 127}; + + // Maes: char? + char[] frenchKeyMap + = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ' ', '!', '"', + '#', '$', '%', '&', '%', '(', ')', '*', '+', ';', '-', ':', + '!', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', + 'M', '<', '=', '>', '?', '@', 'Q', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', ',', 'N', 'O', 'P', 'A', 'R', + 'S', 'T', 'U', 'V', 'Z', 'X', 'Y', 'W', '^', '\\', '$', '^', + '_', '@', 'Q', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', ',', 'N', 'O', 'P', 'A', 'R', 'S', 'T', 'U', 'V', + 'Z', 'X', 'Y', 'W', '^', '\\', '$', '^', 127}; + + protected final char ForeignTranslation(char ch) { + return ch < 128 ? frenchKeyMap[ch] : ch; + } + + public HU(final DoomMain DOOM) { + this.DOOM = DOOM; + this.w_message = new hu_stext_t(); + + this.w_inputbuffer = new hu_itext_t[MAXPLAYERS]; + for (int i = 0; i < MAXPLAYERS; i++) { + this.w_inputbuffer[i] = new hu_itext_t(); + } + this.w_title = new hu_textline_t(); + this.w_chat = new hu_itext_t(); + } + + /** + * Loads a bunch of STCFNx fonts from WAD, and sets some of the remaining + * constants. + * + * @throws Exception + */ + @Override + public void Init() { + if (DOOM.language == Language_t.french) { + shiftxform = french_shiftxform; + } else { + shiftxform = english_shiftxform; + } + + // load the heads-up font + int j = HU_FONTSTART; + + // So it basically loads a bunch of patch_t's from memory. + Arrays.setAll(hu_font, i -> new patch_t()); + + for (int i = 0; i < HU_FONTSIZE; i++) { + final String buffer = String.format("STCFN%03d", j++); + // hu_font[i] = ((patch_t[]) wd.CacheLumpName(buffer, PU_STATIC); + hu_font[i] = (DOOM.wadLoader.CachePatchName(buffer, PU_STATIC)); + } + + // MAES: Doom's SC had a really fucked up endianness change for height. + // I don't really see the point in that, as in the WAD patches appear + // to be all Little Endian... mystery :-S + // HU_TITLEY = (167 - Swap.SHORT(hu_font[0].height)); + HU_TITLEY = (167 - hu_font[0].height); + HU_INPUTY = (HU_MSGY + HU_MSGHEIGHT * hu_font[0].height + 1); + + } + + @Override + public void Stop() { + headsupactive = false; + } + + @Override + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + public void Start() { + + int i; + String s; + + // MAES: fugly hax. These were compile-time inlines, + // so they can either work as functions, or be set whenever the HU is started + // (typically once per level). They need to be aware of game progress, + // and episode numbers <1 will cause it to bomb. + // MAES: hack to handle Betray in XBLA 31/5/2011 + if ((DOOM.gamemap > 32) && (DOOM.getGameMode() == GameMode.pack_xbla)) { + this.HU_TITLE = mapnames[(DOOM.gameepisode - 1) * 9 + DOOM.gamemap - 2]; + + this.HU_TITLE2 = mapnames2[DOOM.gamemap - 1]; + this.HU_TITLEP = mapnamesp[DOOM.gamemap - 2]; // fixed from HU_TITLEPw + this.HU_TITLET = mapnamest[DOOM.gamemap - 2]; + } else { + this.HU_TITLE = mapnames[(DOOM.gameepisode - 1) * 9 + DOOM.gamemap - 1]; + this.HU_TITLE2 = mapnames2[DOOM.gamemap - 1]; + this.HU_TITLEP = mapnamesp[DOOM.gamemap - 1]; // fixed from HU_TITLEP + this.HU_TITLET = mapnamest[DOOM.gamemap - 1]; + } + + if (headsupactive) { + this.Stop(); + } + + plr = DOOM.players[DOOM.consoleplayer]; + message_on[0] = false; + message_dontfuckwithme = false; + message_nottobefuckedwith = false; + chat_on[0] = false; + + // create the message widget + this.w_message.initSText(HU_MSGX, HU_MSGY, HU_MSGHEIGHT, hu_font, + HU_FONTSTART, this.message_on); + + // create the map title widget + this.w_title.initTextLine(HU_TITLEX, HU_TITLEY, hu_font, HU_FONTSTART); + + switch (DOOM.getGameMode()) { + case shareware: + case registered: + case retail: + case freedoom1: + s = HU_TITLE; + break; + + case pack_plut: + s = HU_TITLEP; + break; + case pack_tnt: + s = HU_TITLET; + break; + + case commercial: + case freedoom2: + case freedm: + default: + s = HU_TITLE2; + break; + } + + // MAES: oh great, more pointer-char magic... oh no you don't, you ugly + // cow horse and reindeer lover. + // while (*s) this.w_title.addCharToTextLine(*(s++)); + int ptr = 0; + while (ptr < s.length()) { + this.w_title.addCharToTextLine(s.charAt(ptr++)); + } + // create the chat widget + this.w_chat.initIText(HU_INPUTX, HU_INPUTY, hu_font, HU_FONTSTART, + chat_on); + + // create the inputbuffer widgets + for (i = 0; i < MAXPLAYERS; i++) { + w_inputbuffer[i] = new hu_itext_t(); + w_inputbuffer[i].initIText(0, 0, null, 0, always_off); + } + headsupactive = true; + + } + + @Override + public void Drawer() { + this.w_message.drawSText(); + this.w_chat.drawIText(); + if (DOOM.automapactive) { + this.w_title.drawTextLine(false); + } + } + + @Override + public void Erase() { + this.w_message.eraseSText(); + this.w_chat.eraseIText(); + this.w_title.eraseTextLine(); + } + + @Override + public void Ticker() { + + int i; + boolean rc; + char c; + + // tick down message counter if message is up + if ((message_counter != 0) && !((--message_counter) != 0)) { + message_on[0] = false; + message_nottobefuckedwith = false; + } + + if (DOOM.menu.getShowMessages() || message_dontfuckwithme) { + + // display message if necessary + if (((plr.message != null) && !message_nottobefuckedwith) + || ((plr.message != null) && message_dontfuckwithme)) { + this.w_message.addMessageToSText(null, plr.message); + plr.message = null; + message_on[0] = true; + message_counter = HU_MSGTIMEOUT; + message_nottobefuckedwith = message_dontfuckwithme; + message_dontfuckwithme = false; + } + + } // else message_on = false; + + // check for incoming chat characters + if (DOOM.netgame) { + for (i = 0; i < MAXPLAYERS; i++) { + if (!DOOM.playeringame[i]) { + continue; + } + if ((i != DOOM.consoleplayer) + && ((c = DOOM.players[i].cmd.chatchar) != 0)) { + if (c <= HU_BROADCAST) { + chat_dest[i] = c; + } else { + if (c >= 'a' && c <= 'z') { + c = (char) shiftxform[c]; + } + rc = w_inputbuffer[i].keyInIText(c); + if (rc && c == ScanCode.SC_ENTER.c) { + if ((w_inputbuffer[i].l.len != 0) + && (chat_dest[i] == DOOM.consoleplayer + 1) + || (chat_dest[i] == HU_BROADCAST)) { + w_message.addMessageToSText(player_names[i], w_inputbuffer[i].l.text.toString()); + + message_nottobefuckedwith = true; + message_on[0] = true; + message_counter = HU_MSGTIMEOUT; + if (DOOM.isCommercial()) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_radio); + } else { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_tink); + } + + } + w_inputbuffer[i].resetIText(); + } + } + DOOM.players[i].cmd.chatchar = 0; + } + } + } + + } + + protected final int QUEUESIZE = 128; + + protected char[] chatchars = new char[QUEUESIZE]; + + protected int head = 0; + + protected int tail = 0; + + @SourceCode.Exact + @HU_Stuff.C(HU_queueChatChar) + protected void queueChatChar(char c) { + if (((head + 1) & (QUEUESIZE - 1)) == tail) { + plr.message = HUSTR_MSGU; + } else { + chatchars[head] = c; + head = (head + 1) & (QUEUESIZE - 1); + } + } + + @Override + public char dequeueChatChar() { + char c; + + if (head != tail) { + c = chatchars[tail]; + tail = (tail + 1) & (QUEUESIZE - 1); + } else { + c = 0; + } + + return c; + } + + // MAES: These were "static" inside HU_Responder, since they were meant to + // represent state. + protected StringBuilder lastmessage = new StringBuilder(HU_MAXLINELENGTH + 1); + + // protected char[] lastmessage=new char[HU_MAXLINELENGTH+1]; + protected boolean shiftdown = false; + + protected boolean altdown = false; + + protected char[] destination_keys = {HUSTR_KEYGREEN, HUSTR_KEYINDIGO, HUSTR_KEYBROWN, HUSTR_KEYRED}; + + protected int num_nobrainers = 0; + + @Override + @SourceCode.Compatible + @HU_Stuff.C(HU_Responder) + public boolean Responder(event_t ev) { + + //System.out.println("Player "+DM.players[0].mo.x); + int numplayers = 0; + // MAES: Adding BOOLEANS to ints, are we ?! + for (int i = 0; i < MAXPLAYERS; i++) { + numplayers += (DOOM.playeringame[i]) ? 1 : 0; + } + + if (ev.isKey(ScanCode.SC_LSHIFT) || ev.isKey(ScanCode.SC_RSHIFT)) { + shiftdown = ev.isType(evtype_t.ev_keydown); + return false; + } else if (ev.isKey(ScanCode.SC_LALT) || ev.isKey(ScanCode.SC_RALT)) { + altdown = ev.isType(evtype_t.ev_keydown); + return false; + } + + if (!ev.isType(evtype_t.ev_keydown)) { + return false; + } + + final boolean eatkey; + if (!chat_on[0]) { + if (ev.isKey(HU_MSGREFRESH)) { + message_on[0] = true; + message_counter = HU_MSGTIMEOUT; + eatkey = true; + } else if (DOOM.netgame && ev.isKey(HU_INPUTTOGGLE)) { + eatkey = chat_on[0] = true; + HUlib_resetIText: + { + w_chat.resetIText(); + } + HU_queueChatChar: + { + this.queueChatChar(HU_BROADCAST); + } + } else if (DOOM.netgame && numplayers > 2) { + eatkey = ev.ifKey(sc -> { + boolean r = false; + for (int i = 0; i < MAXPLAYERS; i++) { + if (sc.c == destination_keys[i]) { + if (DOOM.playeringame[i] && i != DOOM.consoleplayer) { + r = chat_on[0] = true; + HUlib_resetIText: + { + w_chat.resetIText(); + } + HU_queueChatChar: + { + this.queueChatChar((char) (i + 1)); + } + break; + } else if (i == DOOM.consoleplayer) { + num_nobrainers++; + if (num_nobrainers < 3) { + plr.message = HUSTR_TALKTOSELF1; + } else if (num_nobrainers < 6) { + plr.message = HUSTR_TALKTOSELF2; + } else if (num_nobrainers < 9) { + plr.message = HUSTR_TALKTOSELF3; + } else if (num_nobrainers < 32) { + plr.message = HUSTR_TALKTOSELF4; + } else { + plr.message = HUSTR_TALKTOSELF5; + } + } + } + } + return r; + }); + } else { + eatkey = false; + } + } else { + eatkey = ev.ifKey(sc -> { + final boolean ret; + char c = sc.c; + // send a macro + if (altdown) { + c = (char) (c - '0'); + if (c > 9) { + return false; + } + + // fprintf(stderr, "got here\n"); + char[] macromessage = chat_macros[c].toCharArray(); + + // kill last message with a '\n' + HU_queueChatChar: + { + this.queueChatChar(ScanCode.SC_ENTER.c); + } // DEBUG!!! + + // send the macro message + int index = 0; + while (macromessage[index] != 0) { + HU_queueChatChar: + { + this.queueChatChar(macromessage[index]); + } + } + HU_queueChatChar: + { + this.queueChatChar(ScanCode.SC_ENTER.c); + } + + // leave chat mode and notify that it was sent + chat_on[0] = false; + lastmessage.setLength(0); + lastmessage.append(chat_macros[c]); + plr.message = lastmessage.toString(); + ret = true; + } else { + if (DOOM.language == Language_t.french) { + c = ForeignTranslation(c); + } + if (shiftdown || (c >= 'a' && c <= 'z')) { + c = shiftxform[c]; + } + HUlib_keyInIText: + { + ret = w_chat.keyInIText(c); + } + if (ret) { + // static unsigned char buf[20]; // DEBUG + HU_queueChatChar: + { + this.queueChatChar(c); + } + + // sprintf(buf, "KEY: %d => %d", ev->data1, c); + // plr->message = buf; + } + if (c == ScanCode.SC_ENTER.c) { + chat_on[0] = false; + if ((w_chat.l.len != 0)) { + lastmessage.setLength(0); + lastmessage.append(w_chat.l.text); + plr.message = new String(lastmessage); + } + } else if (c == ScanCode.SC_ESCAPE.c) { + chat_on[0] = false; + } + } + return ret; + }); + } + + return eatkey; + } + + // ///////////////////////////////// STRUCTS + // /////////////////////////////////// + /** + * Input Text Line widget + * (child of Text Line widget) + */ + class hu_itext_t { + + hu_textline_t l; // text line to input on + + // left margin past which I am not to delete characters + int lm; + + // pointer to boolean stating whether to update window + boolean[] on; + + boolean laston; // last value of *->on; + + public hu_itext_t() { + + } + + public void initIText(int x, int y, patch_t[] font, int startchar, + boolean[] on) { + this.lm = 0; // default left margin is start of text + this.on = on; + this.laston = true; + l = new hu_textline_t(x, y, font, startchar); + } + + // The following deletion routines adhere to the left margin restriction + @SourceCode.Exact + @HU_Lib.C(HUlib_delCharFromIText) + public void delCharFromIText() { + if (this.l.len != this.lm) { + HUlib_delCharFromTextLine: + { + this.l.delCharFromTextLine(); + } + } + } + + public void eraseLineFromIText() { + while (this.lm != this.l.len) { + l.delCharFromTextLine(); + } + } + + // Resets left margin as well + @SourceCode.Exact + @HU_Lib.C(HUlib_resetIText) + public void resetIText() { + this.lm = 0; + this.l.clearTextLine(); + } + + public void addPrefixToIText(char[] str) { + int ptr = 0; + while (str[ptr] > 0) { + l.addCharToTextLine(str[ptr++]); + this.lm = this.l.len; + } + } + + // Maes: String overload + public void addPrefixToIText(String str) { + int ptr = 0; + while (str.charAt(ptr) > 0) { + l.addCharToTextLine(str.charAt(ptr++)); + this.lm = this.l.len; + } + } + + // wrapper function for handling general keyed input. + // returns true if it ate the key + @SourceCode.Exact + @HU_Lib.C(HUlib_keyInIText) + public boolean keyInIText(char ch) { + + if (ch >= ' ' && ch <= '_') { + HUlib_addCharToTextLine: + { + this.l.addCharToTextLine(ch); + } + } else if (ch == ScanCode.SC_BACKSPACE.c) { + HUlib_delCharFromIText: + { + this.delCharFromIText(); + } + } else if (ch != ScanCode.SC_ENTER.c) { + return false; // did not eat key + } + return true; // ate the key + + } + + public void drawIText() { + + if (!this.on[0]) { + return; + } + this.l.drawTextLine(true); // draw the line w/ cursor + + } + + void eraseIText() { + if (this.laston && !this.on[0]) { + this.l.needsupdate = 4; + } + this.l.eraseTextLine(); + this.laston = this.on[0]; + } + + } + + /** Scrolling Text window widget + * (child of Text Line widget) + */ + class hu_stext_t { + + hu_textline_t[] lines = new hu_textline_t[HU_MAXLINES]; // text lines to draw + + int height; // height in lines + + int currline; // current line number + + // pointer to boolean stating whether to update window + boolean[] on; + + boolean laston; // last value of *->on. + + public hu_stext_t() { + + } + + public hu_stext_t(int x, int y, int h, patch_t[] font, int startchar, + boolean[] on) { + this.initSText(x, y, h, font, startchar, on); + } + + public void initSText(int x, int y, int h, patch_t[] font, + int startchar, boolean[] on) { + + for (int i = 0; i < HU_MAXLINES; i++) { + this.lines[i] = new hu_textline_t(); + } + this.height = h; + this.on = on; + this.laston = true; + this.currline = 0; + for (int i = 0; i < h; i++) { + this.lines[i].initTextLine(x, y - i + * (font[0].height + 1), font, startchar); + } + + } + + public void addLineToSText() { + + // add a clear line + if (++this.currline == this.height) { + this.currline = 0; + } + this.lines[this.currline].clearTextLine(); + + // everything needs updating + for (int i = 0; i < this.height; i++) { + this.lines[i].needsupdate = 4; + } + + } + + public void addMessageToSText(char[] prefix, char[] msg) { + this.addLineToSText(); + int ptr = 0; + if ((prefix != null) && (prefix.length > 0)) { + + while ((ptr < prefix.length) && (prefix[ptr] > 0)) { + this.lines[this.currline].addCharToTextLine(prefix[ptr++]); + } + } + + ptr = 0; + while ((ptr < msg.length) && (msg[ptr] > 0)) { + this.lines[this.currline].addCharToTextLine(msg[ptr++]); + } + } + + public void addMessageToSText(String prefix, String msg) { + this.addLineToSText(); + if ((prefix != null) && (prefix.length() > 0)) { + for (int i = 0; i < prefix.length(); i++) { + this.lines[this.currline].addCharToTextLine(prefix.charAt(i)); + } + } + for (int i = 0; i < msg.length(); i++) { + this.lines[this.currline].addCharToTextLine(msg.charAt(i)); + } + } + + public void drawSText() { + int i, idx; + hu_textline_t l; + + if (!this.on[0]) { + return; // if not on, don't draw + } + + // draw everything + for (i = 0; i < this.height; i++) { + idx = this.currline - i; + if (idx < 0) { + idx += this.height; // handle queue of lines + } + l = this.lines[idx]; + + // need a decision made here on whether to skip the draw + l.drawTextLine(false); // no cursor, please + } + + } + + public void eraseSText() { + for (int i = 0; i < this.height; i++) { + if (laston && !on[0]) { + lines[i].needsupdate = 4; + } + this.lines[i].eraseTextLine(); + } + laston = on[0]; + + } + + /** + * MAES: this was the only variable in HUlib.c, and only instances of + * hu_textline_t ever use it. For this reason, it makes sense to have it + * common (?) between all instances of hu_textline_t and set it + * somewhere else. Of course, if could be made an instance method or a + * HUlib object could be defined. + */ + protected boolean automapactive; // in AM_map.c + + public boolean isAutomapactive() { + return automapactive; + } + + public void setAutomapactive(boolean automapactive) { + this.automapactive = automapactive; + } + + /** + * Same here. + */ + // TODO: boolean : whether the screen is always erased + protected boolean noterased; // =viewwindowx; + + public boolean isNoterased() { + return noterased; + } + + public void setNoterased(boolean noterased) { + this.noterased = noterased; + } + + StringBuilder sb = new StringBuilder(); + + public String toString() { + sb.setLength(0); + sb.append(this.lines[0].text); + sb.append(this.lines[1].text); + sb.append(this.lines[2].text); + sb.append(this.lines[3].text); + return sb.toString(); + } + + } + + // Text Line widget + // (parent of Scrolling Text and Input Text widgets) + class hu_textline_t { + + // left-justified position of scrolling text window + int x; + + int y; + + // MAES: was ** + patch_t[] f; // font + + int sc; // start character + + char[] text = new char[HU_MAXLINELENGTH + 1]; // line of text + + int len; // current line length + + // whether this line needs to be udpated + int needsupdate; + + public hu_textline_t() { + + } + + @SourceCode.Compatible + @HU_Lib.C(HUlib_clearTextLine) + public void clearTextLine() { + this.len = 0; + C2JUtils.memset(this.text, (char) 0, this.text.length); + // It's actually used as a status, go figure. + this.needsupdate = 1; + } + + // Maes: this could as well be the contructor + public void initTextLine(int x, int y, patch_t[] f, int sc) { + this.x = x; + this.y = y; + this.f = f; + this.sc = sc; + this.clearTextLine(); + } + + public hu_textline_t(int x, int y, patch_t[] f, int sc) { + this.x = x; + this.y = y; + this.f = f; + this.sc = sc; + this.clearTextLine(); + } + + @SourceCode.Exact + @HU_Lib.C(HUlib_addCharToTextLine) + public boolean addCharToTextLine(char ch) { + + if (len == HU_MAXLINELENGTH) { + return false; + } else { + this.text[len++] = ch; + this.text[len] = (char) 0; + // this.l[this.len] = 0; + // MAES: for some reason this is set as "4", so this is a status + // rather than a boolean. + this.needsupdate = 4; + return true; + } + + } + + /** + * MAES: This is much better than cluttering up the syntax everytime a + * STRING must be added. + * + * @param s + * @return + */ + /* + public boolean addStringToTextLine(String s) { + int index = 0; + if (this.len == HU_MAXLINELENGTH) + return false; + else + while ((index= this.sc && c <= '_') { + // MAES: fixed a FUCKING STUPID bug caused by SWAP.SHORT + w = this.f[c - this.sc].width; + if (x + w > DOOM.vs.getScreenWidth()) { + break; + } + + DOOM.graphicSystem.DrawPatchScaled(FG, f[c - sc], DOOM.vs, x, y); + x += w; + } else { + // Leave a space + x += 4; + if (x >= DOOM.vs.getScreenWidth()) { + break; + } + } + } + + // draw the cursor if requested + if (drawcursor + && x + this.f['_' - this.sc].width <= DOOM.vs.getScreenWidth()) { + DOOM.graphicSystem.DrawPatchScaled(FG, this.f['_' - this.sc], DOOM.vs, x, this.y); + } + } + + // MAES: was "static" in C within HUlib. Which may mean it's instance + // specific or global-ish. Or both. + protected boolean lastautomapactive = true; + + /** + * Erases as little as possible to remove text messages + * Only erases when NOT in automap and the screen is reduced, + * and the text must either need updating or refreshing + * (because of a recent change back from the automap) + * + * Rewritten by Good Sign 2017/04/06 + */ + @SuppressWarnings("unchecked") + public void eraseTextLine() { + if (!DOOM.automapactive && DOOM.sceneRenderer.getView().windowx != 0 && this.needsupdate > 0) { + final ViewVars active = DOOM.sceneRenderer.getView(); + final int // active part of the screen + activeEndX = active.x + active.width, + activeEndY = active.y + active.height, + // scaled text ranges + dupY = DOOM.graphicSystem.getScalingY(), + lineY = y * dupY, + lineHeight = (this.f[0].height + 1) * dupY, + lineEndY = lineY + lineHeight; + + final Rectangle rect = new Rectangle(0, lineY, DOOM.vs.getScreenWidth(), lineHeight); + + // TOP FULL WIDTH + if (lineY < active.y) { + if (lineEndY >= active.y) { + rect.height = active.y - lineY; + } + DOOM.graphicSystem.CopyRect(BG, rect, FG); + } + // CENTER SIDES + if ((lineEndY >= active.y && lineEndY < activeEndY) || (lineY >= active.y && lineY < activeEndY)) { + if (lineY < active.y) { + rect.y = active.y; + rect.height = lineHeight - rect.height; // = lineHeight - (active.y - lineY); + } else { + rect.y = lineY; + if (lineEndY >= activeEndY) { + rect.height = activeEndY - lineY; + } else { + rect.height = lineHeight; + } + } + // LEFT + rect.width = active.x; + DOOM.graphicSystem.CopyRect(BG, rect, FG); + // RIGHT + rect.width = DOOM.vs.getScreenWidth() - activeEndX; + DOOM.graphicSystem.CopyRect(BG, rect, FG); + rect.width = DOOM.vs.getScreenWidth(); // restore, don't need to bother later + } + // BOTTOM FULL WIDTH + if (lineEndY >= activeEndY) { + if (lineY >= activeEndY) { + rect.y = lineY; + } else { + rect.y = activeEndY; + rect.height = lineHeight - rect.height; // = lineHeight - (activeEndY - lineY); + } + DOOM.graphicSystem.CopyRect(BG, rect, FG); + } + } + + lastautomapactive = DOOM.automapactive; + if (this.needsupdate != 0) { + this.needsupdate--; + } + } + } + + @Override + public patch_t[] getHUFonts() { + return this.hu_font; + } +} + +//$Log: HU.java,v $ +//Revision 1.32 2012/09/24 17:16:23 velktron +//Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +//Revision 1.31.2.2 2012/09/24 16:57:43 velktron +//Addressed generics warnings. +// +//Revision 1.31.2.1 2012/09/19 17:43:06 velktron +//Aware of new ViewVars structure. +// +//Revision 1.31 2011/11/01 22:17:46 velktron +//Cleaned up a bit, implements IHeadsUp +// +//Revision 1.30 2011/10/23 18:11:58 velktron +//Generic compliance for DoomVideoInterface +// +//Revision 1.29 2011/10/07 16:05:22 velktron +//Now using g.Keys for key input stuff. +// +//Revision 1.28 2011/05/31 23:46:18 velktron +//Fixed scaling. +// +//Revision 1.27 2011/05/31 21:42:30 velktron +//Handling for map33 +// +//Revision 1.26 2011/05/24 17:45:08 velktron +//IHeadsUp interface, setChatMacro method. +// +//Revision 1.25 2011/05/23 16:56:44 velktron +//Migrated to VideoScaleInfo. +// +//Revision 1.24 2011/05/21 14:42:32 velktron +//Adapted to use new gamemode system. +// +//Revision 1.23 2011/05/20 18:27:12 velktron +//DoomMenu -> IDoomMenu +// +//Revision 1.22 2011/05/20 18:24:19 velktron +//FINALLY fixed a stupid bug that broke HU messages. +// +//Revision 1.21 2011/05/18 16:52:40 velktron +//Changed to DoomStatus \ No newline at end of file diff --git a/doom/src/hu/IHeadsUp.java b/doom/src/hu/IHeadsUp.java new file mode 100644 index 0000000..5667b6b --- /dev/null +++ b/doom/src/hu/IHeadsUp.java @@ -0,0 +1,31 @@ +package hu; + +import doom.SourceCode.HU_Stuff; +import static doom.SourceCode.HU_Stuff.HU_Responder; +import doom.event_t; +import rr.patch_t; + +public interface IHeadsUp { + + void Ticker(); + + void Erase(); + + void Drawer(); + + @HU_Stuff.C(HU_Responder) + boolean Responder(event_t ev); + + patch_t[] getHUFonts(); + + char dequeueChatChar(); + + void Init(); + + void setChatMacro(int i, String s); + + void Start(); + + void Stop(); + +} \ No newline at end of file diff --git a/doom/src/i/DiskDrawer.java b/doom/src/i/DiskDrawer.java new file mode 100644 index 0000000..eb4129d --- /dev/null +++ b/doom/src/i/DiskDrawer.java @@ -0,0 +1,54 @@ +package i; + +import doom.DoomMain; +import rr.patch_t; +import static v.renderers.DoomScreen.FG; + +public class DiskDrawer implements IDiskDrawer { + + private DoomMain DOOM; + private patch_t disk; + private int timer = 0; + private String diskname; + + public static final String STDISK = "STDISK"; + public static final String STCDROM = "STCDROM"; + + public DiskDrawer(DoomMain DOOM, String icon) { + this.DOOM = DOOM; + this.diskname = icon; + } + + @Override + public void Init() { + this.disk = DOOM.wadLoader.CachePatchName(diskname); + } + + @Override + public void Drawer() { + if (timer > 0) { + if (timer % 2 == 0) { + DOOM.graphicSystem.DrawPatchScaled(FG, disk, DOOM.vs, 304, 184); + } + } + if (timer >= 0) { + timer--; + } + } + + @Override + public void setReading(int reading) { + timer = reading; + } + + @Override + public boolean isReading() { + return timer > 0; + } + + @Override + public boolean justDoneReading() { + return timer == 0; + } + +} \ No newline at end of file diff --git a/doom/src/i/DoomEventInterface.java b/doom/src/i/DoomEventInterface.java new file mode 100644 index 0000000..26fd774 --- /dev/null +++ b/doom/src/i/DoomEventInterface.java @@ -0,0 +1,19 @@ +package i; + +/** Interface for Doom-to-System event handling methods + * + * @author Velktron + * + */ +public interface DoomEventInterface { + + /** The implementation is windowing subsystem-specific + * e.g. DOS, XServer, AWT or Swing or whatever. + * + */ + public void GetEvent(); + + public boolean mouseMoving(); + + public void setMouseMoving(boolean mousMoving); +} \ No newline at end of file diff --git a/doom/src/i/DoomSoundInterface.java b/doom/src/i/DoomSoundInterface.java new file mode 100644 index 0000000..c23c52f --- /dev/null +++ b/doom/src/i/DoomSoundInterface.java @@ -0,0 +1,112 @@ +package i; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomSoundInterface.java,v 1.3 2011/02/11 00:11:13 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// System interface, sound. +// +//----------------------------------------------------------------------------- +import data.sfxinfo_t; + + +/* +// UNIX hack, to be removed. +#ifdef SNDSERV +#include +extern FILE* sndserver; +extern char* sndserver_filename; +#endif*/ +public interface DoomSoundInterface { + +// Init at program start... + public void I_InitSound(); + +// ... update sound buffer and audio device at runtime... + public void I_UpdateSound(); + + public void I_SubmitSound(); + +// ... shut down and relase at program termination. + public void I_ShutdownSound(); + +// +// SFX I/O +// +// Initialize channels? + void I_SetChannels(); + +// Get raw data lump index for sound descriptor. + public int I_GetSfxLumpNum(sfxinfo_t sfxinfo); + +// Starts a sound in a particular sound channel. + public int + I_StartSound(int id, + int vol, + int sep, + int pitch, + int priority); + +// Stops a sound channel. + public void I_StopSound(int handle); + +// Called by S_*() functions +// to see if a channel is still playing. +// Returns 0 if no longer playing, 1 if playing. + public boolean I_SoundIsPlaying(int handle); + +// Updates the volume, separation, +// and pitch of a sound channel. + public void + I_UpdateSoundParams(int handle, + int vol, + int sep, + int pitch); + +// +// MUSIC I/O +// + public void I_InitMusic(); + + public void I_ShutdownMusic(); +// Volume. + + public void I_SetMusicVolume(int volume); +// PAUSE game handling. + + public void I_PauseSong(int handle); + + public void I_ResumeSong(int handle); +// Registers a song handle to song data. + + public int I_RegisterSong(byte[] data); +// Called by anything that wishes to start music. +// plays a song, and when the song is done, +// starts playing it again in an endless loop. +// Horrible thing to do, considering. + + public void + I_PlaySong(int handle, + int looping); +// Stops a song over 3 seconds. + + public void I_StopSong(int handle); +// See above (register), then think backwards + + public void I_UnRegisterSong(int handle); +} \ No newline at end of file diff --git a/doom/src/i/DoomSystem.java b/doom/src/i/DoomSystem.java new file mode 100644 index 0000000..87622f3 --- /dev/null +++ b/doom/src/i/DoomSystem.java @@ -0,0 +1,265 @@ +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomSystem.java,v 1.18 2013/06/04 11:29:39 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: DoomSystem.java,v $ +// Revision 1.18 2013/06/04 11:29:39 velktron +// Shut up logger +// +// Revision 1.17 2013/06/03 10:54:11 velktron +// System interface allows for a logging subsystem, but implementation details may very well vary. +// +// Revision 1.16.2.1 2012/11/19 22:14:35 velktron +// Sync tooling shutdown. +// +// Revision 1.16 2012/11/06 16:05:29 velktron +// Variables manager now part of Main. +// +// Revision 1.15 2012/09/24 17:16:22 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.14.2.2 2012/09/17 15:57:53 velktron +// Aware of IVariablesManager +// +// Revision 1.14.2.1 2012/06/14 22:38:06 velktron +// Update to handle new disk flasher +// +// Revision 1.14 2011/10/24 02:11:27 velktron +// Stream compliancy +// +// Revision 1.13 2011/09/29 15:16:04 velktron +// Modal popup generation moved here. +// +// Revision 1.12 2011/06/12 21:54:31 velktron +// Separate music + sound closing. +// +// Revision 1.11 2011/06/05 22:52:28 velktron +// Proper audio subsystem shutdown. +// +// Revision 1.10 2011/05/29 22:15:32 velktron +// Introduced IRandom interface. +// +// Revision 1.9 2011/05/26 17:56:32 velktron +// Removed ticker functionality, moved to ITicker interface. +// +// Revision 1.8 2011/05/18 16:53:29 velktron +// Implements IDoomSystem now. +// +// Revision 1.7 2011/05/17 16:54:09 velktron +// Switched to DoomStatus +// +// Revision 1.6 2011/05/13 17:44:24 velktron +// Global error function, shutdown on demos. +// +// Revision 1.5 2011/02/11 00:11:13 velktron +// A MUCH needed update to v1.3. +// +// Revision 1.4 2010/12/15 16:12:19 velktron +// Changes in Wiper code and alternate timing method, hoping to fix the Athlon X2 +// +// Revision 1.3 2010/09/24 17:58:39 velktron +// Menus and HU functional -mostly. +// +// Revision 1.2 2010/09/23 20:36:45 velktron +// *** empty log message *** +// +// Revision 1.1 2010/09/23 15:11:57 velktron +// A bit closer... +// +// Revision 1.3 2010/09/07 16:23:00 velktron +// *** empty log message *** +// +// Revision 1.2 2010/08/30 15:53:19 velktron +// Screen wipes work...Finale coded but untested. +// GRID.WAD included for testing. +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// +//----------------------------------------------------------------------------- +package i; + +import awt.MsgBox; +import doom.DoomMain; +import doom.ticcmd_t; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class DoomSystem implements IDoomSystem { + + private static final Logger LOGGER = Loggers.getLogger(DoomSystem.class.getName()); + + public static void MiscError(String error, Object... args) { + LOGGER.log(Level.SEVERE, String.format("Error: %s", error)); + } + + static int mb_used = 6; + + // Even the SYSTEM needs to know about DOOM!!!! + private final DoomMain DM; + private final ticcmd_t emptycmd; + + public DoomSystem(DoomMain DM) { + this.DM = DM; + this.emptycmd = new ticcmd_t(); + } + + @Override + public void Tactile(int on, int off, int total) { + // UNUSED. + on = off = total = 0; + } + + @Override + public ticcmd_t BaseTiccmd() { + return emptycmd; + } + + @Override + public int GetHeapSize() { + return mb_used * 1024 * 1024; + } + + @Override + public byte[] ZoneBase(int size) { + return (new byte[mb_used * 1024 * 1024]); + } + + // + //I_Quit + // + @Override + public void Quit() { + //DM.CheckDemoStatus(); + try { + DM.QuitNetGame(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error: Quit() / DM.QuitNetGame()", e); + } + //DM.debugEnd(); + /** + * In Java we are not locked for exit until everything is shut down + */ + //DM.soundDriver.ShutdownSound(); + //DM.music.ShutdownMusic(); + DM.commit(); + DM.CM.SaveDefaults(); + System.exit(0); + } + + /** + * I_Init + */ + @Override + public void Init() { + //TODO: InitSound(); + //TODO: InitGraphics(); + } + + @Override + public void WaitVBL(int count) { + try { + Thread.sleep(count * 1000 / 70); + } catch (InterruptedException e) { + LOGGER.log(Level.SEVERE, "Error: WaitVBL / Thread.sleep()", e); + } + } + + @Override + public void BeginRead() { + if (DM.diskDrawer != null) { + if (!DM.diskDrawer.isReading()) { + // Set 8 tick reading time + DM.diskDrawer.setReading(8); + } + } + + } + + @Override + public void EndRead() { + } + + @Override + public void AllocLow(int length) { + // Dummy + + } + + // + // I_Error + // + @Override + public void Error(String error, Object... args) { + LOGGER.log(Level.SEVERE, String.format("Error: " + error, args)); + + //va_end (argptr); + //fflush( stderr ); + // Shutdown. Here might be other errors. + if (DM.demorecording) { + DM.CheckDemoStatus(); + } + + try { + DM.QuitNetGame(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error: Error() / DM.QuitNetGame()", e); + } + // DM.VI.ShutdownGraphics(); + + System.exit(-1); + } + + @Override + public void Error(String error) { + //va_list argptr; + + // Message first. + //va_start (argptr,error); + LOGGER.log(Level.SEVERE, String.format("Error: %s", error)); + //va_end (argptr); + + //fflush( stderr ); + // Shutdown. Here might be other errors. + //if (demorecording) + //G_CheckDemoStatus(); + //D_QuitNetGame (); + //I_ShutdownGraphics(); + System.exit(-1); + } + + // This particular implementation will generate a popup box.// + @Override + public boolean GenerateAlert(String title, String cause, boolean showCancelButton) { + MsgBox alert = new MsgBox(null, title, cause, showCancelButton); + return alert.isOk(); + } +} \ No newline at end of file diff --git a/doom/src/i/DummySystem.java b/doom/src/i/DummySystem.java new file mode 100644 index 0000000..26eb5b5 --- /dev/null +++ b/doom/src/i/DummySystem.java @@ -0,0 +1,85 @@ +package i; + +import doom.ticcmd_t; + +public class DummySystem implements IDoomSystem { + + @Override + public void AllocLow(int length) { + // TODO Auto-generated method stub + + } + + @Override + public void BeginRead() { + // TODO Auto-generated method stub + + } + + @Override + public void EndRead() { + // TODO Auto-generated method stub + + } + + @Override + public void WaitVBL(int count) { + // TODO Auto-generated method stub + + } + + @Override + public byte[] ZoneBase(int size) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int GetHeapSize() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void Tactile(int on, int off, int total) { + // TODO Auto-generated method stub + + } + + @Override + public void Quit() { + // TODO Auto-generated method stub + + } + + @Override + public ticcmd_t BaseTiccmd() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void Error(String error, Object... args) { + // TODO Auto-generated method stub + + } + + @Override + public void Error(String error) { + // TODO Auto-generated method stub + + } + + @Override + public void Init() { + // TODO Auto-generated method stub + + } + + @Override + public boolean GenerateAlert(String title, String cause, boolean showCancelButton) { + // TODO Auto-generated method stub + return false; + } + +} \ No newline at end of file diff --git a/doom/src/i/IDiskDrawer.java b/doom/src/i/IDiskDrawer.java new file mode 100644 index 0000000..cee021b --- /dev/null +++ b/doom/src/i/IDiskDrawer.java @@ -0,0 +1,32 @@ +package i; + +public interface IDiskDrawer extends IDrawer { + + /** + * Set a timeout (in tics) for displaying the disk icon + * + * @param timeout + */ + void setReading(int reading); + + /** + * Disk displayer is currently active + * + * @return + */ + boolean isReading(); + + /** + * Only call after the Wadloader is instantiated and initialized itself. + * + */ + void Init(); + + /** + * Status only valid after the last tic has been drawn. Use to know when to redraw status bar. + * + * @return + */ + boolean justDoneReading(); + +} \ No newline at end of file diff --git a/doom/src/i/IDoomSystem.java b/doom/src/i/IDoomSystem.java new file mode 100644 index 0000000..730f229 --- /dev/null +++ b/doom/src/i/IDoomSystem.java @@ -0,0 +1,41 @@ +package i; + +import doom.ticcmd_t; + +public interface IDoomSystem { + + public void AllocLow(int length); + + public void BeginRead(); + + public void EndRead(); + + public void WaitVBL(int count); + + public byte[] ZoneBase(int size); + + public int GetHeapSize(); + + public void Tactile(int on, int off, int total); + + public void Quit(); + + public ticcmd_t BaseTiccmd(); + + public void Error(String error, Object... args); + + void Error(String error); + + void Init(); + + /** Generate a blocking alert with the intention of continuing or aborting + * a certain game-altering action. E.g. loading PWADs, or upon critical + * level loading failures. This can be either a popup panel or console + * message. + * + * @param cause Provide a clear string explaining why the alert was generated + * @return true if we should continue, false if an alternate action should be taken. + */ + boolean GenerateAlert(String title, String cause, boolean showCancelButton); + +} \ No newline at end of file diff --git a/doom/src/i/IDrawer.java b/doom/src/i/IDrawer.java new file mode 100644 index 0000000..cb98f6e --- /dev/null +++ b/doom/src/i/IDrawer.java @@ -0,0 +1,7 @@ +package i; + +public interface IDrawer { + + public void Drawer(); + +} \ No newline at end of file diff --git a/doom/src/i/Strings.java b/doom/src/i/Strings.java new file mode 100644 index 0000000..c17c7c2 --- /dev/null +++ b/doom/src/i/Strings.java @@ -0,0 +1,41 @@ +package i; + +public final class Strings { + + public static final String MOCHA_DOOM_TITLE = "Vanilla Mocha Doom Alpha 1.6.15"; + + public static final String MODIFIED_GAME_TITLE = "Modified game alert"; + + public static final String MODIFIED_GAME_DIALOG + = ("
" + + "===========================================================================
" + + "ATTENTION: This version of DOOM has been modified. If you would like to
" + + "get a copy of the original game, call 1-800-IDGAMES or see the readme file.
" + + " You will not receive technical support for modified games.
" + + " press OK to continue
" + + "===========================================================================
" + + "
"); + + public static final String LEVEL_FAILURE_TITLE = "Level loading failure"; + + public static final String LEVEL_FAILURE_CAUSE + = ("
" + + "Level loading failed!
" + + "Press OK to end this game without exiting, or cancel to quit Doom." + + "
"); + + public static final String NO_WAD_FILE_FOUND_TITLE = "Cannot find a game IWAD"; + + public static final String NO_WAD_FILE_FOUND_NOTE = "" + + "===========================================================================
" + + "Execution could not continue:
" + + "Cannot find a game IWAD (doom.wad, doom2.wad, etc.).
" + + "
" + + "You can do either of the following:
" + + "- Place one or more of these WADs in the same directory as Mocha Doom.
" + + "- Start Mocha Doom with '-iwad' parameter e.g. 'mochadoom -iwad wads/doom.wad'
" + + "- Define 'DOOMWADDIR' environment variable which points to a directory of WAD files.
" + + "===========================================================================
" + + ""; + +} \ No newline at end of file diff --git a/doom/src/i/SystemSoundInterface.java b/doom/src/i/SystemSoundInterface.java new file mode 100644 index 0000000..d74dc3b --- /dev/null +++ b/doom/src/i/SystemSoundInterface.java @@ -0,0 +1,112 @@ +package i; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: SystemSoundInterface.java,v 1.2 2011/05/17 16:51:20 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// System interface, sound. +// +//----------------------------------------------------------------------------- +import data.sfxinfo_t; + + +/* +// UNIX hack, to be removed. +#ifdef SNDSERV +#include +extern FILE* sndserver; +extern char* sndserver_filename; +#endif*/ +public interface SystemSoundInterface { + +// Init at program start... + public void InitSound(); + +// ... update sound buffer and audio device at runtime... + public void UpdateSound(); + + public void SubmitSound(); + +// ... shut down and relase at program termination. + public void ShutdownSound(); + +// +// SFX I/O +// +// Initialize channels? + void SetChannels(); + +// Get raw data lump index for sound descriptor. + public int GetSfxLumpNum(sfxinfo_t sfxinfo); + +// Starts a sound in a particular sound channel. + public int + StartSound(int id, + int vol, + int sep, + int pitch, + int priority); + +// Stops a sound channel. + public void StopSound(int handle); + +// Called by S_*() functions +// to see if a channel is still playing. +// Returns 0 if no longer playing, 1 if playing. + public boolean SoundIsPlaying(int handle); + +// Updates the volume, separation, +// and pitch of a sound channel. + public void + UpdateSoundParams(int handle, + int vol, + int sep, + int pitch); + +// +// MUSIC I/O +// + public void InitMusic(); + + public void ShutdownMusic(); +// Volume. + + public void SetMusicVolume(int volume); +// PAUSE game handling. + + public void PauseSong(int handle); + + public void ResumeSong(int handle); +// Registers a song handle to song data. + + public int RegisterSong(byte[] data); +// Called by anything that wishes to start music. +// plays a song, and when the song is done, +// starts playing it again in an endless loop. +// Horrible thing to do, considering. + + public void + PlaySong(int handle, + int looping); +// Stops a song over 3 seconds. + + public void StopSong(int handle); +// See above (register), then think backwards + + public void UnRegisterSong(int handle); +} \ No newline at end of file diff --git a/doom/src/i/system.java b/doom/src/i/system.java new file mode 100644 index 0000000..8315367 --- /dev/null +++ b/doom/src/i/system.java @@ -0,0 +1,195 @@ +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: system.java,v 1.5 2011/02/11 00:11:13 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: system.java,v $ +// Revision 1.5 2011/02/11 00:11:13 velktron +// A MUCH needed update to v1.3. +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// +//----------------------------------------------------------------------------- +package i; + +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class system { + + private static final Logger LOGGER = Loggers.getLogger(system.class.getName()); + + /* +#include +#include +#include + +#include +#include +#include + +#include "doomdef.h" +#include "m_misc.h" +#include "i_video.h" +#include "i_sound.h" + +#include "d_net.h" +#include "g_game.h" + +#ifdef __GNUG__ +#pragma implementation "i_system.h" +#endif +#include "i_system.h" + + */ + static int mb_used = 6; + + public void + Tactile(int on, + int off, + int total) { + // UNUSED. + on = off = total = 0; + } + + /* +ticcmd_t emptycmd; +ticcmd_t* I_BaseTiccmd(void) +{ + return &emptycmd; +} + + */ + public static int GetHeapSize() { + return mb_used * 1024 * 1024; + } + + /* +byte* I_ZoneBase (int* size) +{ + *size = mb_used*1024*1024; + return (byte *) malloc (*size); +} + */ +// +// I_GetTime +// returns time in 1/70th second tics +// +/* +int I_GetTime () +{ + struct timeval tp; + struct timezone tzp; + int newtics; + static int basetime=0; + + gettimeofday(&tp, &tzp); + if (!basetime) + basetime = tp.tv_sec; + newtics = (tp.tv_sec-basetime)*TICRATE + tp.tv_usec*TICRATE/1000000; + return newtics; +} + */ +// +// I_Init +// +/* +void I_Init (void) +{ + I_InitSound(); + // I_InitGraphics(); +} + +// +// I_Quit +// +void I_Quit (void) +{ + D_QuitNetGame (); + I_ShutdownSound(); + I_ShutdownMusic(); + M_SaveDefaults (); + I_ShutdownGraphics(); + exit(0); +} + +void I_WaitVBL(int count) +{ +#ifdef SGI + sginap(1); +#else +#ifdef SUN + sleep(0); +#else + usleep (count * (1000000/70) ); +#endif +#endif +} + +void I_BeginRead(void) +{ +} + +void I_EndRead(void) +{ +} + +byte* I_AllocLow(int length) +{ + byte* mem; + + mem = (byte *)malloc (length); + memset (mem,0,length); + return mem; +} + + */ +// +// I_Error +// + public static boolean demorecording; + + public static void Error(String error, Object... args) { + //va_list argptr; + + // Message first. + //va_start (argptr,error); + LOGGER.log(Level.SEVERE, String.format("Error: " + error, args)); + //va_end (argptr); + + //fflush( stderr ); + // Shutdown. Here might be other errors. + //if (demorecording) + //G_CheckDemoStatus(); + //D_QuitNetGame (); + //I_ShutdownGraphics(); + System.exit(-1); + } +} \ No newline at end of file diff --git a/doom/src/m/AbstractDoomMenu.java b/doom/src/m/AbstractDoomMenu.java new file mode 100644 index 0000000..b4921b7 --- /dev/null +++ b/doom/src/m/AbstractDoomMenu.java @@ -0,0 +1,13 @@ +package m; + +import doom.DoomMain; + +public abstract class AbstractDoomMenu implements IDoomMenu { + + ////////////////////// CONTEXT /////////////////// + final DoomMain DOOM; + + public AbstractDoomMenu(DoomMain DOOM) { + this.DOOM = DOOM; + } +} \ No newline at end of file diff --git a/doom/src/m/BBox.java b/doom/src/m/BBox.java new file mode 100644 index 0000000..7b0e4e5 --- /dev/null +++ b/doom/src/m/BBox.java @@ -0,0 +1,165 @@ +package m; + +import static data.Limits.MAXINT; +import static data.Limits.MININT; + +/** A fucked-up bounding box class. + * Fucked-up because it's supposed to wrap fixed_t's.... no fucking way I'm doing + * this with fixed_t objects. + * + * @author admin + * + */ +public class BBox { + + public static final int BOXTOP = 0; + public static final int BOXBOTTOM = 1; + public static final int BOXLEFT = 2; + public static final int BOXRIGHT = 3; + /** (fixed_t) */ + public int[] bbox; + + /** Points of the bbox as an object */ + public BBox() { + bbox = new int[4]; + } + + // Static method + public static void ClearBox(fixed_t[] box) { + box[BOXRIGHT].set(MININT); + box[BOXTOP].set(MININT); + box[BOXLEFT].set(MAXINT); + box[BOXBOTTOM].set(MAXINT); + } + + // Instance method + public void ClearBox() { + bbox[BOXRIGHT] = (MININT); + bbox[BOXTOP] = (MININT); + bbox[BOXLEFT] = (MAXINT); + bbox[BOXBOTTOM] = (MAXINT); + } + + public static void AddToBox(fixed_t[] box, fixed_t x, fixed_t y) { + if (x.compareTo(box[BOXLEFT]) < 0) { + box[BOXLEFT].copy(x); + } else if (x.compareTo(box[BOXRIGHT]) > 0) { + box[BOXRIGHT].copy(x); + } + if (y.compareTo(box[BOXBOTTOM]) < 0) { + box[BOXBOTTOM] = y; + } else if (y.compareTo(box[BOXTOP]) > 0) { + box[BOXTOP] = y; + } + } + + public void AddToBox(fixed_t x, fixed_t y) { + if (x.compareTo(bbox[BOXLEFT]) < 0) { + bbox[BOXLEFT] = x.val; + } else if (x.compareTo(bbox[BOXRIGHT]) > 0) { + bbox[BOXRIGHT] = x.val; + } + if (y.compareTo(bbox[BOXBOTTOM]) < 0) { + bbox[BOXBOTTOM] = y.val; + } else if (y.compareTo(bbox[BOXTOP]) > 0) { + bbox[BOXTOP] = y.val; + } + } + + /** + * MAES: Keeping with C's type (in)consistency, we also allow to input ints + * -_- + * + * @param x + * @param y + */ + public void AddToBox(int x, int y) { + if (x < bbox[BOXLEFT]) { + bbox[BOXLEFT] = (x); + } + if (x > bbox[BOXRIGHT]) { + bbox[BOXRIGHT] = (x); + } + if (y < bbox[BOXBOTTOM]) { + bbox[BOXBOTTOM] = (y); + } + if (y > bbox[BOXTOP]) { + bbox[BOXTOP] = (y); + } + } + + /** + * R_AddPointToBox Expand a given bbox so that it encloses a given point. + * + * @param x + * @param y + * @param box + */ + public static void AddPointToBox(int x, int y, fixed_t[] box) { + if (x < box[BOXLEFT].val) { + box[BOXLEFT].set(x); + } + if (x > box[BOXRIGHT].val) { + box[BOXRIGHT].set(x); + } + if (y < box[BOXBOTTOM].val) { + box[BOXBOTTOM].set(y); + } + if (y > box[BOXTOP].val) { + box[BOXTOP].set(y); + } + } + + /** + * R_AddPointToBox Expand this bbox so that it encloses a given point. + * + * @param x + * @param y + * @param box + */ + public void AddPointToBox(int x, int y) { + if (x < bbox[BOXLEFT]) { + bbox[BOXLEFT] = x; + } + if (x > bbox[BOXRIGHT]) { + bbox[BOXRIGHT] = x; + } + if (y < bbox[BOXBOTTOM]) { + bbox[BOXBOTTOM] = y; + } + if (y > bbox[BOXTOP]) { + bbox[BOXTOP] = y; + } + } + + public int get(int BOXCOORDS) { + return this.bbox[BOXCOORDS]; + } + + public void set(int BOXCOORDS, int val) { + this.bbox[BOXCOORDS] = val; + } + + public static void ClearBox(int[] bbox) { + bbox[BOXRIGHT] = (MININT); + bbox[BOXTOP] = (MININT); + bbox[BOXLEFT] = (MAXINT); + bbox[BOXBOTTOM] = (MAXINT); + } + + public static void AddToBox(int[] box, int x, int y) { + if (x < box[BOXLEFT]) { + box[BOXLEFT] = x; + } + if (x > box[BOXRIGHT]) { + box[BOXRIGHT] = x; + } + if (y < box[BOXBOTTOM]) { + box[BOXBOTTOM] = y; + } + if (y > box[BOXTOP]) { + box[BOXTOP] = y; + } + } + +} \ No newline at end of file diff --git a/doom/src/m/DelegateRandom.java b/doom/src/m/DelegateRandom.java new file mode 100644 index 0000000..315232c --- /dev/null +++ b/doom/src/m/DelegateRandom.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package m; + +import data.Defines; +import data.mobjtype_t; +import doom.SourceCode.M_Random; +import static doom.SourceCode.M_Random.M_ClearRandom; +import static doom.SourceCode.M_Random.M_Random; +import static doom.SourceCode.M_Random.P_Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.ActiveStates; +import utils.C2JUtils; + +/** + * A "IRandom" that delegates its function to one of the two available IRandom implementations + * By default, MochaDoom now uses JavaRandom, however it switches + * to DoomRandom (supposedly Vanilla DOOM v1.9 compatible, tested only in Chocolate DOOM) + * whenever you start recording or playing demo. When you start then new game, MochaDoom restores new JavaRandom. + * + * However, if you start MochaDoom with -javarandom command line argument and -record demo, + * then MochaDoom will record the demo using JavaRandom. Such demo will be neither compatible + * with Vanilla DOOM v1.9, nor with another source port. + * + * Only MochaDoom can play JavaRandom demos. + * - Good Sign 2017/04/10 + * + * @author Good Sign + */ +public class DelegateRandom implements IRandom { + + private static final Logger LOGGER = Loggers.getLogger(DelegateRandom.class.getName()); + + private IRandom random; + private IRandom altRandom; + + public DelegateRandom() { + this.random = new JavaRandom(); + } + + public void requireRandom(final int version) { + if (C2JUtils.flags(version, Defines.JAVARANDOM_MASK) && this.random instanceof DoomRandom) { + switchRandom(true); + } else if (!C2JUtils.flags(version, Defines.JAVARANDOM_MASK) && !(this.random instanceof DoomRandom)) { + switchRandom(false); + } + } + + private void switchRandom(boolean which) { + IRandom arandom = altRandom; + if (arandom != null && ((!which && arandom instanceof DoomRandom) || (which && arandom instanceof JavaRandom))) { + this.altRandom = random; + this.random = arandom; + LOGGER.log(Level.INFO, String.format("M_Random: Switching to %s", random.getClass().getSimpleName())); + } else { + this.altRandom = random; + this.random = which ? new JavaRandom() : new DoomRandom(); + LOGGER.log(Level.INFO, String.format("M_Random: Switching to %s (new instance)", random.getClass().getSimpleName())); + } + //random.ClearRandom(); + } + + @Override + @M_Random.C(P_Random) + public int P_Random() { + return random.P_Random(); + } + + @Override + @M_Random.C(M_Random) + public int M_Random() { + return random.M_Random(); + } + + @Override + @M_Random.C(M_ClearRandom) + public void ClearRandom() { + random.ClearRandom(); + } + + @Override + public int getIndex() { + return random.getIndex(); + } + + @Override + public int P_Random(int caller) { + return random.P_Random(caller); + } + + @Override + public int P_Random(String message) { + return random.P_Random(message); + } + + @Override + public int P_Random(ActiveStates caller, int sequence) { + return random.P_Random(caller, sequence); + } + + @Override + public int P_Random(ActiveStates caller, mobjtype_t type, int sequence) { + return random.P_Random(caller, type, sequence); + } + +} \ No newline at end of file diff --git a/doom/src/m/DoomRandom.java b/doom/src/m/DoomRandom.java new file mode 100644 index 0000000..44737e6 --- /dev/null +++ b/doom/src/m/DoomRandom.java @@ -0,0 +1,181 @@ +package m; + +import data.mobjtype_t; +import p.ActiveStates; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomRandom.java,v 1.4 2013/06/04 11:29:25 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: DoomRandom.java,v $ +// Revision 1.4 2013/06/04 11:29:25 velktron +// Dummy implementations +// +// Revision 1.3 2013/06/03 10:53:29 velktron +// Implements the new IRandom. +// +// Revision 1.2.10.3 2013/01/09 19:38:26 velktron +// Printing arbitrary messages +// +// Revision 1.2.10.2 2012/11/20 15:59:20 velktron +// More tooling functions. +// +// Revision 1.2.10.1 2012/11/19 22:11:36 velktron +// Added demo sync tooling. +// +// Revision 1.2 2011/05/30 02:24:30 velktron +// *** empty log message *** +// +// Revision 1.1 2011/05/29 22:15:32 velktron +// Introduced IRandom interface. +// +// Revision 1.4 2010/09/22 16:40:02 velktron +// MASSIVE changes in the status passing model. +// DoomMain and DoomGame unified. +// Doomstat merged into DoomMain (now status and game functions are one). +// +// Most of DoomMain implemented. Possible to attempt a "classic type" start but will stop when reading sprites. +// +// Revision 1.3 2010/09/10 17:35:49 velktron +// DoomGame, Menu, renderers +// +// Revision 1.2 2010/07/06 16:32:38 velktron +// Threw some work in WI, now EndLevel. YEAH THERE'S GONNA BE A SEPARATE EndLevel OBJECT THAT'S HOW PIMP THE PROJECT IS!!!!11!!! +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, and there's still mixed C code in there. I suggest you load everything up in Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// Random number LUT. +// +//----------------------------------------------------------------------------- +class DoomRandom implements IRandom { + + /** + * M_Random + * Returns a 0-255 number. Made into shorts for Java, because of their nature. + */ + public static short rndtable[] = { + 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66, + 74, 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36, + 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188, + 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224, + 149, 104, 25, 178, 252, 182, 202, 182, 141, 197, 4, 81, 181, 242, + 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, 249, 0, + 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235, + 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113, + 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75, + 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196, + 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, 170, 247, 181, 113, + 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, 166, 103, 241, + 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224, + 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95, + 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226, + 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36, + 17, 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106, + 197, 242, 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136, + 120, 163, 236, 249 + }; + + protected int rndindex = 0; + protected int prndindex = 0; + + // Which one is deterministic? + @Override + public int P_Random() { + prndindex = (prndindex + 1) & 0xff; + return rndtable[prndindex]; + } + + /** + * [Maes] I'd rather dispatch the call here, than making IRandom aware of DoomStatus. Replace RND.P_Random calls + * with DM.P_Random(callerid) etc. + * + * Fixme: this could be made into a proper enum + * + * @param caller + */ + @Override + public int P_Random(int caller) { + int value = P_Random(); + SLY.sync("PR #%d [%d]=%d\n", caller, prndindex, value); + return value; + } + + @Override + public int P_Random(String message) { + int value = P_Random(); + SLY.sync("PR %s [%d]=%d\n", message, + prndindex, value); + return value; + } + + @Override + public int P_Random(ActiveStates caller, int sequence) { + /* + SLY.sync("PR #%d %s_%d [%d]=%d\n", caller.ordinal(),caller,sequence, + prndindex, value);*/ + + return P_Random(); + } + + @Override + public int P_Random(ActiveStates caller, mobjtype_t type, int sequence) { + /* + SLY.sync("PR #%d %s_%d %s [%d]=%d\n", caller.ordinal(),caller,sequence, + type, prndindex, value);*/ + + return P_Random(); + } + + @Override + public int M_Random() { + rndindex = (rndindex + 1) & 0xff; + return rndtable[rndindex]; + } + + @Override + public void ClearRandom() { + rndindex = prndindex = 0; + } + + DoomRandom() { + SLY = null; + } + + @Override + public int getIndex() { + return prndindex; + } + + DoomRandom(ISyncLogger SLY) { + this.SLY = SLY; + } + + private final ISyncLogger SLY; + +} \ No newline at end of file diff --git a/doom/src/m/DoomSetting.java b/doom/src/m/DoomSetting.java new file mode 100644 index 0000000..bdbb4c1 --- /dev/null +++ b/doom/src/m/DoomSetting.java @@ -0,0 +1,223 @@ +package m; + +import utils.C2JUtils; + +/** A "Doom setting". Based on current experience, it could + * represent an integer value, a string, or a boolean value. + * + * Therefore, every setting can be interpreted as any of the above, + * based on some rules. Strings that can be interpreted as parseable + * numbers are obvious, and numbers can also be interpreted as strings. + * Strings that can't be interpreted as numbers will return "0" as a default + * value. + * + * A numerical value of 1 means "true", any other value is "false". + * A string representing the (case insensitive) value "true" will + * be interpreted as a true boolean, false otherwise. + * + * @author velktron + * + * + */ +public class DoomSetting implements Comparable { + + public static final int BOOLEAN = 1; + public static final int CHAR = 2; + public static final int DOUBLE = 4; + public static final int INTEGER = 8; + public static final int STRING = 16; + + private String name; + + private int typeflag; + + // Every setting can be readily interpreted as any of these + private int int_val; + private long long_val; + private char char_val; + private double double_val; + private boolean boolean_val; + private String string_val; + + /** Should be saved to file */ + private boolean persist; + + public DoomSetting(String name, String value, boolean persist) { + this.name = name; + this.typeflag = STRING; + this.updateValue(value); + this.persist = persist; + } + + public String getName() { + return name; + } + + public int getInteger() { + return int_val; + } + + public long getLong() { + return long_val; + } + + public char getChar() { + return (char) int_val; + } + + public String getString() { + return string_val; + } + + public double getDouble() { + return double_val; + } + + public boolean getBoolean() { + return boolean_val; + } + + public boolean getPersist() { + return persist; + } + + public int getTypeFlag() { + return typeflag; + } + + /** All the gory disambiguation work should go here. + * + * @param value + */ + public void updateValue(String value) { + + boolean quoted = false; + + if (value.length() > 2) { + if (quoted = C2JUtils.isQuoted(value, '"')) { + value = C2JUtils.unquote(value, '"'); + } else if (quoted = C2JUtils.isQuoted(value, '\'')) { + value = C2JUtils.unquote(value, '\''); + } + } + + // String value always available + this.string_val = value; + + // If quoted and sensibly ranged, it gets priority as a "character" + if (quoted && value.length() == 1 && value.charAt(0) >= 0 && value.charAt(0) < 255) { + char_val = Character.toLowerCase(value.charAt(0)); + int_val = char_val; + long_val = char_val; + double_val = char_val; + typeflag |= CHAR; + return; + } + + // Not a character, try all other stuff + try { + this.int_val = Integer.parseInt(value); + typeflag |= INTEGER; + } catch (NumberFormatException e) { + // No nookie + this.int_val = -1; + } + + try { + this.long_val = Long.parseLong(value); + } catch (NumberFormatException e) { + try { + // Try decoding it as hex, octal, whatever. + this.long_val = Long.decode(value); + typeflag |= INTEGER; + } catch (NumberFormatException h) { + // If even THAT fails, then no nookie. + this.long_val = -1; + } + } + + try { + this.double_val = Double.parseDouble(value); + typeflag |= DOUBLE; + } catch (NumberFormatException e) { + // No nookie + this.double_val = Double.NaN; + } + + // Use long value to "trump" smaller ones + int_val = (int) long_val; + char_val = (char) int_val; + + // Boolean has a few more options; + // Only mark something explicitly as boolean if the string reads + // actually "true" or "false". Numbers such as 0 and 1 might still get + // interpreted as booleans, but that shouldn't trump the entire number, + // otherwise everything and the cat is boolean + this.boolean_val = (int_val == 1); + + if (Boolean.parseBoolean(value) + || (value.compareToIgnoreCase("false") == 0)) { + this.boolean_val = (int_val == 1) || Boolean.parseBoolean(value); + this.typeflag |= BOOLEAN; + } + } + + /** Answer definitively if a setting cannot ABSOLUTELY be + * parsed into a number using simple Integer rules. + * This excludes some special names like "+Inf" and "NaN". + * + * @return + */ + public boolean isIntegerNumeric() { + + try { + this.long_val = Long.parseLong(string_val); + } catch (NumberFormatException e) { + try { + // Try decoding it as hex, octal, whatever. + Long.decode(string_val); + + } catch (NumberFormatException h) { + // If even THAT fails, then no nookie. + return false; + } + } + + // Everything OK, I presume... + return true; + } + + /** Settings are "comparable" to each other by name, so we can save + * nicely sorted setting files ;-) + * + * @param o + * @return + */ + @Override + public int compareTo(DoomSetting o) { + return this.name.compareToIgnoreCase(o.getName()); + } + + public String toString() { + return string_val; + } + + /** A special setting that returns false, 0 and an empty string, if required. + * Simplifies handling of nulls A LOT. So code that relies on specific settings + * should be organized to work only on clear positivies (e.g. use a "fullscreen" setting + * that must exist and be equal to 1 or true, instead of assuming that a zero/false + * value enables it. */ + public static DoomSetting NULL_SETTING = new DoomSetting("NULL", "", false); + + static { + // It's EVERYTHING + NULL_SETTING.typeflag = 0x1F; + NULL_SETTING.string_val = ""; + NULL_SETTING.char_val = 0; + NULL_SETTING.double_val = 0; + NULL_SETTING.boolean_val = false; + NULL_SETTING.int_val = 0; + NULL_SETTING.long_val = 0; + } + +} \ No newline at end of file diff --git a/doom/src/m/DrawRoutine.java b/doom/src/m/DrawRoutine.java new file mode 100644 index 0000000..5376c74 --- /dev/null +++ b/doom/src/m/DrawRoutine.java @@ -0,0 +1,13 @@ +package m; + +/** menu_t required a function pointer to a (routine)() that drew stuff. + * So any class implementing them will implement this interface, and + * we can have a single class type for all of them. + * + * @author Maes + * + */ +public interface DrawRoutine { + + public void invoke(); +} \ No newline at end of file diff --git a/doom/src/m/DummyMenu.java b/doom/src/m/DummyMenu.java new file mode 100644 index 0000000..d579092 --- /dev/null +++ b/doom/src/m/DummyMenu.java @@ -0,0 +1,84 @@ +package m; + +import doom.DoomMain; +import doom.event_t; + +/** A dummy menu, useful for testers that do need a defined + * menu object. + * + * @author Maes + * + */ +public class DummyMenu extends AbstractDoomMenu { + + public DummyMenu(DoomMain DOOM) { + super(DOOM); + } + + @Override + public boolean Responder(event_t ev) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void Ticker() { + // TODO Auto-generated method stub + + } + + @Override + public void Drawer() { + // TODO Auto-generated method stub + + } + + @Override + public void Init() { + // TODO Auto-generated method stub + + } + + @Override + public void StartControlPanel() { + // TODO Auto-generated method stub + + } + + @Override + public boolean getShowMessages() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setShowMessages(boolean val) { + // TODO Auto-generated method stub + + } + + @Override + public int getScreenBlocks() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setScreenBlocks(int val) { + // TODO Auto-generated method stub + + } + + @Override + public int getDetailLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void ClearMenus() { + // TODO Auto-generated method stub + + } + +} \ No newline at end of file diff --git a/doom/src/m/FixedFloat.java b/doom/src/m/FixedFloat.java new file mode 100644 index 0000000..a77a8cb --- /dev/null +++ b/doom/src/m/FixedFloat.java @@ -0,0 +1,135 @@ +package m; + +/** Some utilities for switching between floating and signed 16.16 fixed-point at will. + * They use direct bit manipulation with little -if any- looping. + * + * The methods can probably be generalized but not a priority for now. + * They do not handle Infinities, NaNs and unnormalized numbers. + * + * @author Maes + * + */ +public class FixedFloat { + + // Various bit masks for IEEE-754 floating point + public static final int MANTISSA_32 = 0x007FFFFF; + public static final int EXP_32 = 0x7F800000; + public static final int IMPLICIT_32 = 0x00800000; + public static final int SIGN_32 = 0x80000000; + public static final int NONSIGN_32 = 0x7FFFFFFF; + public static final long SIGN_64 = 0x8000000000000000L; + public static final long EXP_64 = 0x7FF0000000000000L; + public static final long IMPLICIT_64 = 0x0010000000000000L; + public static final long MANTISSA_64 = 0x000fffffffffffffL; + + public static float toFloat(int fixed) { + if (fixed == 0) { + return (float) (0.0); + } + // Remember sign. + int sign = fixed & SIGN_32; + if (fixed < 0) { + fixed = -fixed; + } + int exp = findShift(fixed); + // First shift to left to "cancel" bits "above" the first. + int mantissa = (fixed << (exp + 2)) >>> 9; + int result = sign | (((14 - exp) + 127) << 23) | mantissa; + /*if (fixed<0) System.out.println(Integer.toBinaryString(fixed) +"\n"+ + Integer.toBinaryString(-fixed) +"\n"+ + Integer.toBinaryString(result));*/ + return Float.intBitsToFloat(result); + } + + private static int findShift(int fixed) { + // only non-sign bits. + fixed &= NONSIGN_32; + // We assume that the MSb after the sign is set. + int shift = 30; + while ((shift >= 0) && (fixed >>> shift) == 0) // It's not, apparently + { + shift--; + } + + // Positions 0-15 are fractional, anything above 15 is integer. + // Return two's complement shift. + return (30 - shift); + + } + + public static double toDouble(int fixed) { + + // Remember sign. + long fx = fixed; + fx <<= 32; + long sign = (long) fx & SIGN_64; + + if (fixed < 0) { + fixed = -fixed; + fx = -fx; + } + long exp = findShift(fixed); + // First shift to left to "swallow" sign and implicit 1. + long bits = (fx << (exp + 2)) >>> 12; + long result = sign | (((14 - exp) + 1023) << 52) | bits; + return Double.longBitsToDouble(result); + } + + public static int toFixed(float fl) { + // Get the raw bits. + int flbits = Float.floatToRawIntBits(fl); + // Remember sign. + int sign = flbits & SIGN_32; + // Join together: the implcit 1 and the mantissa bits. + // We now have the "denormalized" value. + int denorm = IMPLICIT_32 | (flbits & MANTISSA_32); + // Get exponent...acceptable values are (-15 ~ 15), else wrap around (use only sign and lowest 4 bits). + int exp = (((flbits & EXP_32) >> 23) - 127) & 0x8000000F; + /* Remember, leftmost "1" will be at position 23. + * So for an exponent of 0, we must shift to position 16. + * For positive exponents in general, we must shift -7 + exp. + * and for one of 15, to position 30, plus the sign. + * While there is space for all bits, we can't keep them all, + * as some (well, many)numbers can't be represented in fixed point. + * + */ + int result; + if ((exp - 7) >= 0) { + result = sign | (denorm << (exp - 7)); + } else { + result = sign | (denorm >>> (7 - exp)); + } + return result; + } + + public static int toFixed(double fl) { + + // Get the raw bits. + long flbits = Double.doubleToRawLongBits(fl); + // Remember sign. + int sign = (int) ((flbits & SIGN_64) >> 32); + // Join together: the implcit 1 and the mantissa bits. + // We now have the "denormalized" value. + long denorm = IMPLICIT_64 | (flbits & MANTISSA_64); + //System.out.println("Denorm"+Integer.toBinaryString(denorm)); + // Get exponent...acceptable values are (-15 ~ 15), else wrap around (use only sign and lowest 4 bits). + int exp = (int) (((flbits & EXP_64) >> 52) - 1023) & 0x8000000F; + /* Remember, leftmost "1" will be at position 53. + * So for an exponent of 0, we must shift to position 16. + * For positive exponents in general, we must shift -37 + exp. + * and for one of 15, to position 30, plus the sign. + * While there is space for all bits, we can't keep them all, + * as some (well, many)numbers can't be represented in fixed point. + * + */ + int result; + if ((exp - 36) >= 0) { + result = (int) (sign | (denorm << (exp - 36))); + } else { + result = (int) (sign | (denorm >>> (36 - exp))); + } + //int result=sign|(IMPLICIT_32|(mantissa<<(exp-127)))<<8; + return result; + } + +} \ No newline at end of file diff --git a/doom/src/m/IDoomMenu.java b/doom/src/m/IDoomMenu.java new file mode 100644 index 0000000..82f12bf --- /dev/null +++ b/doom/src/m/IDoomMenu.java @@ -0,0 +1,84 @@ +package m; + +import doom.SourceCode.M_Menu; +import static doom.SourceCode.M_Menu.M_Drawer; +import static doom.SourceCode.M_Menu.M_Init; +import static doom.SourceCode.M_Menu.M_Responder; +import static doom.SourceCode.M_Menu.M_StartControlPanel; +import static doom.SourceCode.M_Menu.M_Ticker; +import doom.event_t; + +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: IDoomMenu.java,v 1.5 2011/09/29 15:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Menu widget stuff, episode selection and such. +// +// ----------------------------------------------------------------------------- +/** + * + */ +public interface IDoomMenu { + + // + // MENUS + // + /** + * Called by main loop, saves config file and calls I_Quit when user exits. + * Even when the menu is not displayed, this can resize the view and change + * game parameters. Does all the real work of the menu interaction. + */ + @M_Menu.C(M_Responder) + public boolean Responder(event_t ev); + + /** + * Called by main loop, only used for menu (skull cursor) animation. + */ + @M_Menu.C(M_Ticker) + public void Ticker(); + + /** + * Called by main loop, draws the menus directly into the screen buffer. + */ + @M_Menu.C(M_Drawer) + public void Drawer(); + + /** + * Called by D_DoomMain, loads the config file. + */ + @M_Menu.C(M_Init) + public void Init(); + + /** + * Called by intro code to force menu up upon a keypress, does nothing if + * menu is already up. + */ + @M_Menu.C(M_StartControlPanel) + public void StartControlPanel(); + + public boolean getShowMessages(); + + public void setShowMessages(boolean val); + + public int getScreenBlocks(); + + public void setScreenBlocks(int val); + + public int getDetailLevel(); + + void ClearMenus(); +} \ No newline at end of file diff --git a/doom/src/m/IRandom.java b/doom/src/m/IRandom.java new file mode 100644 index 0000000..97bf0aa --- /dev/null +++ b/doom/src/m/IRandom.java @@ -0,0 +1,23 @@ +package m; + +import data.mobjtype_t; +import p.ActiveStates; + +public interface IRandom { + + public int P_Random(); + + public int M_Random(); + + public void ClearRandom(); + + public int getIndex(); + + public int P_Random(int caller); + + public int P_Random(String message); + + public int P_Random(ActiveStates caller, int sequence); + + public int P_Random(ActiveStates caller, mobjtype_t type, int sequence); +} \ No newline at end of file diff --git a/doom/src/m/ISyncLogger.java b/doom/src/m/ISyncLogger.java new file mode 100644 index 0000000..7aa13ab --- /dev/null +++ b/doom/src/m/ISyncLogger.java @@ -0,0 +1,12 @@ +package m; + +import java.io.IOException; + +public interface ISyncLogger { + + public void debugStart() throws IOException; + + public void debugEnd(); + + public void sync(String format, Object... args); +} \ No newline at end of file diff --git a/doom/src/m/JavaRandom.java b/doom/src/m/JavaRandom.java new file mode 100644 index 0000000..021f971 --- /dev/null +++ b/doom/src/m/JavaRandom.java @@ -0,0 +1,115 @@ +package m; + +import data.mobjtype_t; +import java.util.Random; +import p.ActiveStates; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: JavaRandom.java,v 1.3 2013/06/03 11:00:03 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// Random number LUT using java.util.Random +// Don't expect vanilla demo compatibility with THIS! +// +//----------------------------------------------------------------------------- +/** + * Actually, now there is demo compatilbility switch: use of JavaRandom is now + * default in singleplayer, unless you play demo, unless you record demo, + * when you play demo, DoomRandom is picked instead, same for record, unless + * you specify -javarandom command line argument, in that case when you record + * demo, version information will be changed, and JavaRandom used, + * when you play this demo, DoomRandom will not be picked, when you play + * another demo, it will pick DoomRandom. + * + * When you dont pass -javarandom, but play demo recorded with JavaRandom, + * it will pick JavaRandom for this demo playback + * - Good Sign 2017/04/14 + */ +class JavaRandom implements IRandom { + + protected int rndindex = 0; + protected int prndindex = 0; + + // Which one is deterministic? + @Override + public int P_Random() { + rndindex++; + return (0xFF & r.nextInt()); + } + + @Override + public int M_Random() { + prndindex++; + return (0xFF & m.nextInt()); + } + + @Override + public final void ClearRandom() { + rndindex = prndindex = 0; + r.setSeed(666); + } + + JavaRandom() { + r = new Random(666); + m = new Random(666); + this.ClearRandom(); + } + + @Override + public int getIndex() { + return rndindex; + } + + private final Random r; + private final Random m; + + @Override + public int P_Random(int caller) { + // DUMMY + return P_Random(); + } + + @Override + public int P_Random(String message) { + // DUMMY + return P_Random(); + } + + @Override + public int P_Random(ActiveStates caller, int sequence) { + // DUMMY + return P_Random(); + } + + @Override + public int P_Random(ActiveStates caller, mobjtype_t type, int sequence) { + // DUMMY + return P_Random(); + } + +} + +//$Log: JavaRandom.java,v $ +//Revision 1.3 2013/06/03 11:00:03 velktron +//Implements interface without logging. +// +//Revision 1.2 2011/07/27 20:47:46 velktron +//Proper commenting, cleanup. +// +//Revision 1.1 2011/05/29 22:15:32 velktron +//Introduced IRandom interface. diff --git a/doom/src/m/Menu.java b/doom/src/m/Menu.java new file mode 100644 index 0000000..928f965 --- /dev/null +++ b/doom/src/m/Menu.java @@ -0,0 +1,1875 @@ +package m; + +import static data.Defines.HU_FONTSIZE; +import static data.Defines.HU_FONTSTART; +import static data.Defines.PU_CACHE; +import static data.Defines.SAVESTRINGSIZE; +import static data.dstrings.NUM_QUITMESSAGES; +import static data.dstrings.SAVEGAMENAME; +import static data.dstrings.endmsg; +import data.sounds.sfxenum_t; +import defines.Language_t; +import defines.gamestate_t; +import defines.skill_t; +import doom.CommandVariable; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.M_Menu; +import static doom.SourceCode.M_Menu.M_Responder; +import static doom.SourceCode.M_Menu.M_StartControlPanel; +import static doom.SourceCode.M_Menu.M_Ticker; +import doom.englsh; +import static doom.englsh.DOSY; +import static doom.englsh.EMPTYSTRING; +import static doom.englsh.ENDGAME; +import static doom.englsh.GAMMALVL0; +import static doom.englsh.GAMMALVL1; +import static doom.englsh.GAMMALVL2; +import static doom.englsh.GAMMALVL3; +import static doom.englsh.GAMMALVL4; +import static doom.englsh.LOADNET; +import static doom.englsh.MSGOFF; +import static doom.englsh.MSGON; +import static doom.englsh.NETEND; +import static doom.englsh.NEWGAME; +import static doom.englsh.NIGHTMARE; +import static doom.englsh.QLOADNET; +import static doom.englsh.QLPROMPT; +import static doom.englsh.QSAVESPOT; +import static doom.englsh.QSPROMPT; +import static doom.englsh.SAVEDEAD; +import static doom.englsh.SWSTRING; +import doom.event_t; +import doom.evtype_t; +import g.Signals.ScanCode; +import static g.Signals.ScanCode.*; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.patch_t; +import timing.DelegateTicker; +import utils.C2JUtils; +import static v.renderers.DoomScreen.FG; +import w.DoomIO; + +public class Menu extends AbstractDoomMenu { + + private static final Logger LOGGER = Loggers.getLogger(Menu.class.getName()); + + ////////////////// CONSTRUCTOR //////////////// + public Menu(DoomMain DOOM) { + super(DOOM); + } + + /** The fonts ... must "peg" them to those from HU */ + patch_t[] hu_font = new patch_t[HU_FONTSIZE]; + + /** WTF?! */ + boolean message_dontfuckwithme; + + // int mouseSensitivity; // has default + /** Show messages has default, 0 = off, 1 = on */ + private boolean showMessages = false; + + /** + * showMessages can be read outside of Menu, but not modified. Menu has the + * actual C definition (not declaration) + */ + @Override + public boolean getShowMessages() { + return showMessages; + } + + @Override + public void setShowMessages(boolean val) { + this.showMessages = val; + } + + /** Blocky mode, has default, 0 = high, 1 = normal */ + int detailLevel; + + int screenblocks = 10; // has default + + /** temp for screenblocks (0-9) */ + int screenSize; + + /** -1 = no quicksave slot picked! */ + int quickSaveSlot; + + /** 1 = message to be printed */ + boolean messageToPrint; + + /** ...and here is the message string! */ + String messageString; + + /** message x & y */ + int messx, messy; + + boolean messageLastMenuActive; + + /** timed message = no input from user */ + boolean messageNeedsInput; + + /** Probably I need some MessageRoutine interface at this point? */ + public MenuRoutine messageRoutine; + + /** we are going to be entering a savegame string */ + boolean saveStringEnter; + + int saveSlot; // which slot to save in + + int saveCharIndex; // which char we're editing + + /** old save description before edit */ + char[] saveOldString = new char[SAVESTRINGSIZE]; + + boolean inhelpscreens; + + //int menuactive; + protected static final int SKULLXOFF = -32; + + protected static final int LINEHEIGHT = 16; + + char[][] savegamestrings = new char[10][SAVESTRINGSIZE]; + + String endstring = new String(); + + // + // MENU TYPEDEFS + // + /** menu item skull is on */ + short itemOn; + + /** skull animation counter */ + short skullAnimCounter; + + /** which skull to draw */ + short whichSkull; + + /** + * graphic name of skulls warning: initializer-string for array of chars is + * too long + */ + private static String[] skullName = {"M_SKULL1", "M_SKULL2"}; + + /** current menudef */ + // MAES: pointer? array? + menu_t currentMenu; + + // + // DOOM MENU + // + // MAES: was an enum called "main_e" used purely as numerals. No need for + // strong typing. + /** + * MenuRoutine class definitions, replacing "function pointers". + */ + MenuRoutine ChangeDetail, ChangeMessages, ChangeSensitivity, ChooseSkill, + EndGame, EndGameResponse, Episode, FinishReadThis, LoadGame, + LoadSelect, MusicVol, NewGame, Options, VerifyNightmare, + SaveSelect, SfxVol, SizeDisplay, SaveGame, Sound, QuitDOOM, + QuitResponse, QuickLoadResponse, QuickSaveResponse, ReadThis, ReadThis2; + + /** DrawRoutine class definitions, replacing "function pointers". */ + DrawRoutine DrawEpisode, DrawLoad, DrawMainMenu, DrawNewGame, DrawOptions, + DrawReadThis1, DrawReadThis2, DrawSave, DrawSound; + + /** Initialize menu routines first */ + private void initMenuRoutines() { + ChangeMessages = new M_ChangeMessages(); + ChangeDetail = new M_ChangeDetail(); + ChangeSensitivity = new M_ChangeSensitivity(); + ChooseSkill = new M_ChooseSkill(); + EndGame = new M_EndGame(); + EndGameResponse = new M_EndGameResponse(); + Episode = new M_Episode(); + FinishReadThis = new M_FinishReadThis(); + LoadGame = new M_LoadGame(); + LoadSelect = new M_LoadSelect(); + MusicVol = new M_MusicVol(); + NewGame = new M_NewGame(); + Options = new M_Options(); + + QuitDOOM = new M_QuitDOOM(); + QuickLoadResponse = new M_QuickLoadResponse(); + QuickSaveResponse = new M_QuickSaveResponse(); + QuitResponse = new M_QuitResponse(); + + ReadThis = new M_ReadThis(); + ReadThis2 = new M_ReadThis2(); + + SaveGame = new M_SaveGame(); + SaveSelect = new M_SaveSelect(); + SfxVol = new M_SfxVol(); + SizeDisplay = new M_SizeDisplay(); + Sound = new M_Sound(); + VerifyNightmare = new M_VerifyNightmare(); + } + + /** Then drawroutines */ + private void initDrawRoutines() { + DrawEpisode = new M_DrawEpisode(); + DrawNewGame = new M_DrawNewGame(); + DrawReadThis1 = new M_DrawReadThis1(); + DrawReadThis2 = new M_DrawReadThis2(); + DrawOptions = new M_DrawOptions(); + DrawLoad = new M_DrawLoad(); + DrawSave = new M_DrawSave(); + DrawSound = new M_DrawSound(); + DrawMainMenu = new M_DrawMainMenu(); + } + + /** Menuitem definitions. A "menu" can consist of multiple menuitems */ + menuitem_t[] MainMenu, EpisodeMenu, NewGameMenu, OptionsMenu, ReadMenu1, ReadMenu2, SoundMenu, LoadMenu, SaveMenu; + + /** Actual menus. Each can point to an array of menuitems */ + menu_t MainDef, EpiDef, NewDef, OptionsDef, ReadDef1, ReadDef2, SoundDef, LoadDef, SaveDef; + + /** First initialize those */ + private void initMenuItems() { + MainMenu = new menuitem_t[]{ + new menuitem_t(1, "M_NGAME", NewGame, SC_N), + new menuitem_t(1, "M_OPTION", Options, SC_O), + new menuitem_t(1, "M_LOADG", LoadGame, SC_L), + new menuitem_t(1, "M_SAVEG", SaveGame, SC_S), + // Another hickup with Special edition. + new menuitem_t(1, "M_RDTHIS", ReadThis, SC_R), + new menuitem_t(1, "M_QUITG", QuitDOOM, SC_Q) + }; + + MainDef = new menu_t(main_end, null, MainMenu, DrawMainMenu, 97, 64, 0); + + // + // EPISODE SELECT + // + EpisodeMenu = new menuitem_t[]{ + new menuitem_t(1, "M_EPI1", Episode, SC_K), + new menuitem_t(1, "M_EPI2", Episode, SC_T), + new menuitem_t(1, "M_EPI3", Episode, SC_I), + new menuitem_t(1, "M_EPI4", Episode, SC_T) + }; + + EpiDef = new menu_t( + ep_end, // # of menu items + MainDef, // previous menu + EpisodeMenu, // menuitem_t -> + DrawEpisode, // drawing routine -> + 48, 63, // x,y + ep1 // lastOn + ); + + // + // NEW GAME + // + NewGameMenu = new menuitem_t[]{ + new menuitem_t(1, "M_JKILL", ChooseSkill, SC_I), + new menuitem_t(1, "M_ROUGH", ChooseSkill, SC_H), + new menuitem_t(1, "M_HURT", ChooseSkill, SC_H), + new menuitem_t(1, "M_ULTRA", ChooseSkill, SC_U), + new menuitem_t(1, "M_NMARE", ChooseSkill, SC_N) + }; + + NewDef = new menu_t( + newg_end, // # of menu items + EpiDef, // previous menu + NewGameMenu, // menuitem_t -> + DrawNewGame, // drawing routine -> + 48, 63, // x,y + hurtme // lastOn + ); + + // + // OPTIONS MENU + // + OptionsMenu = new menuitem_t[]{ + new menuitem_t(1, "M_ENDGAM", EndGame, SC_3), + new menuitem_t(1, "M_MESSG", ChangeMessages, SC_M), + new menuitem_t(1, "M_DETAIL", ChangeDetail, SC_G), + new menuitem_t(2, "M_SCRNSZ", SizeDisplay, SC_S), + new menuitem_t(-1, "", null), + new menuitem_t(2, "M_MSENS", ChangeSensitivity, SC_M), + new menuitem_t(-1, "", null), + new menuitem_t(1, "M_SVOL", Sound, SC_S) + }; + + OptionsDef = new menu_t(opt_end, this.MainDef, OptionsMenu, DrawOptions, 60, 37, 0); + + // Read This! MENU 1 + ReadMenu1 = new menuitem_t[]{new menuitem_t(1, "", ReadThis2, SC_0)}; + + ReadDef1 = new menu_t(read1_end, MainDef, ReadMenu1, DrawReadThis1, 280, 185, 0); + + // Read This! MENU 2 + ReadMenu2 = new menuitem_t[]{new menuitem_t(1, "", FinishReadThis, SC_0)}; + + ReadDef2 = new menu_t(read2_end, ReadDef1, ReadMenu2, DrawReadThis2, 330, 175, 0); + + // + // SOUND VOLUME MENU + // + SoundMenu = new menuitem_t[]{ + new menuitem_t(2, "M_SFXVOL", SfxVol, SC_S), + new menuitem_t(-1, "", null), + new menuitem_t(2, "M_MUSVOL", MusicVol, SC_M), + new menuitem_t(-1, "", null) + }; + + SoundDef = new menu_t(sound_end, OptionsDef, SoundMenu, DrawSound, 80, 64, 0); + + // + // LOAD GAME MENU + // + LoadMenu = new menuitem_t[]{new menuitem_t(1, "", LoadSelect, SC_1), + new menuitem_t(1, "", LoadSelect, SC_2), + new menuitem_t(1, "", LoadSelect, SC_3), + new menuitem_t(1, "", LoadSelect, SC_4), + new menuitem_t(1, "", LoadSelect, SC_5), + new menuitem_t(1, "", LoadSelect, SC_6)}; + + LoadDef + = new menu_t(load_end, MainDef, LoadMenu, DrawLoad, 80, 54, 0); + + // + // SAVE GAME MENU + // + SaveMenu = new menuitem_t[]{ + new menuitem_t(1, "", SaveSelect, SC_1), + new menuitem_t(1, "", SaveSelect, SC_2), + new menuitem_t(1, "", SaveSelect, SC_3), + new menuitem_t(1, "", SaveSelect, SC_4), + new menuitem_t(1, "", SaveSelect, SC_5), + new menuitem_t(1, "", SaveSelect, SC_6) + }; + + SaveDef = new menu_t(load_end, MainDef, SaveMenu, DrawSave, 80, 54, 0); + } + + /** + * M_ReadSaveStrings + * read the strings from the savegame files + */ + public void ReadSaveStrings() { + DataInputStream handle; + int count; + int i; + String name; + + for (i = 0; i < load_end; i++) { + if (DOOM.cVarManager.bool(CommandVariable.CDROM)) { + name = "c:\\doomdata\\" + SAVEGAMENAME + (i) + ".dsg"; + } else { + name = SAVEGAMENAME + (i) + ".dsg"; + } + + try { + handle = new DataInputStream(new BufferedInputStream(new FileInputStream(name))); + savegamestrings[i] + = DoomIO.readString(handle, SAVESTRINGSIZE).toCharArray(); + handle.close(); + LoadMenu[i].status = 1; + } catch (IOException e) { + savegamestrings[i][0] = 0x00; + LoadMenu[i].status = 0; + continue; + } + + } + } + + /** + * Draw border for the savegame description. This is special in that it's + * not "invokable" like the other drawroutines, but standalone. + */ + private void DrawSaveLoadBorder(int x, int y) { + int i; + + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_LSLEFT"), DOOM.vs, x - 8, y + 7); + + for (i = 0; i < 24; i++) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_LSCNTR"), DOOM.vs, x, y + 7); + x += 8; + } + + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_LSRGHT"), DOOM.vs, x, y + 7); + } + + /** Draws slider rail of a specified width (each notch is 8 base units wide) + * and with a slider selector at position thermDot. + * + * @param x + * @param y + * @param thermWidth + * @param thermDot + */ + public void DrawThermo(int x, int y, int thermWidth, int thermDot) { + int xx = x; + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_THERML"), DOOM.vs, xx, y); + xx += 8; + for (int i = 0; i < thermWidth; i++) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_THERMM"), DOOM.vs, xx, y); + xx += 8; + } + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_THERMR"), DOOM.vs, xx, y); + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_THERMO"), DOOM.vs, (x + 8) + thermDot * 8, y); + } + + public void DrawEmptyCell(menu_t menu, int item) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CacheLumpName("M_CELL1", PU_CACHE, patch_t.class), DOOM.vs, menu.x - 10, menu.y + item * LINEHEIGHT - 1); + } + + public void DrawSelCell(menu_t menu, int item) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CacheLumpName("M_CELL2", PU_CACHE, patch_t.class), DOOM.vs, menu.x - 10, menu.y + item * LINEHEIGHT - 1); + } + + // + // M_SaveGame & Cie. + // + public class M_DrawSave implements DrawRoutine { + + @Override + public void invoke() { + int i; + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_SAVEG"), DOOM.vs, 72, 28); + for (i = 0; i < load_end; i++) { + DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); + WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, savegamestrings[i]); + } + + if (saveStringEnter) { + i = StringWidth(savegamestrings[saveSlot]); + WriteText(LoadDef.x + i, LoadDef.y + LINEHEIGHT * saveSlot, "_"); + } + } + } + + /** + * M_Responder calls this when user is finished + * + * @param slot + */ + public void DoSave(int slot) { + DOOM.SaveGame(slot, new String(savegamestrings[slot])); + ClearMenus(); + + // PICK QUICKSAVE SLOT YET? + if (quickSaveSlot == -2) { + quickSaveSlot = slot; + } + } + + /** + * User wants to save. Start string input for M_Responder + */ + class M_SaveSelect implements MenuRoutine { + + @Override + public void invoke(int choice) { + // we are going to be intercepting all chars + //System.out.println("ACCEPTING typing input"); + saveStringEnter = true; + + saveSlot = choice; + C2JUtils.strcpy(saveOldString, savegamestrings[choice]); + if (C2JUtils.strcmp(savegamestrings[choice], EMPTYSTRING)) { + savegamestrings[choice][0] = 0; + } + saveCharIndex = C2JUtils.strlen(savegamestrings[choice]); + } + } + + /** + * Selected from DOOM menu + */ + class M_SaveGame implements MenuRoutine { + + @Override + public void invoke(int choice) { + if (!DOOM.usergame) { + StartMessage(SAVEDEAD, null, false); + return; + } + + if (DOOM.gamestate != gamestate_t.GS_LEVEL) { + return; + } + + SetupNextMenu(SaveDef); + ReadSaveStrings(); + } + } + + // + // M_QuickSave + // + private String tempstring; + + class M_QuickSaveResponse implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch == 'y') { + DoSave(quickSaveSlot); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchx); + } + } + } + + private void QuickSave() { + if (!DOOM.usergame) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_oof); + return; + } + + if (DOOM.gamestate != gamestate_t.GS_LEVEL) { + return; + } + + if (quickSaveSlot < 0) { + StartControlPanel(); + ReadSaveStrings(); + SetupNextMenu(SaveDef); + quickSaveSlot = -2; // means to pick a slot now + return; + } + tempstring = String.format(QSPROMPT, C2JUtils.nullTerminatedString(savegamestrings[quickSaveSlot])); + StartMessage(tempstring, this.QuickSaveResponse, true); + } + + // + // M_QuickLoad + // + class M_QuickLoadResponse implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch == 'y') { + LoadSelect.invoke(quickSaveSlot); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchx); + } + } + } + + class M_QuitResponse implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch != 'y') { + return; + } + if (!DOOM.netgame) { + if (DOOM.isCommercial()) { + DOOM.doomSound.StartSound(null, quitsounds2[(DOOM.gametic >> 2) & 7]); + } else { + DOOM.doomSound.StartSound(null, quitsounds[(DOOM.gametic >> 2) & 7]); + } + // TI.WaitVBL(105); + } + DOOM.doomSystem.Quit(); + } + } + + public void QuickLoad() { + if (DOOM.netgame) { + StartMessage(QLOADNET, null, false); + return; + } + + if (quickSaveSlot < 0) { + StartMessage(QSAVESPOT, null, false); + return; + } + tempstring = String.format(QLPROMPT, C2JUtils.nullTerminatedString(savegamestrings[quickSaveSlot])); + StartMessage(tempstring, QuickLoadResponse, true); + } + + class M_Sound implements MenuRoutine { + + @Override + public void invoke(int choice) { + SetupNextMenu(SoundDef); + } + } + + class M_SfxVol implements MenuRoutine { + + @Override + public void invoke(int choice) { + switch (choice) { + case 0: + if (DOOM.snd_SfxVolume != 0) { + DOOM.snd_SfxVolume--; + } + break; + case 1: + if (DOOM.snd_SfxVolume < 15) { + DOOM.snd_SfxVolume++; + } + break; + } + + DOOM.doomSound.SetSfxVolume(DOOM.snd_SfxVolume * 8); + } + } + + class M_MusicVol implements MenuRoutine { + + @Override + public void invoke(int choice) { + switch (choice) { + case 0: + if (DOOM.snd_MusicVolume != 0) { + DOOM.snd_MusicVolume--; + } + break; + case 1: + if (DOOM.snd_MusicVolume < 15) { + DOOM.snd_MusicVolume++; + } + break; + } + + DOOM.doomSound.SetMusicVolume(DOOM.snd_MusicVolume * 8); + } + } + + // + // M_Episode + // + private int epi; + + class M_VerifyNightmare implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch != 'y') { + return; + } + + DOOM.DeferedInitNew(skill_t.sk_nightmare, epi + 1, 1); + ClearMenus(); + } + } + + /** + * M_ReadThis + */ + class M_ReadThis implements MenuRoutine { + + @Override + public void invoke(int choice) { + choice = 0; + SetupNextMenu(ReadDef1); + } + } + + class M_ReadThis2 implements MenuRoutine { + + @Override + public void invoke(int choice) { + choice = 0; + SetupNextMenu(ReadDef2); + } + } + + class M_FinishReadThis implements MenuRoutine { + + @Override + public void invoke(int choice) { + choice = 0; + SetupNextMenu(MainDef); + } + } + + // + // M_QuitDOOM + // + class M_QuitDOOM implements MenuRoutine { + + @Override + public void invoke(int choice) { + // We pick index 0 which is language sensitive, + // or one at random, between 1 and maximum number. + if (DOOM.language != Language_t.english) { + endstring = endmsg[0] + "\n\n" + DOSY; + } else { + endstring + = endmsg[(DOOM.gametic % (NUM_QUITMESSAGES - 2)) + 1] + "\n\n" + + DOSY; + } + StartMessage(endstring, QuitResponse, true); + } + } + + class M_QuitGame implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch != 'y') { + return; + } + if (!DOOM.netgame) { + if (DOOM.isCommercial()) { + DOOM.doomSound.StartSound(null, quitsounds2[(DOOM.gametic >> 2) & 7]); + } else { + DOOM.doomSound.StartSound(null, quitsounds[(DOOM.gametic >> 2) & 7]); + } + DOOM.doomSystem.WaitVBL(105); + } + DOOM.doomSystem.Quit(); + } + } + + class M_SizeDisplay implements MenuRoutine { + + @Override + public void invoke(int choice) { + switch (choice) { + case 0: + if (screenSize > 0) { + screenblocks--; + screenSize--; + } + break; + case 1: + if (screenSize < 8) { + screenblocks++; + screenSize++; + } + break; + } + + DOOM.sceneRenderer.SetViewSize(screenblocks, detailLevel); + } + + } + + class M_Options implements MenuRoutine { + + @Override + public void invoke(int choice) { + SetupNextMenu(OptionsDef); + } + + } + + class M_NewGame implements MenuRoutine { + + @Override + public void invoke(int choice) { + if (DOOM.netgame && !DOOM.demoplayback) { + StartMessage(NEWGAME, null, false); + return; + } + + if (DOOM.isCommercial()) { + SetupNextMenu(NewDef); + } else { + SetupNextMenu(EpiDef); + } + } + + } + + public void StartMessage(String string, MenuRoutine routine, boolean input) { + messageLastMenuActive = DOOM.menuactive; + messageToPrint = true; + messageString = string; + messageRoutine = routine; + messageNeedsInput = input; + DOOM.menuactive = true; // "true" + } + + public void StopMessage() { + DOOM.menuactive = messageLastMenuActive; + messageToPrint = false; + } + + /** + * Find string width from hu_font chars + */ + public int StringWidth(char[] string) { + int i; + int w = 0; + int c; + + for (i = 0; i < C2JUtils.strlen(string); i++) { + c = Character.toUpperCase(string[i]) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) { + w += 4; + } else { + w += hu_font[c].width; + } + } + + return w; + } + + /** + * Find string height from hu_font chars. + * + * Actually it just counts occurences of 'n' and adds height to height. + */ + private int StringHeight(char[] string) { + int i; + int h; + int height = hu_font[0].height; + + h = height; + for (i = 0; i < string.length; i++) { + if (string[i] == '\n') { + h += height; + } + } + + return h; + } + + /** + * Find string height from hu_font chars + */ + private int StringHeight(String string) { + return this.StringHeight(string.toCharArray()); + } + + /** + * Write a string using the hu_font + */ + private void WriteText(int x, int y, char[] string) { + int w; + char[] ch; + int c; + int cx; + int cy; + + ch = string; + int chptr = 0; + cx = x; + cy = y; + + while (chptr < ch.length) { + c = ch[chptr]; + chptr++; + if (c == 0) { + break; + } + if (c == '\n') { + cx = x; + cy += 12; + continue; + } + + c = Character.toUpperCase(c) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE) { + cx += 4; + continue; + } + + w = hu_font[c].width; + if (cx + w > DOOM.vs.getScreenWidth()) { + break; + } + + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, cx, cy); + cx += w; + } + + } + + private void WriteText(int x, int y, String string) { + if (string == null || string.length() == 0) { + return; + } + + int w; + int cx; + int cy; + + int chptr = 0; + char c; + + cx = x; + cy = y; + + while (chptr < string.length()) { + c = string.charAt(chptr++); + if (c == 0) { + break; + } + if (c == '\n') { + cx = x; + cy += 12; + continue; + } + + c = (char) (Character.toUpperCase(c) - HU_FONTSTART); + if (c < 0 || c >= HU_FONTSIZE) { + cx += 4; + continue; + } + + w = hu_font[c].width; + if (cx + w > DOOM.vs.getScreenWidth()) { + break; + } + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, cx, cy); + cx += w; + } + + } + + // These belong to the responder. + private int joywait = 0; + + private int mousewait = 0; + + private int mousey = 0; + + private int lasty = 0; + + private int mousex = 0; + + private int lastx = 0; + + @Override + @SourceCode.Compatible + @M_Menu.C(M_Responder) + public boolean Responder(event_t ev) { + final ScanCode sc; + + if (DOOM.use_joystick && ev.isType(evtype_t.ev_joystick) && joywait < DOOM.ticker.GetTime()) { + // Joystick input + sc = ev.mapByJoy(joyEvent -> { + ScanCode r = SC_NULL; + if (joyEvent.y == -1) { + r = SC_UP; + joywait = DOOM.ticker.GetTime() + 5; + } else if (joyEvent.y == 1) { + r = SC_DOWN; + joywait = DOOM.ticker.GetTime() + 5; + } + + if (joyEvent.x == -1) { + r = SC_LEFT; + joywait = DOOM.ticker.GetTime() + 2; + } else if (joyEvent.x == 1) { + r = SC_RIGHT; + joywait = DOOM.ticker.GetTime() + 2; + } + + if (joyEvent.isJoy(event_t.JOY_2)) { + r = SC_BACKSPACE; + joywait = DOOM.ticker.GetTime() + 5; + } else if (joyEvent.isJoy(event_t.JOY_1)) { + r = SC_ENTER; + joywait = DOOM.ticker.GetTime() + 5; + } + return r; + }); + } else if (DOOM.use_mouse && ev.isType(evtype_t.ev_mouse) && mousewait < DOOM.ticker.GetTime()) { + // Mouse input + if ((sc = ev.mapByMouse(mouseEvent -> { + ScanCode r = SC_NULL; + mousey += mouseEvent.y; + if (mousey < lasty - 30) { + r = SC_DOWN; + mousewait = DOOM.ticker.GetTime() + 5; + mousey = lasty -= 30; + } else if (mousey > lasty + 30) { + r = SC_UP; + mousewait = DOOM.ticker.GetTime() + 5; + mousey = lasty += 30; + } + + mousex += mouseEvent.x; + if (mousex < lastx - 30) { + r = SC_LEFT; + mousewait = DOOM.ticker.GetTime() + 5; + mousex = lastx -= 30; + } else if (mousex > lastx + 30) { + r = SC_RIGHT; + mousewait = DOOM.ticker.GetTime() + 5; + mousex = lastx += 30; + } + + if (mouseEvent.isMouse(event_t.MOUSE_RIGHT)) { + r = SC_BACKSPACE; + mousewait = DOOM.ticker.GetTime() + 15; + } else if (mouseEvent.isMouse(event_t.MOUSE_LEFT)) { + r = SC_ENTER; + mousewait = DOOM.ticker.GetTime() + 15; + } + return r; + })) == SC_NULL) { + return false; + } + } else if (ev.isType(evtype_t.ev_keydown)) { + sc = ev.getSC(); + } else { + return false; + } + + // Save Game string input + if (saveStringEnter) { + switch (sc) { + case SC_BACKSPACE: + if (saveCharIndex > 0) { + saveCharIndex--; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + case SC_ESCAPE: + saveStringEnter = false; + C2JUtils.strcpy(savegamestrings[saveSlot], saveOldString); + break; + case SC_ENTER: + saveStringEnter = false; + if (savegamestrings[saveSlot][0] != 0) { + DoSave(saveSlot); + } + break; + default: + char ch = Character.toUpperCase(sc.c); + if (ch != ' ') { + if (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE) { + break; + } + } + + if (ch >= ' ' && ch <= 0x7F && saveCharIndex < SAVESTRINGSIZE - 1 + && StringWidth(savegamestrings[saveSlot]) < (SAVESTRINGSIZE - 2) * 8) { + savegamestrings[saveSlot][saveCharIndex++] = ch; + savegamestrings[saveSlot][saveCharIndex] = 0; + } + break; + } + return true; + } + + // Take care of any messages that need input + if (messageToPrint) { + if (messageNeedsInput == true && !(sc == SC_SPACE || sc == SC_N || sc == SC_Y || sc == SC_ESCAPE)) { + return false; + } + + DOOM.menuactive = messageLastMenuActive; + messageToPrint = false; + if (messageRoutine != null) { + messageRoutine.invoke(sc.c); + } + + DOOM.menuactive = false; // "false" + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchx); + return true; + } + + if ((DOOM.devparm && sc == SC_F1) || sc == SC_PRTSCRN) { + DOOM.ScreenShot(); + return true; + } + + // F-Keys + if (!DOOM.menuactive) { + switch (sc) { + case SC_MINUS: // Screen size down + if (DOOM.automapactive || DOOM.headsUp.chat_on[0]) { + return false; + } + SizeDisplay.invoke(0); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_stnmov); + return true; + + case SC_EQUALS: // Screen size up + if (DOOM.automapactive || DOOM.headsUp.chat_on[0]) { + return false; + } + SizeDisplay.invoke(1); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_stnmov); + return true; + + case SC_F1: // Help key + StartControlPanel(); + + if (DOOM.isRegistered() || DOOM.isShareware()) { + currentMenu = ReadDef2; + } else { + currentMenu = ReadDef1; + } + itemOn = 0; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + return true; + + case SC_F2: // Save + StartControlPanel(); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + SaveGame.invoke(0); + return true; + + case SC_F3: // Load + StartControlPanel(); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + LoadGame.invoke(0); + return true; + + case SC_F4: // Sound Volume + StartControlPanel(); + currentMenu = SoundDef; + itemOn = (short) sfx_vol; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + return true; + + case SC_F5: // Detail toggle + ChangeDetail.invoke(0); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + return true; + + case SC_F6: // Quicksave + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + QuickSave(); + return true; + + case SC_F7: // End game + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + EndGame.invoke(0); + return true; + + case SC_F8: // Toggle messages + ChangeMessages.invoke(0); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + return true; + + case SC_F9: // Quickload + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + QuickLoad(); + return true; + + case SC_F10: // Quit DOOM + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + QuitDOOM.invoke(0); + return true; + + case SC_F11: // gamma toggle + int usegamma = DOOM.graphicSystem.getUsegamma(); + usegamma++; + if (usegamma > 4) { + usegamma = 0; + } + DOOM.players[DOOM.consoleplayer].message = gammamsg[usegamma]; + DOOM.graphicSystem.setUsegamma(usegamma); + DOOM.autoMap.Repalette(); + return true; + + default: + break; + + } + } else if (sc == SC_F5 && DOOM.ticker instanceof DelegateTicker) { // Toggle ticker + ((DelegateTicker) DOOM.ticker).changeTicker(); + LOGGER.log(Level.WARNING, "Warning! Ticker changed; time reset"); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_radio); + return true; + } + + // Pop-up menu? + if (!DOOM.menuactive) { + if (sc == SC_ESCAPE) { + StartControlPanel(); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + return true; + } + return false; + } + + // Keys usable within menu + switch (sc) { + case SC_DOWN: + do { + if (itemOn + 1 > currentMenu.numitems - 1) { + itemOn = 0; + } else { + itemOn++; + } + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pstop); + } while (currentMenu.menuitems[itemOn].status == -1); + return true; + + case SC_UP: + do { + if (itemOn == 0) { + itemOn = (short) (currentMenu.numitems - 1); + } else { + itemOn--; + } + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pstop); + } while (currentMenu.menuitems[itemOn].status == -1); + return true; + + case SC_LEFT: + if ((currentMenu.menuitems[itemOn].routine != null) + && (currentMenu.menuitems[itemOn].status == 2)) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_stnmov); + currentMenu.menuitems[itemOn].routine.invoke(0); + } + return true; + + case SC_RIGHT: + if ((currentMenu.menuitems[itemOn].routine != null) + && (currentMenu.menuitems[itemOn].status == 2)) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_stnmov); + currentMenu.menuitems[itemOn].routine.invoke(1); + } + return true; + + case SC_NPENTER: + case SC_ENTER: { + if ((currentMenu.menuitems[itemOn].routine != null) && currentMenu.menuitems[itemOn].status != 0) { + currentMenu.lastOn = itemOn; + if (currentMenu.menuitems[itemOn].status == 2) { + currentMenu.menuitems[itemOn].routine.invoke(1); // right + // arrow + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_stnmov); + } else { + currentMenu.menuitems[itemOn].routine.invoke(itemOn); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pistol); + } + } + return true; + } + + case SC_ESCAPE: + currentMenu.lastOn = itemOn; + ClearMenus(); + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchx); + return true; + + case SC_BACKSPACE: + currentMenu.lastOn = itemOn; + if (currentMenu.prevMenu != null) { + currentMenu = currentMenu.prevMenu; + itemOn = (short) currentMenu.lastOn; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_swtchn); + } + return true; + + default: + for (int i = itemOn + 1; i < currentMenu.numitems; i++) { + if (currentMenu.menuitems[i].alphaKey == sc) { + itemOn = (short) i; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pstop); + return true; + } + } + for (int i = 0; i <= itemOn; i++) { + if (currentMenu.menuitems[i].alphaKey == sc) { + itemOn = (short) i; + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_pstop); + return true; + } + } + break; + + } + + return false; + } + + /** + * M_StartControlPanel + */ + @Override + @SourceCode.Exact + @M_Menu.C(M_StartControlPanel) + public void StartControlPanel() { + // intro might call this repeatedly + if (DOOM.menuactive) { + return; + } + + DOOM.menuactive = true; + currentMenu = MainDef; // JDC + itemOn = (short) currentMenu.lastOn; // JDC + } + + /** + * M_Drawer Called after the view has been rendered, but before it has been + * blitted. + */ + public void Drawer() { + + int x; + int y; + int max; + char[] string = new char[40]; + char[] msstring; + int start; + inhelpscreens = false; // Horiz. & Vertically center string and print + // it. + if (messageToPrint) { + start = 0; + y = 100 - this.StringHeight(messageString) / 2; + msstring = messageString.toCharArray(); + while (start < messageString.length()) { + int i = 0; + for (i = 0; i < messageString.length() - start; i++) { + if (msstring[start + i] == '\n') { + C2JUtils.memset(string, (char) 0, 40); + C2JUtils.strcpy(string, msstring, start, i); + start += i + 1; + break; + } + } + + if (i == (messageString.length() - start)) { + C2JUtils.strcpy(string, msstring, start); + start += i; + } + x = 160 - this.StringWidth(string) / 2; + this.WriteText(x, y, string); + y += hu_font[0].height; + } + return; + } + if (!DOOM.menuactive) { + return; + } + if (currentMenu.routine != null) { + currentMenu.routine.invoke(); // call Draw routine + } + // DRAW MENU + x = currentMenu.x; + y = currentMenu.y; + max = currentMenu.numitems; + for (int i = 0; i < max; i++) { + if (currentMenu.menuitems[i].name != null && !"".equals(currentMenu.menuitems[i].name)) { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName( + currentMenu.menuitems[i].name, PU_CACHE), DOOM.vs, x, y); + } + y += LINEHEIGHT; + } + + // DRAW SKULL + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(skullName[whichSkull], + PU_CACHE), DOOM.vs, x + SKULLXOFF, currentMenu.y - 5 + itemOn + * LINEHEIGHT); + } + + // + // M_ClearMenus + // + public void ClearMenus() { + DOOM.menuactive = false; + //Engine.getEngine().window.setMouseCaptured(); + DOOM.graphicSystem.forcePalette(); + + // MAES: was commented out :-/ + //if (!DM.netgame && DM.usergame && DM.paused) + // DM.setPaused(true); + } + + /** + * M_SetupNextMenu + */ + public void SetupNextMenu(menu_t menudef) { + currentMenu = menudef; + itemOn = (short) currentMenu.lastOn; + } + + /** + * M_Ticker + */ + @Override + @SourceCode.Exact + @M_Menu.C(M_Ticker) + public void Ticker() { + if (--skullAnimCounter <= 0) { + whichSkull ^= 1; + skullAnimCounter = 8; + } + } + + /** + * M_Init + */ + public void Init() { + + // Init menus. + this.initMenuRoutines(); + this.initDrawRoutines(); + this.initMenuItems(); + this.hu_font = DOOM.headsUp.getHUFonts(); + + currentMenu = MainDef; + DOOM.menuactive = false; + itemOn = (short) currentMenu.lastOn; + whichSkull = 0; + skullAnimCounter = 10; + screenSize = screenblocks - 3; + messageToPrint = false; + messageString = null; + messageLastMenuActive = DOOM.menuactive; + quickSaveSlot = -1; + + // Here we could catch other version dependencies, + // like HELP1/2, and four episodes. + switch (DOOM.getGameMode()) { + case freedm: + case freedoom2: + case commercial: + case pack_plut: + case pack_tnt: + // This is used because DOOM 2 had only one HELP + // page. I use CREDIT as second page now, but + // kept this hack for educational purposes. + MainMenu[readthis] = MainMenu[quitdoom]; + MainDef.numitems--; + MainDef.y += 8; + NewDef.prevMenu = MainDef; + ReadDef1.routine = DrawReadThis1; + ReadDef1.x = 330; + ReadDef1.y = 165; + ReadMenu1[0].routine = FinishReadThis; + break; + case shareware: + // Episode 2 and 3 are handled, + // branching to an ad screen. + // We need to remove the fourth episode. + case registered: + EpiDef.numitems--; + break; + case freedoom1: + case retail: + // We are fine. + default: + break; + } + + } + + /** + * M_DrawText Returns the final X coordinate HU_Init must have been called + * to init the font. Unused? + * + * @param x + * @param y + * @param direct + * @param string + * @return + */ + public int DrawText(int x, int y, boolean direct, String string) { + int c; + int w; + int ptr = 0; + + while ((c = string.charAt(ptr)) > 0) { + c = Character.toUpperCase(c) - HU_FONTSTART; + ptr++; + if (c < 0 || c > HU_FONTSIZE) { + x += 4; + continue; + } + + w = hu_font[c].width; + if (x + w > DOOM.vs.getScreenWidth()) { + break; + } + if (direct) { + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, x, y); + } else { + DOOM.graphicSystem.DrawPatchScaled(FG, hu_font[c], DOOM.vs, x, y); + } + x += w; + } + + return x; + } + + // ////////////////////////// DRAWROUTINES + // ////////////////////////////////// + class M_DrawEpisode + implements DrawRoutine { + + @Override + public void invoke() { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_EPISOD"), DOOM.vs, 54, 38); + } + + } + + /** + * M_LoadGame & Cie. + */ + class M_DrawLoad + implements DrawRoutine { + + @Override + public void invoke() { + int i; + + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_LOADG"), DOOM.vs, 72, 28); + for (i = 0; i < load_end; i++) { + DrawSaveLoadBorder(LoadDef.x, LoadDef.y + LINEHEIGHT * i); + WriteText(LoadDef.x, LoadDef.y + LINEHEIGHT * i, + savegamestrings[i]); + } + + } + } + + class M_DrawMainMenu + implements DrawRoutine { + + @Override + public void invoke() { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_DOOM"), DOOM.vs, 94, 2); + } + } + + class M_DrawNewGame + implements DrawRoutine { + + @Override + public void invoke() { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_NEWG"), DOOM.vs, 96, 14); + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_SKILL"), DOOM.vs, 54, 38); + } + } + + class M_DrawOptions + implements DrawRoutine { + + private final String detailNames[] = {"M_GDHIGH", "M_GDLOW"}; + private final String msgNames[] = {"M_MSGOFF", "M_MSGON"}; + + @Override + public void invoke() { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName("M_OPTTTL"), DOOM.vs, 108, 15); + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(detailNames[detailLevel]), DOOM.vs, OptionsDef.x + 175, OptionsDef.y + LINEHEIGHT * detail); + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(msgNames[showMessages ? 1 : 0]), DOOM.vs, OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages); + + DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT + * (mousesens + 1), 10, DOOM.mouseSensitivity); + + DrawThermo(OptionsDef.x, + OptionsDef.y + LINEHEIGHT * (scrnsize + 1), 9, screenSize); + + } + + } + + /** + * Read This Menus + * Had a "quick hack to fix romero bug" + */ + class M_DrawReadThis1 implements DrawRoutine { + + @Override + public void invoke() { + String lumpname; + int skullx, skully; + inhelpscreens = true; + + switch (DOOM.getGameMode()) { + case commercial: + case freedm: + case freedoom2: + case pack_plut: + case pack_tnt: + skullx = 330; + skully = 165; + lumpname = "HELP"; + break; + case shareware: + lumpname = "HELP2"; + skullx = 280; + skully = 185; + break; + default: + lumpname = "CREDIT"; + skullx = 330; + skully = 165; + break; + } + + ReadDef1.x = skullx; + ReadDef1.y = skully; + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(lumpname), DOOM.vs, 0, 0); + // Maes: we need to do this here, otherwide the status bar appears "dirty" + DOOM.statusBar.forceRefresh(); + } + } + + /** + * Read This Menus - optional second page. + */ + class M_DrawReadThis2 implements DrawRoutine { + + @Override + public void invoke() { + String lumpname; + int skullx, skully; + inhelpscreens = true; + + lumpname = "HELP1"; + skullx = 330; + skully = 175; + + ReadDef2.x = skullx; + ReadDef2.y = skully; + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CachePatchName(lumpname), DOOM.vs, 0, 0); + // Maes: we need to do this here, otherwide the status bar appears "dirty" + DOOM.statusBar.forceRefresh(); + } + } + + /** + * Change Sfx & Music volumes + */ + class M_DrawSound + implements DrawRoutine { + + public void invoke() { + DOOM.graphicSystem.DrawPatchScaled(FG, DOOM.wadLoader.CacheLumpName("M_SVOL", PU_CACHE, patch_t.class), DOOM.vs, 60, 38); + + DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (sfx_vol + 1), 16, + DOOM.snd_SfxVolume); + + DrawThermo(SoundDef.x, SoundDef.y + LINEHEIGHT * (music_vol + 1), + 16, DOOM.snd_MusicVolume); + } + } + + // /////////////////////////// MENU ROUTINES + // /////////////////////////////////// + class M_ChangeDetail implements MenuRoutine { + + @Override + public void invoke(int choice) { + choice = 0; + detailLevel = 1 - detailLevel; + + // FIXME - does not work. Remove anyway? + //System.err.print("M_ChangeDetail: low detail mode n.a.\n"); + //return; + DOOM.sceneRenderer.SetViewSize(screenblocks, detailLevel); + if (detailLevel == 0) { + DOOM.players[DOOM.consoleplayer].message = englsh.DETAILHI; + } else { + DOOM.players[DOOM.consoleplayer].message = englsh.DETAILLO; + } + + } + } + + /** + * Toggle messages on/off + */ + class M_ChangeMessages implements MenuRoutine { + + @Override + public void invoke(int choice) { + // warning: unused parameter `int choice' + //choice = 0; + showMessages = !showMessages; + + if (!showMessages) { + DOOM.players[DOOM.consoleplayer].message = MSGOFF; + } else { + DOOM.players[DOOM.consoleplayer].message = MSGON; + } + + message_dontfuckwithme = true; + } + } + + class M_ChangeSensitivity implements MenuRoutine { + + @Override + public void invoke(int choice) { + switch (choice) { + case 0: + if (DOOM.mouseSensitivity != 0) { + DOOM.mouseSensitivity--; + } + break; + case 1: + if (DOOM.mouseSensitivity < 9) { + DOOM.mouseSensitivity++; + } + break; + } + } + } + + class M_ChooseSkill implements MenuRoutine { + + @Override + public void invoke(int choice) { + if (choice == nightmare) { + StartMessage(NIGHTMARE, VerifyNightmare, true); + return; + } + + DOOM.DeferedInitNew(skill_t.values()[choice], epi + 1, 1); + ClearMenus(); + } + + } + + /** + * M_EndGame + */ + class M_EndGame implements MenuRoutine { + + @Override + public void invoke(int choice) { + choice = 0; + if (!DOOM.usergame) { + DOOM.doomSound.StartSound(null, sfxenum_t.sfx_oof); + return; + } + + if (DOOM.netgame) { + StartMessage(NETEND, null, false); + return; + } + + StartMessage(ENDGAME, EndGameResponse, true); + } + } + + class M_EndGameResponse implements MenuRoutine { + + @Override + public void invoke(int ch) { + if (ch != 'y') { + return; + } + + currentMenu.lastOn = itemOn; + ClearMenus(); + DOOM.StartTitle(); + } + } + + class M_Episode implements MenuRoutine { + + @Override + public void invoke(int choice) { + + if (DOOM.isShareware() && (choice != 0)) { + StartMessage(SWSTRING, null, false); + SetupNextMenu(ReadDef2); + return; + } + + // Yet another hack... + if (!DOOM.isRetail() && (choice > 2)) { + LOGGER.log(Level.WARNING, "M_Episode: 4th episode requires UltimateDOOM"); + choice = 0; + } + + epi = choice; + SetupNextMenu(NewDef); + } + + } + + /** + * User wants to load this game + */ + class M_LoadSelect implements MenuRoutine { + + @Override + public void invoke(int choice) { + String name; + + if (DOOM.cVarManager.bool(CommandVariable.CDROM)) { + name = ("c:\\doomdata\\" + SAVEGAMENAME + (choice) + ".dsg"); + } else { + name = (SAVEGAMENAME + (choice) + ".dsg"); + } + DOOM.LoadGame(name); + ClearMenus(); + } + } + + /** + * Selected from DOOM menu + */ + class M_LoadGame implements MenuRoutine { + + @Override + public void invoke(int choice) { + + if (DOOM.netgame) { + StartMessage(LOADNET, null, false); + return; + } + + SetupNextMenu(LoadDef); + ReadSaveStrings(); + } + } + + // ////////////////////// VARIOUS CONSTS ////////////////////// + private static final sfxenum_t[] quitsounds + = {sfxenum_t.sfx_pldeth, sfxenum_t.sfx_dmpain, sfxenum_t.sfx_popain, + sfxenum_t.sfx_slop, sfxenum_t.sfx_telept, sfxenum_t.sfx_posit1, + sfxenum_t.sfx_posit3, sfxenum_t.sfx_sgtatk}; + + private static final sfxenum_t[] quitsounds2 + = {sfxenum_t.sfx_vilact, sfxenum_t.sfx_getpow, sfxenum_t.sfx_boscub, + sfxenum_t.sfx_slop, sfxenum_t.sfx_skeswg, sfxenum_t.sfx_kntdth, + sfxenum_t.sfx_bspact, sfxenum_t.sfx_sgtatk}; + + /** episodes_e enum */ + private static final int ep1 = 0, ep2 = 1, ep3 = 2, ep4 = 3, ep_end = 4; + + /** load_e enum */ + private static final int load1 = 0, load2 = 1, load3 = 2, load4 = 3, load5 = 4, + load6 = 5, load_end = 6; + + /** options_e enum; */ + private static final int endgame = 0, messages = 1, detail = 2, scrnsize = 3, + option_empty1 = 4, mousesens = 5, option_empty2 = 6, soundvol = 7, + opt_end = 8; + + /** main_e enum; */ + private static final int newgame = 0, options = 1, loadgam = 2, savegame = 3, + readthis = 4, quitdoom = 5, main_end = 6; + + /** read_e enum */ + private static final int rdthsempty1 = 0, read1_end = 1; + + /** read_2 enum */ + private static final int rdthsempty2 = 0, read2_end = 1; + + /** newgame_e enum;*/ + public static final int killthings = 0, toorough = 1, hurtme = 2, violence = 3, + nightmare = 4, newg_end = 5; + + private static final String[] gammamsg = {GAMMALVL0, + GAMMALVL1, GAMMALVL2, GAMMALVL3, GAMMALVL4}; + + /** sound_e enum */ + static final int sfx_vol = 0, sfx_empty1 = 1, music_vol = 2, sfx_empty2 = 3, + sound_end = 4; + + @Override + public void setScreenBlocks(int val) { + this.screenblocks = val; + } + + @Override + public int getScreenBlocks() { + return this.screenblocks; + } + + @Override + public int getDetailLevel() { + return this.detailLevel; + } +} \ No newline at end of file diff --git a/doom/src/m/MenuMisc.java b/doom/src/m/MenuMisc.java new file mode 100644 index 0000000..9998c35 --- /dev/null +++ b/doom/src/m/MenuMisc.java @@ -0,0 +1,298 @@ +package m; + +import i.DoomSystem; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; +import java.awt.image.IndexColorModel; +import java.io.BufferedInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import mochadoom.Loggers; +import w.IWritableDoomObject; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: MenuMisc.java,v 1.29 2012/09/24 17:16:22 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// Main loop menu stuff. +// Default Config File. +// PCX Screenshots. +// +//----------------------------------------------------------------------------- +public abstract class MenuMisc { + + private static final Logger LOGGER = Loggers.getLogger(MenuMisc.class.getName()); + + public static final String rcsid = "$Id: MenuMisc.java,v 1.29 2012/09/24 17:16:22 velktron Exp $"; + + // + // SCREEN SHOTS + // + public static boolean WriteFile(String name, byte[] source, int length) { + OutputStream handle; + try { + handle = new FileOutputStream(name); + handle.write(source, 0, length); + handle.close(); + } catch (Exception e) { + DoomSystem.MiscError("Couldn't write file %s (%s)", name, e.getMessage()); + return false; + } + + return true; + } + + public static boolean WriteFile(String name, IWritableDoomObject source) { + DataOutputStream handle; + try { + handle = new DataOutputStream(new FileOutputStream(name)); + source.write(handle); + handle.close(); + } catch (Exception e) { + DoomSystem.MiscError("Couldn't write file %s (%s)", name, e.getMessage()); + return false; + } + + return true; + } + + /** M_ReadFile + * This version returns a variable-size ByteBuffer, so + * we don't need to know a-priori how much stuff to read. + * + */ + public static ByteBuffer ReadFile(String name) { + BufferedInputStream handle; + int length; + // struct stat fileinfo; + ByteBuffer buf; + try { + handle = new BufferedInputStream(new FileInputStream(name)); + length = (int) handle.available(); + buf = ByteBuffer.allocate(length); + handle.read(buf.array()); + handle.close(); + } catch (Exception e) { + DoomSystem.MiscError("Couldn't read file %s (%s)", name, e.getMessage()); + return null; + } + + return buf; + } + + /** M_ReadFile */ + public static int ReadFile(String name, byte[] buffer) { + BufferedInputStream handle; + int count, length; + // struct stat fileinfo; + byte[] buf; + try { + handle = new BufferedInputStream(new FileInputStream(name)); + length = (int) handle.available(); + buf = new byte[length]; + count = handle.read(buf); + handle.close(); + + if (count < length) { + throw new Exception("Read only " + count + " bytes out of " + + length); + } + + } catch (Exception e) { + DoomSystem.MiscError("Couldn't read file %s (%s)", name, e.getMessage()); + return -1; + } + System.arraycopy(buf, 0, buffer, 0, Math.min(count, buffer.length)); + return length; + } + + // + // WritePCXfile + // + public static void + WritePCXfile(String filename, + byte[] data, + int width, + int height, + byte[] palette) { + int length; + pcx_t pcx; + byte[] pack; + + pcx = new pcx_t(); + pack = new byte[width * height * 2]; // allocate that much data, just in case. + + pcx.manufacturer = 0x0a; // PCX id + pcx.version = 5; // 256 color + pcx.encoding = 1; // uncompressed + pcx.bits_per_pixel = 8; // 256 color + pcx.xmin = 0; + pcx.ymin = 0; + pcx.xmax = (char) (width - 1); + pcx.ymax = (char) (height - 1); + pcx.hres = (char) width; + pcx.vres = (char) height; + // memset (pcx->palette,0,sizeof(pcx->palette)); + pcx.color_planes = 1; // chunky image + pcx.bytes_per_line = (char) width; + pcx.palette_type = 2; // not a grey scale + //memset (pcx->filler,0,sizeof(pcx->filler)); + + // pack the image + //pack = &pcx->data; + int p_pack = 0; + + for (int i = 0; i < width * height; i++) { + if ((data[i] & 0xc0) != 0xc0) { + pack[p_pack++] = data[i]; + } else { + pack[p_pack++] = (byte) 0xc1; + pack[p_pack++] = data[i]; + } + } + + // write the palette + pack[p_pack++] = 0x0c; // palette ID byte + for (int i = 0; i < 768; i++) { + pack[p_pack++] = palette[i]; + } + + // write output file + length = p_pack; + pcx.data = Arrays.copyOf(pack, length); + + DataOutputStream f = null; + try { + f = new DataOutputStream(new FileOutputStream(filename)); + + } catch (FileNotFoundException e) { + LOGGER.log(Level.SEVERE, "WritePCXfile: not found", e); + } + + try { + //f.setLength(0); + pcx.write(f); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "WritePCXfile: I/O error", e); + } + + } + + public abstract boolean getShowMessages(); + + public abstract void setShowMessages(boolean val); + + public static void WritePNGfile(String imagename, short[] linear, int width, int height) { + BufferedImage buf = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_555_RGB); + DataBufferUShort sh = (DataBufferUShort) buf.getRaster().getDataBuffer(); + short[] shd = sh.getData(); + System.arraycopy(linear, 0, shd, 0, Math.min(linear.length, shd.length)); + try { + ImageIO.write(buf, "PNG", new File(imagename)); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "WritePNGfile: I/O error", e); + } + } + + public static void WritePNGfile(String imagename, int[] linear, int width, int height) { + BufferedImage buf = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + DataBufferInt sh = (DataBufferInt) buf.getRaster().getDataBuffer(); + int[] shd = sh.getData(); + System.arraycopy(linear, 0, shd, 0, Math.min(linear.length, shd.length)); + try { + ImageIO.write(buf, "PNG", new File(imagename)); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "WritePNGfile: I/O error", e); + } + } + + public static void WritePNGfile(String imagename, byte[] linear, int width, int height, IndexColorModel icm) { + BufferedImage buf = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, icm); + DataBufferByte sh = (DataBufferByte) buf.getRaster().getDataBuffer(); + byte[] shd = sh.getData(); + System.arraycopy(linear, 0, shd, 0, Math.min(linear.length, shd.length)); + try { + ImageIO.write(buf, "PNG", new File(imagename)); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "WritePNGfile: I/O error", e); + } + } +} + +// $Log: MenuMisc.java,v $ +// Revision 1.29 2012/09/24 17:16:22 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.28.2.4 2012/09/24 16:57:43 velktron +// Addressed generics warnings. +// +// Revision 1.28.2.3 2012/09/17 15:58:58 velktron +// Defaults loading & handling moved out to variables management subsystem +// +// Revision 1.28.2.2 2011/11/18 21:37:59 velktron +// Saves PNGs now. +// +// Revision 1.28.2.1 2011/11/14 00:27:11 velktron +// A barely functional HiColor branch. Most stuff broken. DO NOT USE +// +// Revision 1.28 2011/10/25 19:52:03 velktron +// Using buffered I/O when possible +// +// Revision 1.27 2011/10/24 02:11:27 velktron +// Stream compliancy +// +// Revision 1.26 2011/07/30 22:04:30 velktron +// Removed unused imports (including one that would cause problems compiling with OpenJDK). +// +// Revision 1.25 2011/07/15 13:53:52 velktron +// Implemented WritePCXFile, at last. +// +// Revision 1.24 2011/06/03 16:37:09 velktron +// Readfile will only read at most as much as the buffer allows. +// +// Revision 1.23 2011/05/31 13:33:54 velktron +// -verbosity +// +// Revision 1.22 2011/05/31 09:57:45 velktron +// Fixed broken parsing of unspaced strings. +// It's never fun having to come up with your own function for string manipulation! +// +// Revision 1.21 2011/05/30 15:46:50 velktron +// AbstractDoomMenu implemented. +// +// Revision 1.20 2011/05/26 17:54:16 velktron +// Removed some Menu verbosity, better defaults functionality. +// +// Revision 1.19 2011/05/26 13:39:15 velktron +// Now using ICommandLineManager +// +// Revision 1.18 2011/05/24 17:46:03 velktron +// Added vanilla default.cfg loading. +// \ No newline at end of file diff --git a/doom/src/m/MenuRoutine.java b/doom/src/m/MenuRoutine.java new file mode 100644 index 0000000..386bf9a --- /dev/null +++ b/doom/src/m/MenuRoutine.java @@ -0,0 +1,13 @@ +package m; + +/** menuitem_t required a function pointer to a (routine)(int choice) + * So any class implementing them will implement this interface, and + * we can have a single class type for all of them. + * + * @author Velktron + * + */ +public interface MenuRoutine { + + public void invoke(int choice); +} \ No newline at end of file diff --git a/doom/src/m/Settings.java b/doom/src/m/Settings.java new file mode 100644 index 0000000..1f0f1cf --- /dev/null +++ b/doom/src/m/Settings.java @@ -0,0 +1,243 @@ +/*----------------------------------------------------------------------------- +// +// Copyright (C) 1993-1996 Id Software, Inc. +// Copyright (C) 2017 Good Sign +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// From m_misc.c +//-----------------------------------------------------------------------------*/ +package m; + +import awt.FullscreenOptions; +import static doom.ConfigBase.FILE_DOOM; +import static doom.ConfigBase.FILE_MOCHADOOM; +import doom.ConfigBase.Files; +import doom.ConfigManager; +import static doom.englsh.HUSTR_CHATMACRO0; +import static doom.englsh.HUSTR_CHATMACRO1; +import static doom.englsh.HUSTR_CHATMACRO2; +import static doom.englsh.HUSTR_CHATMACRO3; +import static doom.englsh.HUSTR_CHATMACRO4; +import static doom.englsh.HUSTR_CHATMACRO5; +import static doom.englsh.HUSTR_CHATMACRO6; +import static doom.englsh.HUSTR_CHATMACRO7; +import static doom.englsh.HUSTR_CHATMACRO8; +import static doom.englsh.HUSTR_CHATMACRO9; +import static g.Signals.ScanCode.SC_COMMA; +import static g.Signals.ScanCode.SC_LALT; +import static g.Signals.ScanCode.SC_LCTRL; +import static g.Signals.ScanCode.SC_NUMKEY2; +import static g.Signals.ScanCode.SC_NUMKEY4; +import static g.Signals.ScanCode.SC_NUMKEY6; +import static g.Signals.ScanCode.SC_NUMKEY8; +import static g.Signals.ScanCode.SC_PERIOD; +import static g.Signals.ScanCode.SC_RSHIFT; +import static g.Signals.ScanCode.SC_SPACE; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import mochadoom.Engine; +import utils.QuoteType; +import v.graphics.Plotter; +import v.renderers.BppMode; +import v.renderers.SceneRendererMode; +import v.tables.GreyscaleFilter; + +/** + * An enumeration with the most basic default Doom settings their default values, used if nothing else is available. + * They are applied first thing, and then updated from the .cfg file. + * + * The file now also contains settings on many features introduced by this new version of Mocha Doom + * - Good Sign 2017/04/11 + * + * TODO: find a trick to separate settings groups in the same file vanilla-compatibly + */ +public enum Settings { + /** + * Defaults (default.cfg) defined in vanilla format, ordered in vanilla order + */ + mouse_sensitivity(FILE_DOOM, 5), + sfx_volume(FILE_DOOM, 8), + music_volume(FILE_DOOM, 8), + show_messages(FILE_DOOM, 1), + key_right(FILE_DOOM, SC_NUMKEY6.ordinal()), + key_left(FILE_DOOM, SC_NUMKEY4.ordinal()), + key_up(FILE_DOOM, SC_NUMKEY8.ordinal()), + key_down(FILE_DOOM, SC_NUMKEY2.ordinal()), + key_strafeleft(FILE_DOOM, SC_COMMA.ordinal()), + key_straferight(FILE_DOOM, SC_PERIOD.ordinal()), + key_fire(FILE_DOOM, SC_LCTRL.ordinal()), + key_use(FILE_DOOM, SC_SPACE.ordinal()), + key_strafe(FILE_DOOM, SC_LALT.ordinal()), + key_speed(FILE_DOOM, SC_RSHIFT.ordinal()), + use_mouse(FILE_DOOM, 1), + mouseb_fire(FILE_DOOM, 0), + mouseb_strafe(FILE_DOOM, 1), // AX: Fixed + mouseb_forward(FILE_DOOM, 2), // AX: Value inverted with the one above + use_joystick(FILE_DOOM, 0), + joyb_fire(FILE_DOOM, 0), + joyb_strafe(FILE_DOOM, 1), + joyb_use(FILE_DOOM, 3), + joyb_speed(FILE_DOOM, 2), + screenblocks(FILE_DOOM, 10), + detaillevel(FILE_DOOM, 0), + snd_channels(FILE_DOOM, 8), + snd_musicdevice(FILE_DOOM, 3), // unused, here for compatibility + snd_sfxdevice(FILE_DOOM, 3), // unused, here for compatibility + snd_sbport(FILE_DOOM, 0), // unused, here for compatibility + snd_sbirq(FILE_DOOM, 0), // unused, here for compatibility + snd_sbdma(FILE_DOOM, 0), // unused, here for compatibility + snd_mport(FILE_DOOM, 0), // unused, here for compatibility + usegamma(FILE_DOOM, 0), + chatmacro0(FILE_DOOM, HUSTR_CHATMACRO0), + chatmacro1(FILE_DOOM, HUSTR_CHATMACRO1), + chatmacro2(FILE_DOOM, HUSTR_CHATMACRO2), + chatmacro3(FILE_DOOM, HUSTR_CHATMACRO3), + chatmacro4(FILE_DOOM, HUSTR_CHATMACRO4), + chatmacro5(FILE_DOOM, HUSTR_CHATMACRO5), + chatmacro6(FILE_DOOM, HUSTR_CHATMACRO6), + chatmacro7(FILE_DOOM, HUSTR_CHATMACRO7), + chatmacro8(FILE_DOOM, HUSTR_CHATMACRO8), + chatmacro9(FILE_DOOM, HUSTR_CHATMACRO9), + /** + * Mocha Doom (mochadoom.cfg), these can be defined to anything and will be sorded by name + */ + mb_used(FILE_MOCHADOOM, 2), + fullscreen(FILE_MOCHADOOM, false), + fullscreen_mode(FILE_MOCHADOOM, FullscreenOptions.FullMode.Native), + fullscreen_stretch(FILE_MOCHADOOM, FullscreenOptions.StretchMode.Fit), + fullscreen_interpolation(FILE_MOCHADOOM, FullscreenOptions.InterpolationMode.Nearest), + alwaysrun(FILE_MOCHADOOM, false), // Always run is OFF + vanilla_key_behavior(FILE_MOCHADOOM, true), // Currently forces LSHIFT on RSHIFT, TODO: layouts, etc + automap_plotter_style(FILE_MOCHADOOM, Plotter.Style.Thin), // Thin is vanilla, Thick is scaled, Deep slightly rounded scaled + enable_colormap_lump(FILE_MOCHADOOM, true), // Enables usage of COLORMAP lump read from wad during lights and specials generation + color_depth(FILE_MOCHADOOM, BppMode.Indexed), // Indexed: 256, HiColor: 32 768, TrueColor: 16 777 216 + extend_plats_limit(FILE_MOCHADOOM, true), // Resize instead of "P_AddActivePlat: no more plats!" + extend_button_slots_limit(FILE_MOCHADOOM, true), // Resize instead of "P_StartButton: no button slots left!" + fix_blockmap(FILE_MOCHADOOM, true), // Add support for 512x512 blockmap + fix_gamma_ramp(FILE_MOCHADOOM, false), // Vanilla do not use pure black color because Gamma LUT calculated without it, doubling 128 + fix_gamma_palette(FILE_MOCHADOOM, false), // In vanilla, switching gamma with F11 hides Berserk or Rad suit tint + fix_sky_change(FILE_MOCHADOOM, false), // In vanilla, sky does not change when you exit the level and the next level with new sky + fix_sky_palette(FILE_MOCHADOOM, false), // In vanilla, sky color does not change when under effect of Invulnerability powerup + fix_medi_need(FILE_MOCHADOOM, false), // In vanilla, message "Picked up a medikit that you REALLY need!" never appears due to bug + fix_ouch_face(FILE_MOCHADOOM, false), // In vanilla, ouch face displayed only when acuired 25+ health when damaged for 25+ health + line_of_sight(FILE_MOCHADOOM, LOS.Vanilla), // Deaf monsters when thing pos corellates somehow with map vertex, change desync demos + vestrobe(FILE_MOCHADOOM, false), // Strobe effect on automap cut off from vanilla + scale_screen_tiles(FILE_MOCHADOOM, true), // If you scale screen tiles, it looks like vanilla + scale_melt(FILE_MOCHADOOM, true), // If you scale melt and use DoomRandom generator (not truly random), it looks exacly like vanilla + semi_translucent_fuzz(FILE_MOCHADOOM, false), // only works in AlphaTrueColor mode. Also ignored with fuzz_mix = true + fuzz_mix(FILE_MOCHADOOM, false), // Maes unique features on Fuzz effect. Vanilla dont have that, so they are switched off by default + parallelism_realcolor_tint(FILE_MOCHADOOM, Runtime.getRuntime().availableProcessors()), // Used for real color tinting to speed up + parallelism_patch_columns(FILE_MOCHADOOM, 0), // When drawing screen graphics patches, this speeds up column drawing, <= 0 is serial + greyscale_filter(FILE_MOCHADOOM, GreyscaleFilter.Luminance), // Used for FUZZ effect or with -greypal comand line argument (for test) + scene_renderer_mode(FILE_MOCHADOOM, SceneRendererMode.Serial), // In vanilla, scene renderer is serial. Parallel can be faster + reconstruct_savegame_pointers(FILE_MOCHADOOM, true), // In vanilla, infighting targets are not restored on savegame load + multiply(FILE_MOCHADOOM, 3); // Multiplies the base resolution by n times. Acceptable values are 1-5. + + public final static Map> SETTINGS_MAP = new HashMap<>(); + + static { + Arrays.stream(values()).forEach(Settings::updateConfig); + } + + > Settings(Files config, final T defaultValue) { + this.defaultValue = defaultValue; + this.valueType = defaultValue.getClass(); + this.configBase = config; + } + + Settings(Files config, final String defaultValue) { + this.defaultValue = defaultValue; + this.valueType = String.class; + this.configBase = config; + } + + Settings(Files config, final char defaultValue) { + this.defaultValue = defaultValue; + this.valueType = Character.class; + this.configBase = config; + } + + Settings(Files config, final int defaultValue) { + this.defaultValue = defaultValue; + this.valueType = Integer.class; + this.configBase = config; + } + + Settings(Files config, final long defaultValue) { + this.defaultValue = defaultValue; + this.valueType = Long.class; + this.configBase = config; + } + + Settings(Files config, final double defaultValue) { + this.defaultValue = defaultValue; + this.valueType = Double.class; + this.configBase = config; + } + + Settings(Files config, final boolean defaultValue) { + this.defaultValue = defaultValue; + this.valueType = Boolean.class; + this.configBase = config; + } + + public final Class valueType; + public final Object defaultValue; + private Files configBase; + + public boolean is(Object obj) { + return Engine.getConfig().equals(obj); + } + + public ConfigManager.UpdateStatus hasChange(boolean b) { + configBase.changed = configBase.changed || b; + return b ? ConfigManager.UpdateStatus.UPDATED : ConfigManager.UpdateStatus.UNCHANGED; + } + + public void rebase(Files newConfig) { + if (configBase == newConfig) { + return; + } + SETTINGS_MAP.get(configBase).remove(this); + configBase = newConfig; + updateConfig(); + } + + public Optional quoteType() { + if (valueType == String.class) { + return Optional.of(QuoteType.DOUBLE); + } else if (valueType == Character.class) { + return Optional.of(QuoteType.SINGLE); + } + + return Optional.empty(); + } + + public enum LOS { + Vanilla, Boom + } + + private void updateConfig() { + SETTINGS_MAP.compute(configBase, (c, list) -> { + if (list == null) { + list = EnumSet.of(this); + } else { + list.add(this); + } + + return list; + }); + } +} \ No newline at end of file diff --git a/doom/src/m/Swap.java b/doom/src/m/Swap.java new file mode 100644 index 0000000..68443bb --- /dev/null +++ b/doom/src/m/Swap.java @@ -0,0 +1,67 @@ +package m; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: Swap.java,v 1.2 2011/07/27 20:48:20 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Endianess handling, swapping 16bit and 32bit. +// It's role is much less important than in C-based ports (because of stream +// built-in endianness settings), but they are still used occasionally. +// +//----------------------------------------------------------------------------- +public final class Swap { + +// Swap 16bit, that is, MSB and LSB byte. + public final static short SHORT(short x) { + // No masking with 0xFF should be necessary. + // MAES: necessary with java due to sign trailing. + + return (short) ((short) ((x >>> 8) & 0xFF) | (x << 8)); + } + +//Swap 16bit, that is, MSB and LSB byte. + public final static short SHORT(char x) { + // No masking with 0xFF should be necessary. + // MAES: necessary with java due to sign trailing. + + return (short) ((short) ((x >>> 8) & 0xFF) | (x << 8)); + } + +//Swap 16bit, that is, MSB and LSB byte. + public final static char USHORT(char x) { + // No masking with 0xFF should be necessary. + // MAES: necessary with java due to sign trailing. + + return (char) ((char) ((x >>> 8) & 0xFF) | (x << 8)); + } + +// Swapping 32bit. +// Maes: the "long" here is really 32-bit. + public final static int LONG(int x) { + return (x >>> 24) + | ((x >>> 8) & 0xff00) + | ((x << 8) & 0xff0000) + | (x << 24); + } +} + +//$Log: Swap.java,v $ +//Revision 1.2 2011/07/27 20:48:20 velktron +//Proper commenting, cleanup. +// +//Revision 1.1 2010/06/30 08:58:50 velktron +//Let's see if this stuff will finally commit.... \ No newline at end of file diff --git a/doom/src/m/cheatseq_t.java b/doom/src/m/cheatseq_t.java new file mode 100644 index 0000000..9976365 --- /dev/null +++ b/doom/src/m/cheatseq_t.java @@ -0,0 +1,242 @@ +package m; + +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: cheatseq_t.java,v 1.8 2011/11/01 23:47:50 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: cheatseq_t.java,v $ +// Revision 1.8 2011/11/01 23:47:50 velktron +// Added constructor method to start from unscrambled strings. +// +// Revision 1.7 2011/05/06 14:00:54 velktron +// More of _D_'s changes committed. +// +// Revision 1.6 2010/12/14 00:53:32 velktron +// Some input sanitizing. Far from perfect but heh... +// +// Revision 1.5 2010/08/25 00:50:59 velktron +// Some more work... +// +// Revision 1.4 2010/07/21 11:41:47 velktron +// Work on menus... +// +// Revision 1.3 2010/07/20 15:52:56 velktron +// LOTS of changes, Automap almost complete. Use of fixed_t inside methods +// severely limited. +// +// Revision 1.2 2010/07/03 23:24:13 velktron +// Added a LOT of stuff, like Status bar code & objects. Now we're cooking with +// gas! +// +// Revision 1.1 2010/06/30 08:58:50 velktron +// Let's see if this stuff will finally commit.... +// +// +// Most stuff is still being worked on. For a good place to start and get an +// idea of what is being done, I suggest checking out the "testers" package. +// +// Revision 1.1 2010/06/29 11:07:34 velktron +// Release often, release early they say... +// +// Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, +// and there's still mixed C code in there. I suggest you load everything up in +// Eclpise and see what gives from there. +// +// A good place to start is the testers/ directory, where you can get an idea of +// how a few of the implemented stuff works. +// +// +// DESCRIPTION: +// Cheat sequence checking. +// +// ----------------------------------------------------------------------------- +/** + * Cheat sequence checking. MAES: all of this stuff used to be in cheat.h and + * cheat.c, but seeing how all manipulation is done on "cheatseq_t" objects, it + * makes more sense to move this functionality in here, and go OO all the way. + * So away with the fugly static methods!!! + */ +public class cheatseq_t { + + // This holds the actual data (was a char*). + public char[] sequence; + + // This is used as a pointer inside sequence. + // Was a char*, but in Java it makes more sense to have it as an int. + public int p; + + public cheatseq_t(char[] sequence, int p) { + this.sequence = sequence; + this.p = p; + } + + public cheatseq_t(char[] sequence) { + this.sequence = sequence; + this.p = 0; + } + + public cheatseq_t(String sequence, boolean prescrambled) { + if (prescrambled) { + this.sequence = sequence.toCharArray(); + p = 0; + } else { + this.sequence = scrambleString(sequence); + p = 0; + } + } + + /** + * This was in cheat.c, but makes more sense to be used as an + * initializer/constructor. + */ + public void GetParam(char[] buffer) { + + // char[] p; + char c; + int ptr = 0; + + // p = this.sequence; + // Increments pointer until the sequence reaches its first internal "1" + // ??? + while (this.sequence[ptr++] != 1) + ; + int bptr = 0; + // Now it copies the contents of this cheatseq_t into buffer...and nils + // it??? + do { + c = this.sequence[ptr]; + buffer[bptr++] = c; + this.sequence[ptr++] = 0; + } while ((c != 0) && (this.sequence[ptr] != 0xff)); + + if (this.sequence[ptr] == 0xff) { + buffer[bptr] = 0; + } + + } + + /** + * Called in st_stuff module, which handles the input. Returns true if the + * cheat was successful, false if failed. MAES: Let's make this boolean. + * + * @param cht + * @param key + * @return + */ + public boolean CheckCheat(cheatseq_t cht, int key) { + boolean rc = false; + + if (cht.p < 0) { + cht.p = 0; // initialize if first time + } + if (cht.p == 0) // This actually points inside "sequence" + // *(cht->p++) = key; + { + cht.sequence[cht.p++] = (char) key; + } else if (cheat_xlate_table[(char) key] == cht.sequence[cht.p]) { + cht.p++; + } else // Failure: back to the beginning. + { + cht.p = 0; + } + + if (cht.sequence[cht.p] == 1) { + cht.p++; + } else if (cht.sequence[cht.p] == 0xff) // end of sequence character + { + cht.p = 0; + rc = true; + } + + return rc; + } + + /** + * Called in st_stuff module, which handles the input. Returns true if the + * cheat was successful, false if failed. MAES: Let's make this boolean. + * + * @param key + * @return + */ + public boolean CheckCheat(int key) { + boolean rc = false; + + if (this.p < 0) { + this.p = 0; // initialize if first time + } + if (sequence[p] == 0) // This actually points inside "sequence" + // *(cht->p++) = key; + { + sequence[p++] = (char) key; + } //p++; //_D_: this fixed cheat with parm problem (IDCLIP) + else if (cheat_xlate_table[(char) key] == sequence[p]) { + p++; + } else // Failure: back to the beginning. + { + p = 0; + } + if (sequence[p] == 1) { + p++; + } else if (sequence[p] == 0xff) // end of sequence character + { + p = 0; + rc = true; + } + + return rc; + } + + /** + * Scrambles a character. 7 -> 0 6 -> 1 5 -> 5 4 -> 3 3 -> 4 2 -> 2 1 -> 6 0 + * -> 7 + * + * @param a + * @return + */ + public static char SCRAMBLE(char a) { + return (char) ((((a) & 1) << 7) + (((a) & 2) << 5) + ((a) & 4) + + (((a) & 8) << 1) + (((a) & 16) >>> 1) + ((a) & 32) + + (((a) & 64) >>> 5) + (((a) & 128) >>> 7)); + } + + public static char[] scrambleString(String s) { + + char[] tmp = new char[s.length() + 1]; + for (int i = 0; i < s.length(); i++) { + tmp[i] = SCRAMBLE(s.charAt(i)); + } + tmp[s.length()] = 0xff; + + return tmp; + } + + /** + * These should be common among all instances, unless for soooome reason you + * need multiple different such tables. + */ + public static boolean firsttime = true; + + public static char[] cheat_xlate_table = new char[256]; + + static { + if (firsttime) { + firsttime = false; + for (char i = 0; i < 256; i++) { + cheat_xlate_table[i] = SCRAMBLE(i); + } + } + } +} \ No newline at end of file diff --git a/doom/src/m/default_t.java b/doom/src/m/default_t.java new file mode 100644 index 0000000..32a2b06 --- /dev/null +++ b/doom/src/m/default_t.java @@ -0,0 +1,21 @@ +package m; + +public class default_t { + + public default_t(String name, int[] location, int defaultvalue) { + this.name = name; + this.location = location; + this.defaultvalue = defaultvalue; + } + + public String name; + + /** this is supposed to be a pointer */ + public int[] location; + + public int defaultvalue; + + int scantranslate; // PC scan code hack + + int untranslated; // lousy hack +}; \ No newline at end of file diff --git a/doom/src/m/fixed_t.java b/doom/src/m/fixed_t.java new file mode 100644 index 0000000..9dc280c --- /dev/null +++ b/doom/src/m/fixed_t.java @@ -0,0 +1,284 @@ +package m; + +import data.Defines; +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: fixed_t.java,v 1.14 2011/10/25 19:52:13 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// Fixed point implementation. +// +//----------------------------------------------------------------------------- + +// +// Fixed point, 32bit as 16.16. +// +// Most functionality of C-based ports is preserved, EXCEPT that there's +// no typedef of ints into fixed_t, and that there's no actual object fixed_t +// type that is actually instantiated in the current codebase, for performance reasons. +// There are still remnants of a full OO implementation that still do work, +// and the usual FixedMul/FixedDiv etc. methods are still used throughout the codebase, +// but operate on int operants (signed, 32-bit integers). +public class fixed_t implements Comparable { + + public static final int FRACBITS = 16; + public static final int FRACUNIT = (1 << FRACBITS); + public static final int MAPFRACUNIT = FRACUNIT / Defines.TIC_MUL; + public int val; + + public fixed_t() { + this.set(0); + } + + public int get() { + return val; + } + + public void set(int val) { + this.val = val; + } + + public void copy(fixed_t a) { + this.set(a.get()); + } + + public boolean equals(fixed_t a) { + return (this.get() == a.get()); + } + + public static boolean equals(fixed_t a, fixed_t b) { + return (a.get() == b.get()); + } + + public fixed_t(int val) { + this.val = val; + } + + public fixed_t(fixed_t x) { + this.val = x.val; + } + + public static final String rcsid = "$Id: fixed_t.java,v 1.14 2011/10/25 19:52:13 velktron Exp $"; + + /** Creates a new fixed_t object for the result a*b + * + * @param a + * @param b + * @return + */ + public static int FixedMul(fixed_t a, + fixed_t b) { + return (int) (((long) a.val * (long) b.val) >>> FRACBITS); + } + + public static int FixedMul(int a, + fixed_t b) { + return (int) (((long) a * (long) b.val) >>> FRACBITS); + } + + public static final int FixedMul(int a, + int b) { + return (int) (((long) a * (long) b) >>> FRACBITS); + } + + /** Returns result straight as an int.. + * + * @param a + * @param b + * @return + */ + public static int FixedMulInt(fixed_t a, + fixed_t b) { + return (int) (((long) a.val * (long) b.val) >> FRACBITS); + } + + /** In-place c=a*b + * + * @param a + * @param b + * @param c + */ + public final static void FixedMul(fixed_t a, + fixed_t b, + fixed_t c) { + c.set((int) (((long) a.val * (long) b.val) >> FRACBITS)); + } + + /** In-place this=this*a + * + * @param a + * @param b + * @param c + */ + public final void FixedMul(fixed_t a) { + this.set((int) (((long) a.val * (long) this.val) >> FRACBITS)); + } + + public final static int + FixedDiv(int a, + int b) { + if ((Math.abs(a) >> 14) >= Math.abs(b)) { + return (a ^ b) < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE; + } else { + long result; + + result = ((long) a << 16) / b; + + return (int) result; + } + } + + public final static int + FixedDiv2(int a, + int b) { + + int c; + c = (int) (((long) a << 16) / (long) b); + return c; + + /* + double c; + + c = ((double)a) / ((double)b) * FRACUNIT; + + if (c >= 2147483648.0 || c < -2147483648.0) + throw new ArithmeticException("FixedDiv: divide by zero"); + + return (int)c;*/ + } + + @Override + public int compareTo(fixed_t o) { + if (o.getClass() != fixed_t.class) { + return -1; + } + if (this.val == ((fixed_t) (o)).val) { + return 0; + } + if (this.val > ((fixed_t) (o)).val) { + return 1; + } else { + return -1; + } + } + + public int compareTo(int o) { + if (this.val == o) { + return 0; + } + if (this.val > o) { + return 1; + } else { + return -1; + } + } + + public void add(fixed_t a) { + this.val += a.val; + } + + public void sub(fixed_t a) { + this.val -= a.val; + } + + public void add(int a) { + this.val += a; + } + + public void sub(int a) { + this.val -= a; + } + + /** a+b + * + * @param a + * @param b + * @return + */ + public static int add(fixed_t a, fixed_t b) { + return a.val + b.val; + } + + /** a-b + * + * @param a + * @param b + * @return + */ + public static int sub(fixed_t a, fixed_t b) { + return a.val - b.val; + } + + /** c=a+b + * + * @param c + * @param a + * @param b + */ + public static void add(fixed_t c, fixed_t a, fixed_t b) { + c.val = a.val + b.val; + } + + /** c=a-b + * + * @param c + * @param a + * @param b + */ + public static void sub(fixed_t c, fixed_t a, fixed_t b) { + c.val = a.val - b.val; + } + + /** Equals Zero + * + * @return + */ + public boolean isEZ() { + return (this.val == 0); + } + + /** Greater than Zero + * + * @return + */ + public boolean isGZ() { + return (this.val > 0); + } + + /** Less than Zero + * + * @return + */ + public boolean isLZ() { + return (this.val < 0); + } + +// These are here to make easier handling all those methods in R +// that return "1" or "0" based on one result. + public int oneEZ() { + return (this.val == 0) ? 1 : 0; + } + + public int oneGZ() { + return (this.val > 0) ? 1 : 0; + } + + public int oneLZ() { + return (this.val < 0) ? 1 : 0; + } + +} \ No newline at end of file diff --git a/doom/src/m/menu_t.java b/doom/src/m/menu_t.java new file mode 100644 index 0000000..1159f7d --- /dev/null +++ b/doom/src/m/menu_t.java @@ -0,0 +1,37 @@ +package m; + +/** General form for a classic, Doom-style menu with a bunch of + * items and a drawing routine (menu_t's don't have action callbacks + * proper, though). + * + * @author Maes + * + */ +public class menu_t { + + public menu_t(int numitems, menu_t prev, menuitem_t[] items, + DrawRoutine drawroutine, int x, int y, int lastOn) { + this.numitems = numitems; + this.prevMenu = prev; + this.menuitems = items; + this.routine = drawroutine; + this.x = x; + this.y = y; + this.lastOn = lastOn; + + } + /** # of menu items */ + public int numitems; + + /** previous menu */ + public menu_t prevMenu; + + /** menu items */ + public menuitem_t[] menuitems; + /** draw routine */ + public DrawRoutine routine; + /** x,y of menu */ + public int x, y; + /** last item user was on in menu */ + public int lastOn; +} \ No newline at end of file diff --git a/doom/src/m/menuitem_t.java b/doom/src/m/menuitem_t.java new file mode 100644 index 0000000..dadf321 --- /dev/null +++ b/doom/src/m/menuitem_t.java @@ -0,0 +1,38 @@ +package m; + +import g.Signals; + +public class menuitem_t { + + public menuitem_t(int status, String name, MenuRoutine routine, Signals.ScanCode alphaKey) { + this.status = status; + this.name = name; + this.routine = routine; + this.alphaKey = alphaKey; + } + + public menuitem_t(int status, String name, MenuRoutine routine) { + this.status = status; + this.name = name; + this.routine = routine; + } + + /** + * 0 = no cursor here, 1 = ok, 2 = arrows ok + */ + int status; + + String name; + + // choice = menu item #. + // if status = 2, + // choice=0:leftarrow,1:rightarrow + // MAES: OK... to probably we need some sort of "MenuRoutine" class for this one. + // void (*routine)(int choice); + MenuRoutine routine; + + /** + * hotkey in menu + */ + Signals.ScanCode alphaKey; +} \ No newline at end of file diff --git a/doom/src/m/pcx_t.java b/doom/src/m/pcx_t.java new file mode 100644 index 0000000..fd9cb03 --- /dev/null +++ b/doom/src/m/pcx_t.java @@ -0,0 +1,92 @@ +package m; + +import java.io.DataOutputStream; +import java.io.IOException; +import w.IWritableDoomObject; + +/** Yeah, this is actually a PCX header implementation, and Mocha Doom + * saved PCX screenshots. Implemented it back just to shot that it can be + * done (will switch to PNG ASAP though). + * + * @author Maes + * + */ +public class pcx_t implements IWritableDoomObject { + + // + // SCREEN SHOTS + // + // char -> byte Bytes. + /** manufacturer byte, must be 10 decimal */ + public byte manufacturer; + + /** PCX version number */ + byte version; + + /** run length encoding byte, must be 1 */ + byte encoding; + + /** number of bits per pixel per bit plane */ + byte bits_per_pixel; + + /** image limits in pixels: Xmin, Ymin, Xmax, Ymax */ + public char xmin, ymin, xmax, ymax; + + /** horizontal dots per inch when printed (unreliable) */ + char hres; + + /** vertical dots per inch when printed (unreliable) */ + char vres; + + /** 16-color palette (16 RGB triples between 0-255) + * UNUSED in Doom. */ + byte[] palette = new byte[48]; + + /** reserved, must be zero */ + byte reserved; + + /** number of bit planes */ + byte color_planes; + + /** video memory bytes per image row */ + char bytes_per_line; + + /** 16-color palette interpretation (unreliable) 0=color/b&w 1=grayscale */ + char palette_type; + + // Seems off-spec. However it's left all zeroed out. + byte[] filler = new byte[58]; + + //unsigned char data; + byte[] data; + + @Override + public void write(DataOutputStream f) throws IOException { + // char -> byte Bytes. + + f.writeByte(manufacturer); + f.writeByte(version); + f.writeByte(encoding); + f.writeByte(bits_per_pixel); + + // unsigned short -> char + f.writeChar(Swap.SHORT(xmin)); + f.writeChar(Swap.SHORT(ymin)); + f.writeChar(Swap.SHORT(xmax)); + f.writeChar(Swap.SHORT(ymax)); + + f.writeChar(Swap.SHORT(hres)); + f.writeChar(Swap.SHORT(vres)); + f.write(palette); + + f.writeByte(reserved); + f.writeByte(color_planes); + // unsigned short -> char + f.writeChar(Swap.SHORT(bytes_per_line)); + f.writeChar(Swap.SHORT(palette_type)); + + f.write(filler); + //unsigned char data; // unbounded + f.write(data); + } +}; \ No newline at end of file diff --git a/doom/src/mochadoom/Engine.java b/doom/src/mochadoom/Engine.java new file mode 100644 index 0000000..b0023b7 --- /dev/null +++ b/doom/src/mochadoom/Engine.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package mochadoom; + +import awt.DoomWindow; +import awt.DoomWindowController; +import awt.EventBase.KeyStateInterest; +import static awt.EventBase.KeyStateSatisfaction.WANTS_MORE_ATE; +import static awt.EventBase.KeyStateSatisfaction.WANTS_MORE_PASS; +import awt.EventHandler; +import doom.CVarManager; +import doom.CommandVariable; +import doom.ConfigManager; +import doom.DoomMain; +import static g.Signals.ScanCode.SC_ENTER; +import static g.Signals.ScanCode.SC_ESCAPE; +import static g.Signals.ScanCode.SC_LALT; +import static g.Signals.ScanCode.SC_PAUSE; +import i.Strings; +import java.io.IOException; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Engine { + + private static final Logger LOGGER = Loggers.getLogger(Engine.class.getName()); + + private static volatile Engine instance; + + /** + * Mocha Doom engine entry point + */ + public static void main(final String[] argv) throws IOException { + LOGGER.log(Level.INFO, Strings.MOCHA_DOOM_TITLE); + final Engine local; + synchronized (Engine.class) { + local = new Engine(argv); + } + + /** + * Add eventHandler listeners to JFrame and its Canvas element + */ + /*content.addKeyListener(listener); + content.addMouseListener(listener); + content.addMouseMotionListener(listener); + frame.addComponentListener(listener); + frame.addWindowFocusListener(listener); + frame.addWindowListener(listener);*/ + // never returns + try { + local.DOOM.setupLoop(); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "DOOM.setupLoop failure", e); + System.exit(1); + } + } + + public final CVarManager cvm; + public final ConfigManager cm; + public final DoomWindowController windowController; + private final DoomMain DOOM; + + @SuppressWarnings("unchecked") + private Engine(final String... argv) throws IOException { + instance = this; + + // reads command line arguments + this.cvm = new CVarManager(Arrays.asList(argv)); + + // reads default.cfg and mochadoom.cfg + this.cm = new ConfigManager(); + + // intiializes stuff + this.DOOM = new DoomMain<>(); + + // opens a window + this.windowController = /*cvm.bool(CommandVariable.AWTFRAME) + ? */ DoomWindow.createCanvasWindowController( + DOOM.graphicSystem::getScreenImage, + DOOM::PostEvent, + DOOM.graphicSystem.getScreenWidth(), + DOOM.graphicSystem.getScreenHeight() + )/* : DoomWindow.createJPanelWindowController( + DOOM.graphicSystem::getScreenImage, + DOOM::PostEvent, + DOOM.graphicSystem.getScreenWidth(), + DOOM.graphicSystem.getScreenHeight() + )*/; + + windowController.getObserver().addInterest( + new KeyStateInterest<>(obs -> { + EventHandler.fullscreenChanges(windowController.getObserver(), windowController.switchFullscreen()); + return WANTS_MORE_ATE; + }, SC_LALT, SC_ENTER) + ).addInterest( + new KeyStateInterest<>(obs -> { + if (!windowController.isFullscreen()) { + if (DOOM.menuactive || DOOM.paused || DOOM.demoplayback) { + EventHandler.menuCaptureChanges(obs, DOOM.mousecaptured = !DOOM.mousecaptured); + } else { // can also work when not DOOM.mousecaptured + EventHandler.menuCaptureChanges(obs, DOOM.mousecaptured = true); + } + } + return WANTS_MORE_PASS; + }, SC_LALT) + ).addInterest( + new KeyStateInterest<>(obs -> { + if (!windowController.isFullscreen() && !DOOM.mousecaptured && DOOM.menuactive) { + EventHandler.menuCaptureChanges(obs, DOOM.mousecaptured = true); + } + + return WANTS_MORE_PASS; + }, SC_ESCAPE) + ).addInterest( + new KeyStateInterest<>(obs -> { + if (!windowController.isFullscreen() && !DOOM.mousecaptured && DOOM.paused) { + EventHandler.menuCaptureChanges(obs, DOOM.mousecaptured = true); + } + return WANTS_MORE_PASS; + }, SC_PAUSE) + ); + } + + /** + * Temporary solution. Will be later moved in more detailed place + */ + public static void updateFrame() { + instance.windowController.updateFrame(); + } + + public String getWindowTitle(double frames) { + if (cvm.bool(CommandVariable.SHOWFPS)) { + return String.format("%s - %s FPS: %.2f", Strings.MOCHA_DOOM_TITLE, DOOM.bppMode, frames); + } else { + return String.format("%s - %s", Strings.MOCHA_DOOM_TITLE, DOOM.bppMode); + } + } + + public static Engine getEngine() { + Engine local = Engine.instance; + if (local == null) { + synchronized (Engine.class) { + local = Engine.instance; + if (local == null) { + try { + Engine.instance = local = new Engine(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Engine I/O error", ex); + throw new Error("This launch is DOOMed"); + } + } + } + } + + return local; + } + + public static CVarManager getCVM() { + return getEngine().cvm; + } + + public static ConfigManager getConfig() { + return getEngine().cm; + } +} \ No newline at end of file diff --git a/doom/src/mochadoom/Loggers.java b/doom/src/mochadoom/Loggers.java new file mode 100644 index 0000000..5e54583 --- /dev/null +++ b/doom/src/mochadoom/Loggers.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package mochadoom; + +import awt.DoomWindow; +import awt.EventBase; +import awt.EventBase.ActionMode; +import awt.EventBase.ActionStateHolder; +import awt.EventBase.RelationType; +import doom.CVarManager; +import doom.ConfigManager; +import doom.DoomMain; +import i.DoomSystem; +import java.awt.AWTEvent; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.IntFunction; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import p.ActiveStates; +import v.graphics.Patches; + +/** + * Facility to manage Logger Levels for different classes + * All of that should be used instead of System.err.println for debug + * + * @author Good Sign + */ +public class Loggers { + + static { + System.setProperty("java.util.logging.SimpleFormatter.format", "[%1$tF %1$tT] [%4$-7s] [%3$-20s] %5$s %n"); + } + + private static final Level DEFAULT_LEVEL = Level.WARNING; + + private static final Map PARENT_LOGGERS_MAP = Stream.of( + Level.FINE, Level.FINER, Level.FINEST, Level.INFO, Level.SEVERE, Level.WARNING + ).collect(Collectors.toMap(l -> l, Loggers::newLoggerHandlingLevel)); + + private static final Logger DEFAULT_LOGGER = PARENT_LOGGERS_MAP.get(DEFAULT_LEVEL); + private static final HashMap INDIVIDUAL_CLASS_LOGGERS = new HashMap<>(); + + static { + //INDIVIDUAL_CLASS_LOGGERS.put(EventObserver.class.getName(), PARENT_LOGGERS_MAP.get(Level.FINE)); + //INDIVIDUAL_CLASS_LOGGERS.put(TraitFactory.class.getName(), PARENT_LOGGERS_MAP.get(Level.FINER)); + INDIVIDUAL_CLASS_LOGGERS.put(ActiveStates.class.getName(), PARENT_LOGGERS_MAP.get(Level.FINER)); + INDIVIDUAL_CLASS_LOGGERS.put(DoomWindow.class.getName(), PARENT_LOGGERS_MAP.get(Level.FINE)); + INDIVIDUAL_CLASS_LOGGERS.put(Patches.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + INDIVIDUAL_CLASS_LOGGERS.put(ConfigManager.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + INDIVIDUAL_CLASS_LOGGERS.put(DoomMain.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + INDIVIDUAL_CLASS_LOGGERS.put(DoomSystem.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + INDIVIDUAL_CLASS_LOGGERS.put(CVarManager.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + INDIVIDUAL_CLASS_LOGGERS.put(Engine.class.getName(), PARENT_LOGGERS_MAP.get(Level.INFO)); + } + + public static Logger getLogger(final String className) { + final Logger ret = Logger.getLogger(className); + ret.setParent(INDIVIDUAL_CLASS_LOGGERS.getOrDefault(className, DEFAULT_LOGGER)); + + return ret; + } + + private static EventBase lastHandler = null; + + public static & EventBase> void LogEvent( + final Logger logger, + final ActionStateHolder actionStateHolder, + final EventHandler handler, + final AWTEvent event + ) { + if (!logger.isLoggable(Level.ALL) && lastHandler == handler) { + return; + } + + lastHandler = handler; + + @SuppressWarnings("unchecked") + final IntFunction[]> arrayGenerator = EventBase[]::new; + final EventBase[] depends = actionStateHolder + .cooperations(handler, RelationType.DEPEND) + .stream() + .filter(hdl -> actionStateHolder.hasActionsEnabled(hdl, ActionMode.DEPEND)) + .toArray(arrayGenerator); + + final Map> adjusts = actionStateHolder + .adjustments(handler); + + final EventBase[] causes = actionStateHolder + .cooperations(handler, RelationType.CAUSE) + .stream() + .filter(hdl -> actionStateHolder.hasActionsEnabled(hdl, ActionMode.DEPEND)) + .toArray(arrayGenerator); + + final EventBase[] reverts = actionStateHolder + .cooperations(handler, RelationType.REVERT) + .stream() + .filter(hdl -> actionStateHolder.hasActionsEnabled(hdl, ActionMode.DEPEND)) + .toArray(arrayGenerator); + + if (logger.isLoggable(Level.FINEST)) { + logger.log(Level.FINEST, () -> String.format( + "\n\nENCOUNTERED EVENT: %s [%s] \n%s: %s \n%s \n%s: %s \n%s: %s \nOn event: %s", + handler, ActionMode.PERFORM, + RelationType.DEPEND, Arrays.toString(depends), + adjusts.entrySet().stream().collect(StringBuilder::new, (sb, e) -> sb.append(e.getKey()).append(' ').append(e.getValue()).append('\n'), StringBuilder::append), + RelationType.CAUSE, Arrays.toString(causes), + RelationType.REVERT, Arrays.toString(reverts), + event + )); + } else if (logger.isLoggable(Level.FINER)) { + logger.log(Level.FINER, () -> String.format( + "\n\nENCOUNTERED EVENT: %s [%s] \n%s: %s \n%s \n%s: %s \n%s: %s \n", + handler, ActionMode.PERFORM, + RelationType.DEPEND, Arrays.toString(depends), + adjusts.entrySet().stream().collect(StringBuilder::new, (sb, e) -> sb.append(e.getKey()).append(' ').append(e.getValue()).append('\n'), StringBuilder::append), + RelationType.CAUSE, Arrays.toString(causes), + RelationType.REVERT, Arrays.toString(reverts) + )); + } else { + logger.log(Level.FINE, () -> String.format( + "\nENCOUNTERED EVENT: %s [%s]", + handler, ActionMode.PERFORM + )); + } + } + + private Loggers() { + } + + private static Logger newLoggerHandlingLevel(final Level l) { + final OutHandler h = new OutHandler(); + h.setLevel(l); + final Logger ret = Logger.getAnonymousLogger(); + ret.setUseParentHandlers(false); + ret.setLevel(l); + ret.addHandler(h); + return ret; + } + + private static final class OutHandler extends ConsoleHandler { + + @Override + @SuppressWarnings("UseOfSystemOutOrSystemErr") + protected synchronized void setOutputStream(final OutputStream out) throws SecurityException { + super.setOutputStream(System.out); + } + } +} \ No newline at end of file diff --git a/doom/src/n/BasicNetworkInterface.java b/doom/src/n/BasicNetworkInterface.java new file mode 100644 index 0000000..8aa3a13 --- /dev/null +++ b/doom/src/n/BasicNetworkInterface.java @@ -0,0 +1,410 @@ +package n; + +import static data.Limits.MAXNETNODES; +import doom.CommandVariable; +import doom.DoomMain; +import static doom.NetConsts.CMD_GET; +import static doom.NetConsts.CMD_SEND; +import static doom.NetConsts.DOOMCOM_ID; +import doom.doomcom_t; +import doom.doomdata_t; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import w.DoomBuffer; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: BasicNetworkInterface.java,v 1.5 2011/05/26 13:39:06 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// $Log: BasicNetworkInterface.java,v $ +// Revision 1.5 2011/05/26 13:39:06 velktron +// Now using ICommandLineManager +// +// Revision 1.4 2011/05/18 16:54:31 velktron +// Changed to DoomStatus +// +// Revision 1.3 2011/05/17 16:53:42 velktron +// _D_'s version. +// +// Revision 1.2 2010/12/20 17:15:08 velktron +// Made the renderer more OO -> TextureManager and other changes as well. +// +// Revision 1.1 2010/11/17 23:55:06 velktron +// Kind of playable/controllable. +// +// Revision 1.2 2010/11/11 15:31:28 velktron +// Fixed "warped floor" error. +// +// Revision 1.1 2010/10/22 16:22:43 velktron +// Renderer works stably enough but a ton of bleeding. Started working on netcode. +// +// +// DESCRIPTION: +// +//----------------------------------------------------------------------------- +public class BasicNetworkInterface implements DoomSystemNetworking { + + static final Logger LOGGER = Loggers.getLogger(BasicNetworkInterface.class.getName()); + + protected DoomMain DOOM; + + public BasicNetworkInterface(DoomMain DOOM) { + this.DOOM = DOOM; + //this.myargv=DM.myargv; + //this.myargc=DM.myargc; + sendData = new doomdata_t(); + recvData = new doomdata_t(); + // We can do that since the buffer is reused. + // Note: this will effectively tie doomdata and the datapacket. + recvPacket = new DatagramPacket(recvData.cached(), recvData.cached().length); + sendPacket = new DatagramPacket(sendData.cached(), sendData.cached().length); + } + + // Bind it to the ones inside DN and DM; + //doomdata_t netbuffer; + doomcom_t doomcom; + + // For some odd reason... + /** + * Changes endianness of a number + */ + public static int ntohl(int x) { + return ((((x & 0x000000ff) << 24) + | ((x & 0x0000ff00) << 8) + | ((x & 0x00ff0000) >>> 8) + | ((x & 0xff000000) >>> 24))); + } + + public static short ntohs(short x) { + return (short) (((x & 0x00ff) << 8) | ((x & 0xff00) >>> 8)); + } + + public static int htonl(int x) { + return ntohl(x); + } + + public static short htons(short x) { + return ntohs(x); + } + + //void NetSend (); + //boolean NetListen (); + // + // NETWORKING + // + // Maes: come on, we all know it's 666. + int DOOMPORT = 666;//(IPPORT_USERRESERVED +0x1d ); + + //_D_: for testing purposes. If testing on the same machine, we can't have two UDP servers on the same port + int RECVPORT = DOOMPORT; + int SENDPORT = DOOMPORT; + + //DatagramSocket sendsocket; + DatagramSocket insocket; + + // MAES: closest java equivalent + DatagramSocket /*InetAddress*/ sendaddress[] = new DatagramSocket/*InetAddress*/[MAXNETNODES]; + + interface NetFunction { + + public void invoke(); + } + + // To use inside packetsend. Declare once and reuse to save on heap costs. + private final doomdata_t sendData; + private final doomdata_t recvData; + + // We also reuse always the same DatagramPacket, "peged" to sw's byte buffer. + private final DatagramPacket recvPacket; + private final DatagramPacket sendPacket; + + public void sendSocketPacket(DatagramSocket ds, DatagramPacket dp) throws IOException { + ds.send(dp); + } + + public PacketSend packetSend = new PacketSend(); + + public class PacketSend implements NetFunction { + + @Override + public void invoke() { + int c; + + doomdata_t netbuffer = DOOM.netbuffer; + + // byte swap: so this is transferred as little endian? Ugh + /*sendData.checksum = htonl(netbuffer.checksum); + sendData.player = netbuffer.player; + sendData.retransmitfrom = netbuffer.retransmitfrom; + sendData.starttic = netbuffer.starttic; + sendData.numtics = netbuffer.numtics; + for (c=0 ; c< netbuffer.numtics ; c++) + { + sendData.cmds[c].forwardmove = netbuffer.cmds[c].forwardmove; + sendData.cmds[c].sidemove = netbuffer.cmds[c].sidemove; + sendData.cmds[c].angleturn = htons(netbuffer.cmds[c].angleturn); + sendData.cmds[c].consistancy = htons(netbuffer.cmds[c].consistancy); + sendData.cmds[c].chatchar = netbuffer.cmds[c].chatchar; + sendData.cmds[c].buttons = netbuffer.cmds[c].buttons; + } + */ + //printf ("sending %i\n",gametic); + sendData.copyFrom(netbuffer); + // MAES: This will force the buffer to be refreshed. + byte[] bytes = sendData.pack(); + + /*System.out.print("SEND >> Thisplayer: "+DM.consoleplayer+" numtics: "+sendData.numtics+" consistency: "); + for (doom.ticcmd_t t: sendData.cmds) + System.out.print(t.consistancy+","); + System.out.println();*/ + // The socket already contains the address it needs, + // and the packet's buffer is already modified. Send away. + sendPacket.setData(bytes, 0, doomcom.datalength); + DatagramSocket sendsocket; + try { + sendsocket = sendaddress[doomcom.remotenode]; + sendPacket.setSocketAddress(sendsocket.getRemoteSocketAddress()); + sendSocketPacket(sendsocket, sendPacket); + } catch (Exception e) { + DOOM.doomSystem.Error("SendPacket error: %s", e.getMessage()); + } + + // if (c == -1) + // I_Error ("SendPacket error: %s",strerror(errno)); + } + + } + + public void socketGetPacket(DatagramSocket ds, DatagramPacket dp) throws IOException { + ds.receive(dp); + } + + // Used inside PacketGet + private boolean first = true; + + public PacketGet packetGet = new PacketGet(); + + public class PacketGet implements NetFunction { + + @Override + public void invoke() { + int i; + int c; + + // Receive back into swp. + try { + //recvPacket.setSocketAddress(insocket.getLocalSocketAddress()); + socketGetPacket(insocket, recvPacket); + } catch (SocketTimeoutException e) { + doomcom.remotenode = -1; // no packet + return; + } catch (Exception e) { + if (e.getClass() != java.nio.channels.IllegalBlockingModeException.class) { + DOOM.doomSystem.Error("GetPacket: %s", (Object[]) e.getStackTrace()); + } + } + + recvData.unpack(recvPacket.getData()); + InetAddress fromaddress = recvPacket.getAddress(); + + /*System.out.print("RECV << Thisplayer: "+DM.consoleplayer+" numtics: "+recvData.numtics+" consistency: "); + for (doom.ticcmd_t t: recvData.cmds) + System.out.print(t.consistancy+","); + System.out.println();*/ + { + //static int first=1; + if (first) { + sb.setLength(0); + sb.append("(").append(DOOM.consoleplayer).append(") PacketRECV len="); + sb.append(recvPacket.getLength()); + sb.append(":p=[0x"); + sb.append(Integer.toHexString(recvData.checksum)); + sb.append(" 0x"); + sb.append(DoomBuffer.getBEInt(recvData.retransmitfrom, recvData.starttic, recvData.player, recvData.numtics)); + sb.append("numtics: ").append(recvData.numtics); + LOGGER.log(Level.FINE, sb.toString()); + first = false; + } + } + + // find remote node number + for (i = 0; i < doomcom.numnodes; i++) { + if (sendaddress[i] != null) { + if (fromaddress.equals(sendaddress[i].getInetAddress())) { + break; + } + } + } + + if (i == doomcom.numnodes) { + // packet is not from one of the players (new game broadcast) + doomcom.remotenode = -1; // no packet + return; + } + + doomcom.remotenode = (short) i; // good packet from a game player + doomcom.datalength = (short) recvPacket.getLength(); + + //_D_: temporary hack to test two player on single machine + //doomcom.remotenode = (short)(RECVPORT-DOOMPORT); + // byte swap + /*doomdata_t netbuffer = DM.netbuffer; + netbuffer.checksum = ntohl(recvData.checksum); + netbuffer.player = recvData.player; + netbuffer.retransmitfrom = recvData.retransmitfrom; + netbuffer.starttic = recvData.starttic; + netbuffer.numtics = recvData.numtics; + + for (c=0 ; c< netbuffer.numtics ; c++) + { + netbuffer.cmds[c].forwardmove = recvData.cmds[c].forwardmove; + netbuffer.cmds[c].sidemove = recvData.cmds[c].sidemove; + netbuffer.cmds[c].angleturn = ntohs(recvData.cmds[c].angleturn); + netbuffer.cmds[c].consistancy = ntohs(recvData.cmds[c].consistancy); + netbuffer.cmds[c].chatchar = recvData.cmds[c].chatchar; + netbuffer.cmds[c].buttons = recvData.cmds[c].buttons; + } */ + DOOM.netbuffer.copyFrom(recvData); + + } + + }; + + // Maes: oh great. More function pointer "fun". + NetFunction netget = packetGet; + NetFunction netsend = packetSend; + + // + // I_InitNetwork + // + @Override + public void InitNetwork() { + //struct hostent* hostentry; // host information entry + + doomcom = new doomcom_t(); + //netbuffer = new doomdata_t(); + DOOM.setDoomCom(doomcom); + //DM.netbuffer = netbuffer; + + // set up for network + if (!DOOM.cVarManager.with(CommandVariable.DUP, 0, (Character c) -> { + doomcom.ticdup = (short) (c - '0'); + if (doomcom.ticdup < 1) { + doomcom.ticdup = 1; + } + if (doomcom.ticdup > 9) { + doomcom.ticdup = 9; + } + })) { + doomcom.ticdup = 1; + } + + if (DOOM.cVarManager.bool(CommandVariable.EXTRATIC)) { + doomcom.extratics = 1; + } else { + doomcom.extratics = 0; + } + + DOOM.cVarManager.with(CommandVariable.PORT, 0, (Integer port) -> { + DOOMPORT = port; + LOGGER.log(Level.INFO, String.format("using alternate port %d", DOOMPORT)); + }); + + // parse network game options, + // -net ... + if (!DOOM.cVarManager.present(CommandVariable.NET)) { + // single player game + DOOM.netgame = false; + doomcom.id = DOOMCOM_ID; + doomcom.numplayers = doomcom.numnodes = 1; + doomcom.deathmatch = 0; // false + doomcom.consoleplayer = 0; + return; + } + + DOOM.netgame = true; + + // parse player number and host list + doomcom.consoleplayer = (short) (DOOM.cVarManager.get(CommandVariable.NET, Character.class, 0).get() - '1'); + + RECVPORT = SENDPORT = DOOMPORT; + if (doomcom.consoleplayer == 0) { + SENDPORT++; + } else { + RECVPORT++; + } + + doomcom.numnodes = 1; // this node for sure + + String[] hosts = DOOM.cVarManager.get(CommandVariable.NET, String[].class, 1).get(); + for (String host : hosts) { + try { + InetAddress addr = InetAddress.getByName(host); + DatagramSocket ds = new DatagramSocket(null); + ds.setReuseAddress(true); + ds.connect(addr, SENDPORT); + + sendaddress[doomcom.numnodes] = ds; + } catch (SocketException | UnknownHostException e) { + LOGGER.log(Level.SEVERE, "InitNetwork: failure", e); + } + + doomcom.numnodes++; + } + + doomcom.id = DOOMCOM_ID; + doomcom.numplayers = doomcom.numnodes; + + // build message to receive + try { + insocket = new DatagramSocket(null); + insocket.setReuseAddress(true); + insocket.setSoTimeout(1); + insocket.bind(new InetSocketAddress(RECVPORT)); + } catch (SocketException e1) { + LOGGER.log(Level.SEVERE, "InitNetwork: failure", e1); + } + } + + @Override + public void NetCmd() { + if (insocket == null) //HACK in case "netgame" is due to "addbot" + { + return; + } + + if (DOOM.doomcom.command == CMD_SEND) { + netsend.invoke(); + } else if (doomcom.command == CMD_GET) { + netget.invoke(); + } else { + DOOM.doomSystem.Error("Bad net cmd: %d", doomcom.command); + } + + } + + // Instance StringBuilder + private StringBuilder sb = new StringBuilder(); +} \ No newline at end of file diff --git a/doom/src/n/DoomSystemNetworking.java b/doom/src/n/DoomSystemNetworking.java new file mode 100644 index 0000000..d22b437 --- /dev/null +++ b/doom/src/n/DoomSystemNetworking.java @@ -0,0 +1,43 @@ +package n; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: DoomSystemNetworking.java,v 1.1 2010/11/17 23:55:06 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// System specific network interface stuff. +// +//----------------------------------------------------------------------------- +public interface DoomSystemNetworking { + +// Called by D_DoomMain. + public void InitNetwork(); + + public void NetCmd(); + +} + +//----------------------------------------------------------------------------- +// +// $Log: DoomSystemNetworking.java,v $ +// Revision 1.1 2010/11/17 23:55:06 velktron +// Kind of playable/controllable. +// +// Revision 1.1 2010/10/22 16:22:43 velktron +// Renderer works stably enough but a ton of bleeding. Started working on netcode. +// +// +//----------------------------------------------------------------------------- \ No newline at end of file diff --git a/doom/src/n/DummyNetworkDriver.java b/doom/src/n/DummyNetworkDriver.java new file mode 100644 index 0000000..ff6f3be --- /dev/null +++ b/doom/src/n/DummyNetworkDriver.java @@ -0,0 +1,45 @@ +package n; + +import doom.CommandVariable; +import doom.DoomMain; +import doom.NetConsts; +import doom.doomcom_t; +import mochadoom.Engine; + +/** Does nothing. + * Allows running single-player games without an actual network. + * Hopefully, it will be replaced by a real UDP-based driver one day. + * + * @author Velktron + * + */ +public class DummyNetworkDriver implements NetConsts, DoomSystemNetworking { + + ////////////// STATUS /////////// + private final DoomMain DOOM; + + public DummyNetworkDriver(DoomMain DOOM) { + this.DOOM = DOOM; + } + + @Override + public void InitNetwork() { + doomcom_t doomcom = new doomcom_t(); + doomcom.id = DOOMCOM_ID; + doomcom.ticdup = 1; + + // single player game + DOOM.netgame = Engine.getCVM().present(CommandVariable.NET); + doomcom.id = DOOMCOM_ID; + doomcom.numplayers = doomcom.numnodes = 1; + doomcom.deathmatch = 0; + doomcom.consoleplayer = 0; + DOOM.gameNetworking.setDoomCom(doomcom); + } + + @Override + public void NetCmd() { + // TODO Auto-generated method stub + + } +} \ No newline at end of file diff --git a/doom/src/n/DummyNetworkHandler.java b/doom/src/n/DummyNetworkHandler.java new file mode 100644 index 0000000..8a2566c --- /dev/null +++ b/doom/src/n/DummyNetworkHandler.java @@ -0,0 +1,44 @@ +package n; + +import doom.IDoomGameNetworking; +import doom.doomcom_t; + +public class DummyNetworkHandler implements IDoomGameNetworking { + + @Override + public void NetUpdate() { + // TODO Auto-generated method stub + + } + + @Override + public void TryRunTics() { + // TODO Auto-generated method stub + + } + + @Override + public doomcom_t getDoomCom() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setDoomCom(doomcom_t doomcom) { + // TODO Auto-generated method stub + + } + + @Override + public int getTicdup() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setTicdup(int ticdup) { + // TODO Auto-generated method stub + + } + +} \ No newline at end of file diff --git a/doom/src/n/IDoomNet.java b/doom/src/n/IDoomNet.java new file mode 100644 index 0000000..3f11cae --- /dev/null +++ b/doom/src/n/IDoomNet.java @@ -0,0 +1,6 @@ +package n; + +public interface IDoomNet { + + public void NetUpdate(); +} \ No newline at end of file diff --git a/doom/src/p/AbstractLevelLoader.java b/doom/src/p/AbstractLevelLoader.java new file mode 100644 index 0000000..ace43a3 --- /dev/null +++ b/doom/src/p/AbstractLevelLoader.java @@ -0,0 +1,923 @@ +package p; + +import static data.Defines.BLOCKMAPPADDING; +import static data.Defines.MAPBLOCKSHIFT; +import static data.Defines.MAPBLOCKUNITS; +import static data.Defines.NF_SUBSECTOR; +import static data.Defines.PU_LEVEL; +import data.Limits; +import data.mapthing_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.P_MapUtl; +import static doom.SourceCode.P_MapUtl.P_SetThingPosition; +import doom.SourceCode.R_Main; +import static doom.SourceCode.R_Main.R_PointInSubsector; +import doom.SourceCode.fixed_t; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.BBox; +import m.Settings; +import static m.fixed_t.FRACBITS; +import mochadoom.Engine; +import mochadoom.Loggers; +import static p.mobj_t.MF_NOBLOCKMAP; +import static p.mobj_t.MF_NOSECTOR; +import rr.line_t; +import rr.node_t; +import rr.sector_t; +import rr.seg_t; +import rr.side_t; +import rr.subsector_t; +import rr.vertex_t; +import utils.C2JUtils; +import static utils.C2JUtils.flags; + +/** + * The idea is to lump common externally readable properties that need DIRECT + * ACCESS (aka not behindsetters/getters) here, as well as some common shared + * internal structures/status objects. If you need access to stuff like the + * blockmap/reject table etc. then you should ask for this class. If you only + * need access to some methods like e.g. SetupLevel, you can simply use the + * ILevelLoader interface. + * + * @author velktron + */ +public abstract class AbstractLevelLoader implements ILevelLoader { + + private static final Logger LOGGER = Loggers.getLogger(AbstractLevelLoader.class.getName()); + + // ///////////////// Status objects /////////////////// + final DoomMain DOOM; + + // + // MAP related Lookup tables. + // Store VERTEXES, LINEDEFS, SIDEDEFS, etc. + // + public int numvertexes; + public vertex_t[] vertexes; + public int numsegs; + public seg_t[] segs; + public int numsectors; + public sector_t[] sectors; + public int numsubsectors; + public subsector_t[] subsectors; + public int numnodes; + public node_t[] nodes; + public int numlines; + public line_t[] lines; + public int numsides; + public side_t[] sides; + + // BLOCKMAP + // Created from axis aligned bounding box + // of the map, a rectangular array of + // blocks of size ... + // Used to speed up collision detection + // by spatial subdivision in 2D. + // + /** Blockmap size. */ + public int bmapwidth; // size in mapblocks + + public int bmapheight; // size in mapblocks + + /** + * killough 3/1/98: remove blockmap limit internally. Maes 29/9/2011: Header + * stripped during loading and pointers pre-modified, so there's no double + * arraying. + */ + public int[] blockmap; // was short -- killough + + /** + * Maes 29/9/2011: Used only during loading. No more dichotomy with + * blockmap. + */ + protected int[] blockmaplump; // was short -- killough + + @fixed_t + public int bmaporgx, bmaporgy; + + /** for thing chains */ + public mobj_t[] blocklinks; + + /** + * REJECT For fast sight rejection. Speeds up enemy AI by skipping detailed + * LineOf Sight calculation. Without special effect, this could be used as a + * PVS lookup as well. + */ + public byte[] rejectmatrix; + + // Maintain single and multi player starting spots. + // 1/11/98 killough: Remove limit on deathmatch starts + protected mapthing_t[] deathmatchstarts; // killough + + protected int num_deathmatchstarts; // killough + + // mapthing_t* deathmatch_p; + protected int deathmatch_p; + + protected mapthing_t[] playerstarts = new mapthing_t[Limits.MAXPLAYERS]; + + public AbstractLevelLoader(DoomMain DOOM) { + this.DOOM = DOOM; + } + + /** + * P_SetThingPosition Links a thing into both a block and a subsector based + * on it's x y. Sets thing.subsector properly + */ + @Override + @SourceCode.Exact + @P_MapUtl.C(P_SetThingPosition) + public void SetThingPosition(mobj_t thing) { + final subsector_t ss; + final sector_t sec; + int blockx; + int blocky; + final mobj_t link; + + // link into subsector + R_PointInSubsector: + { + ss = PointInSubsector(thing.x, thing.y); + } + thing.subsector = ss; + + if (!flags(thing.flags, MF_NOSECTOR)) { + // invisible things don't go into the sector links + sec = ss.sector; + + thing.sprev = null; + thing.snext = sec.thinglist; + + if (sec.thinglist != null) { + sec.thinglist.sprev = thing; + } + + sec.thinglist = thing; + } + + // link into blockmap + if (!flags(thing.flags, MF_NOBLOCKMAP)) { + // inert things don't need to be in blockmap + blockx = getSafeBlockX(thing.x - bmaporgx); + blocky = getSafeBlockY(thing.y - bmaporgy); + + // Valid block? + if (blockx >= 0 + && blockx < bmapwidth + && blocky >= 0 + && blocky < bmapheight) { + // Get said block. + link = blocklinks[blocky * bmapwidth + blockx]; + thing.bprev = null; // Thing is put at head of block... + thing.bnext = link; + if (link != null) { // block links back at thing... + // This will work + link.bprev = thing; + } + + // "thing" is now effectively the new head + // Iterators only follow "bnext", not "bprev". + // If link was null, then thing is the first entry. + blocklinks[blocky * bmapwidth + blockx] = thing; + } else { + // thing is off the map + thing.bnext = thing.bprev = null; + } + } + + } + + @Override + @SourceCode.Exact + @R_Main.C(R_PointInSubsector) + public subsector_t PointInSubsector(@fixed_t int x, @fixed_t int y) { + node_t node; + int side; + int nodenum; + + // single subsector is a special case + if (numnodes == 0) { + return subsectors[0]; + } + + nodenum = numnodes - 1; + + while (!C2JUtils.flags(nodenum, NF_SUBSECTOR)) { + node = nodes[nodenum]; + R_PointOnSide: + { + side = node.PointOnSide(x, y); + } + nodenum = node.children[side]; + } + + return subsectors[nodenum & ~NF_SUBSECTOR]; + } + + // + // jff 10/6/98 + // New code added to speed up calculation of internal blockmap + // Algorithm is order of nlines*(ncols+nrows) not nlines*ncols*nrows + // + /** + * places to shift rel position for cell num + */ + protected static final int BLOCK_SHIFT = 7; + + /** + * mask for rel position within cell + */ + protected static final int BLOCK_MASK = ((1 << BLOCK_SHIFT) - 1); + + /** + * size guardband around map used + */ + protected static final int BLOCK_MARGIN = 0; + + // jff 10/8/98 use guardband>0 + // jff 10/12/98 0 ok with + 1 in rows,cols + protected class linelist_t // type used to list lines in each block + { + + public int num; + + public linelist_t next; + } + + /** + * Subroutine to add a line number to a block list It simply returns if the + * line is already in the block + * + * @param lists + * @param count + * @param done + * @param blockno + * @param lineno + */ + private void AddBlockLine(linelist_t[] lists, int[] count, boolean[] done, int blockno, int lineno) { + long a = System.nanoTime(); + linelist_t l; + + if (done[blockno]) { + return; + } + + l = new linelist_t(); + l.num = lineno; + l.next = lists[blockno]; + lists[blockno] = l; + count[blockno]++; + done[blockno] = true; + long b = System.nanoTime(); + + total += (b - a); + } + + long total = 0; + + /** + * Actually construct the blockmap lump from the level data This finds the + * intersection of each linedef with the column and row lines at the left + * and bottom of each blockmap cell. It then adds the line to all block + * lists touching the intersection. MAES 30/9/2011: Converted to Java. It's + * important that LINEDEFS and VERTEXES are already read-in and defined, so + * it is necessary to change map lump ordering for this to work. + */ + protected final void CreateBlockMap() { + int xorg, yorg; // blockmap origin (lower left) + int nrows, ncols; // blockmap dimensions + linelist_t[] blocklists; // array of pointers to lists of lines + int[] blockcount; // array of counters of line lists + boolean[] blockdone; // array keeping track of blocks/line + int NBlocks; // number of cells = nrows*ncols + int linetotal; // total length of all blocklists + int map_minx = Integer.MAX_VALUE; // init for map limits search + int map_miny = Integer.MAX_VALUE; + int map_maxx = Integer.MIN_VALUE; + int map_maxy = Integer.MIN_VALUE; + + long a = System.nanoTime(); + + // scan for map limits, which the blockmap must enclose + for (int i = 0; i < numvertexes; i++) { + int t; + + if ((t = vertexes[i].x) < map_minx) { + map_minx = t; + } else if (t > map_maxx) { + map_maxx = t; + } + if ((t = vertexes[i].y) < map_miny) { + map_miny = t; + } else if (t > map_maxy) { + map_maxy = t; + } + } + map_minx >>= FRACBITS; // work in map coords, not fixed_t + map_maxx >>= FRACBITS; + map_miny >>= FRACBITS; + map_maxy >>= FRACBITS; + + // set up blockmap area to enclose level plus margin + xorg = map_minx - BLOCK_MARGIN; + yorg = map_miny - BLOCK_MARGIN; + ncols = (map_maxx + BLOCK_MARGIN - xorg + 1 + BLOCK_MASK) >> BLOCK_SHIFT; // jff + // 10/12/98 + nrows = (map_maxy + BLOCK_MARGIN - yorg + 1 + BLOCK_MASK) >> BLOCK_SHIFT; // +1 + // needed + // for + NBlocks = ncols * nrows; // map exactly 1 cell + + // create the array of pointers on NBlocks to blocklists + // also create an array of linelist counts on NBlocks + // finally make an array in which we can mark blocks done per line + // CPhipps - calloc's + blocklists = new linelist_t[NBlocks]; + blockcount = new int[NBlocks]; + blockdone = new boolean[NBlocks]; + + // initialize each blocklist, and enter the trailing -1 in all + // blocklists + // note the linked list of lines grows backwards + for (int i = 0; i < NBlocks; i++) { + blocklists[i] = new linelist_t(); + blocklists[i].num = -1; + blocklists[i].next = null; + blockcount[i]++; + } + + // For each linedef in the wad, determine all blockmap blocks it + // touches, + // and add the linedef number to the blocklists for those blocks + for (int i = 0; i < numlines; i++) { + int x1 = lines[i].v1x >> FRACBITS; // lines[i] map coords + int y1 = lines[i].v1y >> FRACBITS; + int x2 = lines[i].v2x >> FRACBITS; + int y2 = lines[i].v2y >> FRACBITS; + int dx = x2 - x1; + int dy = y2 - y1; + boolean vert = dx == 0; // lines[i] slopetype + boolean horiz = dy == 0; + boolean spos = (dx ^ dy) > 0; + boolean sneg = (dx ^ dy) < 0; + int bx, by; // block cell coords + int minx = x1 > x2 ? x2 : x1; // extremal lines[i] coords + int maxx = x1 > x2 ? x1 : x2; + int miny = y1 > y2 ? y2 : y1; + int maxy = y1 > y2 ? y1 : y2; + + // no blocks done for this linedef yet + C2JUtils.memset(blockdone, false, NBlocks); + + // The line always belongs to the blocks containing its endpoints + bx = (x1 - xorg) >> BLOCK_SHIFT; + by = (y1 - yorg) >> BLOCK_SHIFT; + AddBlockLine(blocklists, blockcount, blockdone, by * ncols + bx, i); + bx = (x2 - xorg) >> BLOCK_SHIFT; + by = (y2 - yorg) >> BLOCK_SHIFT; + AddBlockLine(blocklists, blockcount, blockdone, by * ncols + bx, i); + + // For each column, see where the line along its left edge, which + // it contains, intersects the Linedef i. Add i to each + // corresponding + // blocklist. + if (!vert) // don't interesect vertical lines with columns + { + for (int j = 0; j < ncols; j++) { + // intersection of Linedef with x=xorg+(j<> BLOCK_SHIFT; // block row number + int yp = (y - yorg) & BLOCK_MASK; // y position within block + + if (yb < 0 || yb > nrows - 1) // outside blockmap, continue + { + continue; + } + + if (x < minx || x > maxx) // line doesn't touch column + { + continue; + } + + // The cell that contains the intersection point is always + // added + AddBlockLine(blocklists, blockcount, blockdone, ncols * yb + + j, i); + + // if the intersection is at a corner it depends on the + // slope + // (and whether the line extends past the intersection) + // which + // blocks are hit + if (yp == 0) // intersection at a corner + { + if (sneg) // \ - blocks x,y-, x-,y + { + if (yb > 0 && miny < y) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * (yb - 1) + j, i); + } + if (j > 0 && minx < x) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * yb + j - 1, i); + } + } else if (spos) // / - block x-,y- + { + if (yb > 0 && j > 0 && minx < x) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * (yb - 1) + j - 1, i); + } + } else if (horiz) // - - block x-,y + { + if (j > 0 && minx < x) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * yb + j - 1, i); + } + } + } else if (j > 0 && minx < x) // else not at corner: x-,y + { + AddBlockLine(blocklists, blockcount, blockdone, ncols + * yb + j - 1, i); + } + } + } + + // For each row, see where the line along its bottom edge, which + // it contains, intersects the Linedef i. Add i to all the + // corresponding + // blocklists. + if (!horiz) { + for (int j = 0; j < nrows; j++) { + // intersection of Linedef with y=yorg+(j<> BLOCK_SHIFT; // block column number + int xp = (x - xorg) & BLOCK_MASK; // x position within block + + if (xb < 0 || xb > ncols - 1) // outside blockmap, continue + { + continue; + } + + if (y < miny || y > maxy) // line doesn't touch row + { + continue; + } + + // The cell that contains the intersection point is always + // added + AddBlockLine(blocklists, blockcount, blockdone, ncols * j + + xb, i); + + // if the intersection is at a corner it depends on the + // slope + // (and whether the line extends past the intersection) + // which + // blocks are hit + if (xp == 0) // intersection at a corner + { + if (sneg) // \ - blocks x,y-, x-,y + { + if (j > 0 && miny < y) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * (j - 1) + xb, i); + } + if (xb > 0 && minx < x) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * j + xb - 1, i); + } + } else if (vert) // | - block x,y- + { + if (j > 0 && miny < y) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * (j - 1) + xb, i); + } + } else if (spos) // / - block x-,y- + { + if (xb > 0 && j > 0 && miny < y) { + AddBlockLine(blocklists, blockcount, blockdone, + ncols * (j - 1) + xb - 1, i); + } + } + } else if (j > 0 && miny < y) // else not on a corner: x,y- + { + AddBlockLine(blocklists, blockcount, blockdone, ncols + * (j - 1) + xb, i); + } + } + } + } + + // Add initial 0 to all blocklists + // count the total number of lines (and 0's and -1's) + C2JUtils.memset(blockdone, false, NBlocks); + + linetotal = 0; + + for (int i = 0; i < NBlocks; i++) { + AddBlockLine(blocklists, blockcount, blockdone, i, 0); + linetotal += blockcount[i]; + } + + // Create the blockmap lump + // blockmaplump = malloc_IfSameLevel(blockmaplump, 4 + NBlocks + + // linetotal); + blockmaplump = new int[(4 + NBlocks + linetotal)]; + // blockmap header + + blockmaplump[0] = bmaporgx = xorg << FRACBITS; + blockmaplump[1] = bmaporgy = yorg << FRACBITS; + blockmaplump[2] = bmapwidth = ncols; + blockmaplump[3] = bmapheight = nrows; + + // offsets to lists and block lists + for (int i = 0; i < NBlocks; i++) { + linelist_t bl = blocklists[i]; + int offs + = blockmaplump[4 + i] + = // set offset to block's list + (i != 0 ? blockmaplump[4 + i - 1] : 4 + NBlocks) + + (i != 0 ? blockcount[i - 1] : 0); + + // add the lines in each block's list to the blockmaplump + // delete each list node as we go + while (bl != null) { + linelist_t tmp = bl.next; + blockmaplump[offs++] = bl.num; + bl = tmp; + } + } + + long b = System.nanoTime(); + + LOGGER.log(Level.WARNING, String.format("Blockmap generated in %f sec", (b - a) / 1e9)); + LOGGER.log(Level.WARNING, String.format("Time spend in AddBlockLine : %f sec", total / 1e9)); + } + + // jff 10/6/98 + // End new code added to speed up calculation of internal blockmap + // + // P_VerifyBlockMap + // + // haleyjd 03/04/10: do verification on validity of blockmap. + // + protected boolean VerifyBlockMap(int count) { + int x, y; + int p_maxoffs = count; + + for (y = 0; y < bmapheight; y++) { + for (x = 0; x < bmapwidth; x++) { + int offset; + int p_list; + int tmplist; + int blockoffset; + + offset = y * bmapwidth + x; + blockoffset = offset + 4; // That's where the shit starts. + + // check that block offset is in bounds + if (blockoffset >= p_maxoffs) { + LOGGER.log(Level.WARNING, String.format("P_VerifyBlockMap: block offset overflow")); + return false; + } + + offset = blockmaplump[blockoffset]; + + // check that list offset is in bounds + if (offset < 4 || offset >= count) { + LOGGER.log(Level.WARNING, String.format("P_VerifyBlockMap: list offset overflow")); + return false; + } + + p_list = offset; + + // scan forward for a -1 terminator before maxoffs + for (tmplist = p_list;; tmplist++) { + // we have overflowed the lump? + if (tmplist >= p_maxoffs) { + LOGGER.log(Level.WARNING, String.format("P_VerifyBlockMap: open blocklist")); + return false; + } + if (blockmaplump[tmplist] == -1) // found -1 + { + break; + } + } + + // scan the list for out-of-range linedef indicies in list + for (tmplist = p_list; blockmaplump[tmplist] != -1; tmplist++) { + if (blockmaplump[tmplist] < 0 || blockmaplump[tmplist] >= numlines) { + LOGGER.log(Level.WARNING, String.format("P_VerifyBlockMap: index >= numlines")); + return false; + } + } + } + } + + return true; + } + + // cph - convenient sub-function + protected void AddLineToSector(line_t li, sector_t sector) { + int[] bbox = sector.blockbox; + + sector.lines[sector.linecount++] = li; + BBox.AddToBox(bbox, li.v1.x, li.v1.y); + BBox.AddToBox(bbox, li.v2.x, li.v2.y); + } + + /** + * Compute density of reject table. Aids choosing LUT optimizations. + * + * @return + */ + protected float rejectDensity() { + // float[] rowdensity=new float[numsectors]; + float tabledensity; + int tcount = 0; + + for (int i = 0; i < numsectors; i++) { + // int colcount=0; + for (int j = 0; j < numsectors; j++) { + // Determine subsector entries in REJECT table. + int pnum = i * numsectors + j; + int bytenum = pnum >> 3; + int bitnum = 1 << (pnum & 7); + + // Check in REJECT table. + if (!flags(rejectmatrix[bytenum], bitnum)) { + tcount++; + // colcount++; + } + } + // rowdensity[i]=((float)colcount/numsectors); + } + + tabledensity = (float) tcount / (numsectors * numsectors); + return tabledensity; + + } + + protected static int[] POKE_REJECT = new int[]{1, 2, 4, 8, 16, 32, 64, + 128}; + + /** + * Updates Reject table dynamically based on what expensive LOS checks say. + * It does decrease the "reject density" the longer the level runs, however + * its by no means perfect, and results in many sleeping monsters. When + * called, visibility between sectors x and y will be set to "false" for the + * rest of the level, aka they will be rejected based on subsequent sight + * checks. + * + * @param x + * @param y + */ + protected void pokeIntoReject(int x, int y) { + // Locate bit pointer e.g. for a 4x4 table, x=2 and y=3 give + // 3*4+2=14 + final int pnum = y * numsectors + x; + + // Which byte? + // 14= 1110 >>3 = 0001 so + // Byte 0 Byte 1 + // xxxxxxxx xxxxxxxx + // ^ + // 0.....bits......16 + // We are writing inside the second Byte 1 + final int bytenum = pnum >> 3; + + // OK, so how we pinpoint that one bit? + // 1110 & 0111 = 0110 = 6 so it's the sixth bit + // of the second byte + final int bitnum = pnum & 7; + + // This sets only that one bit, and the reject lookup will be faster + // next time. + rejectmatrix[bytenum] |= POKE_REJECT[bitnum]; + + rejectDensity(); + } + + protected void retrieveFromReject(int x, int y, boolean value) { + // Locate bit pointer e.g. for a 4x4 table, x=2 and y=3 give + // 3*4+2=14 + final int pnum = y * numsectors + x; + + // Which byte? + // 14= 1110 >>3 = 0001 so + // Byte 0 Byte 1 + // xxxxxxxx xxxxxxxx + // ^ + // 0.....bits......16 + // We are writing inside the second Byte 1 + final int bytenum = pnum >> 3; + + // OK, so how we pinpoint that one bit? + // 1110 & 0111 = 0110 = 6 so it's the sixth bit + // of the second byte + final int bitnum = pnum & 7; + + // This sets only that one bit, and the reject lookup will be faster + // next time. + rejectmatrix[bytenum] |= POKE_REJECT[bitnum]; + + rejectDensity(); + + } + + // Keeps track of lines that belong to a sector, to exclude e.g. + // orphaned ones from the blockmap. + protected boolean[] used_lines; + + // MAES: extensions to support 512x512 blockmaps. + // They represent the maximum negative number which represents + // a positive offset, otherwise they are left at -257, which + // never triggers a check. + // If a blockmap index is ever LE than either, then + // its actual value is to be interpreted as 0x01FF&x. + // Full 512x512 blockmaps get this value set to -1. + // A 511x511 blockmap would still have a valid negative number + // e.g. -1..510, so they would be set to -2 + public static final boolean FIX_BLOCKMAP_512 = Engine.getConfig().equals(Settings.fix_blockmap, Boolean.TRUE); + public int blockmapxneg = -257; + public int blockmapyneg = -257; + + /** + * Returns an int[] array with orgx, orgy, and number of blocks. Order is: + * orgx,orgy,bckx,bcky + * + * @return + */ + protected final int[] getMapBoundingBox(boolean playable) { + + int minx = Integer.MAX_VALUE; + int miny = Integer.MAX_VALUE; + int maxx = Integer.MIN_VALUE; + int maxy = Integer.MIN_VALUE; + + // Scan linedefs to detect extremes + for (int i = 0; i < this.lines.length; i++) { + + if (playable || used_lines[i]) { + if (lines[i].v1x > maxx) { + maxx = lines[i].v1x; + } + if (lines[i].v1x < minx) { + minx = lines[i].v1x; + } + if (lines[i].v1y > maxy) { + maxy = lines[i].v1y; + } + if (lines[i].v1y < miny) { + miny = lines[i].v1y; + } + if (lines[i].v2x > maxx) { + maxx = lines[i].v2x; + } + if (lines[i].v2x < minx) { + minx = lines[i].v2x; + } + if (lines[i].v2y > maxy) { + maxy = lines[i].v2y; + } + if (lines[i].v2y < miny) { + miny = lines[i].v2y; + } + } + } + + LOGGER.log(Level.WARNING, String.format("Map bounding %d %d %d %d\n", minx >> FRACBITS, + miny >> FRACBITS, maxx >> FRACBITS, maxy >> FRACBITS)); + + // Blow up bounding to the closest 128-sized block, adding 8 units as + // padding. + // This seems to be the "official" formula. + int orgx = -BLOCKMAPPADDING + MAPBLOCKUNITS * (minx / MAPBLOCKUNITS); + int orgy = -BLOCKMAPPADDING + MAPBLOCKUNITS * (miny / MAPBLOCKUNITS); + int bckx = ((BLOCKMAPPADDING + maxx) - orgx); + int bcky = ((BLOCKMAPPADDING + maxy) - orgy); + + LOGGER.log(Level.WARNING, String.format("%d %d %d %d", orgx >> FRACBITS, orgy >> FRACBITS, + 1 + (bckx >> MAPBLOCKSHIFT), 1 + (bcky >> MAPBLOCKSHIFT))); + + return new int[]{orgx, orgy, bckx, bcky}; + } + + protected void LoadReject(int lumpnum) { + byte[] tmpreject = new byte[0]; + + // _D_: uncommented the rejectmatrix variable, this permitted changing + // level to work + try { + tmpreject = DOOM.wadLoader.CacheLumpNumAsRawBytes(lumpnum, PU_LEVEL); + } catch (Exception e) { + // Any exception at this point means missing REJECT lump. Fuck that, + // and move on. + // If everything goes OK, tmpreject will contain the REJECT lump's + // data + // BUT, alas, we're not done yet. + } + + // Sanity check on matrix. + // E.g. a 5-sector map will result in ceil(25/8)=4 bytes. + // If the reject table is broken/corrupt, too bad. It will all be + // zeroes. + // Much better than overflowing. + // TODO: build-in a REJECT-matrix rebuilder? + rejectmatrix + = new byte[(int) (Math + .ceil((this.numsectors * this.numsectors) / 8.0))]; + System.arraycopy(tmpreject, 0, rejectmatrix, 0, + Math.min(tmpreject.length, rejectmatrix.length)); + + // Do warn on atypical reject map lengths, but use either default + // all-zeroes one, + // or whatever you happened to read anyway. + if (tmpreject.length < rejectmatrix.length) { + LOGGER.log(Level.WARNING, String.format("BROKEN REJECT MAP! Length %d expected %d\n", + tmpreject.length, rejectmatrix.length)); + } + + // Maes: purely academic. Most maps are well above 0.68 + // System.out.printf("Reject table density: %f",rejectDensity()); + } + + /** + * Added config switch to turn on/off support + * + * Gets the proper blockmap block for a given X 16.16 Coordinate, sanitized + * for 512-wide blockmaps. + * + * @param blockx + * @return + */ + @SourceCode.Compatible("blockx >> MAPBLOCKSHIFT") + public final int getSafeBlockX(int blockx) { + blockx >>= MAPBLOCKSHIFT; + return (FIX_BLOCKMAP_512 && blockx <= this.blockmapxneg) ? blockx & 0x1FF : blockx; + } + + @SourceCode.Compatible("blockx >> MAPBLOCKSHIFT") + public final int getSafeBlockX(long blockx) { + blockx >>= MAPBLOCKSHIFT; + return (int) ((FIX_BLOCKMAP_512 && blockx <= this.blockmapxneg) ? blockx & 0x1FF : blockx); + } + + /** Gets the proper blockmap block for a given Y 16.16 Coordinate, sanitized + * for 512-wide blockmaps. + * + * @param blocky + * @return */ + @SourceCode.Compatible("blocky >> MAPBLOCKSHIFT") + public final int getSafeBlockY(int blocky) { + blocky >>= MAPBLOCKSHIFT; + return (FIX_BLOCKMAP_512 && blocky <= this.blockmapyneg) ? blocky & 0x1FF : blocky; + } + + @SourceCode.Compatible("blocky >> MAPBLOCKSHIFT") + public final int getSafeBlockY(long blocky) { + blocky >>= MAPBLOCKSHIFT; + return (int) ((FIX_BLOCKMAP_512 && blocky <= this.blockmapyneg) ? blocky & 0x1FF : blocky); + } + + /// Sector tag stuff, lifted off Boom + /** Hash the sector tags across the sectors and linedefs. + * Call in SpawnSpecials. + */ + public void InitTagLists() { + int i; + + for (i = numsectors; --i >= 0;) // Initially make all slots empty. + { + sectors[i].firsttag = -1; + } + for (i = numsectors; --i >= 0;) // Proceed from last to first sector + { // so that lower sectors appear first + int j = sectors[i].tag % numsectors; // Hash func + sectors[i].nexttag = sectors[j].firsttag; // Prepend sector to chain + sectors[j].firsttag = i; + } + + // killough 4/17/98: same thing, only for linedefs + for (i = numlines; --i >= 0;) // Initially make all slots empty. + { + lines[i].firsttag = -1; + } + for (i = numlines; --i >= 0;) // Proceed from last to first linedef + { // so that lower linedefs appear first + int j = lines[i].tag % numlines; // Hash func + lines[i].nexttag = lines[j].firsttag; // Prepend linedef to chain + lines[j].firsttag = i; + } + } + +} \ No newline at end of file diff --git a/doom/src/p/ActionFunctions.java b/doom/src/p/ActionFunctions.java new file mode 100644 index 0000000..b43bc94 --- /dev/null +++ b/doom/src/p/ActionFunctions.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p; + +import automap.IAutoMap; +import data.sounds; +import defines.skill_t; +import doom.DoomMain; +import doom.player_t; +import hu.IHeadsUp; +import i.IDoomSystem; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.Actions.ActionsAttacks; +import p.Actions.ActionsEnemies; +import p.Actions.ActionsThinkers; +import p.Actions.ActiveStates.Ai; +import p.Actions.ActiveStates.Attacks; +import p.Actions.ActiveStates.Thinkers; +import p.Actions.ActiveStates.Weapons; +import rr.SceneRenderer; +import s.ISoundOrigin; +import st.IDoomStatusBar; +import utils.TraitFactory; +import utils.TraitFactory.SharedContext; + +public class ActionFunctions extends UnifiedGameMap implements + ActionsThinkers, ActionsEnemies, ActionsAttacks, Ai, Attacks, Thinkers, Weapons { + + private static final Logger LOGGER = Loggers.getLogger(ActionFunctions.class.getName()); + + private final SharedContext traitsSharedContext; + + public ActionFunctions(final DoomMain DOOM) { + super(DOOM); + this.traitsSharedContext = buildContext(); + } + + private SharedContext buildContext() { + try { + return TraitFactory.build(this, ACTION_KEY_CHAIN); + } catch (IllegalArgumentException | IllegalAccessException ex) { + LOGGER.log(Level.SEVERE, "buildContext failure", ex); + throw new RuntimeException(ex); + } + } + + @Override + public AbstractLevelLoader levelLoader() { + return DOOM.levelLoader; + } + + @Override + public IHeadsUp headsUp() { + return DOOM.headsUp; + } + + @Override + public IDoomSystem doomSystem() { + return DOOM.doomSystem; + } + + @Override + public IDoomStatusBar statusBar() { + return DOOM.statusBar; + } + + @Override + public IAutoMap autoMap() { + return DOOM.autoMap; + } + + @Override + public SceneRenderer sceneRenderer() { + return DOOM.sceneRenderer; + } + + @Override + public UnifiedGameMap.Specials getSpecials() { + return SPECS; + } + + @Override + public UnifiedGameMap.Switches getSwitches() { + return SW; + } + + @Override + public void StopSound(ISoundOrigin origin) { + DOOM.doomSound.StopSound(origin); + } + + @Override + public void StartSound(ISoundOrigin origin, sounds.sfxenum_t s) { + DOOM.doomSound.StartSound(origin, s); + } + + @Override + public void StartSound(ISoundOrigin origin, int s) { + DOOM.doomSound.StartSound(origin, s); + } + + @Override + public player_t getPlayer(int number) { + return DOOM.players[number]; + } + + @Override + public skill_t getGameSkill() { + return DOOM.gameskill; + } + + @Override + public mobj_t createMobj() { + return mobj_t.createOn(DOOM); + } + + @Override + public int LevelTime() { + return DOOM.leveltime; + } + + @Override + public int P_Random() { + return DOOM.random.P_Random(); + } + + @Override + public int ConsolePlayerNumber() { + return DOOM.consoleplayer; + } + + @Override + public int MapNumber() { + return DOOM.gamemap; + } + + @Override + public boolean PlayerInGame(int number) { + return DOOM.playeringame[number]; + } + + @Override + public boolean IsFastParm() { + return DOOM.fastparm; + } + + @Override + public boolean IsPaused() { + return DOOM.paused; + } + + @Override + public boolean IsNetGame() { + return DOOM.netgame; + } + + @Override + public boolean IsDemoPlayback() { + return DOOM.demoplayback; + } + + @Override + public boolean IsDeathMatch() { + return DOOM.deathmatch; + } + + @Override + public boolean IsAutoMapActive() { + return DOOM.automapactive; + } + + @Override + public boolean IsMenuActive() { + return DOOM.menuactive; + } + + /** + * TODO: avoid, deprecate + */ + @Override + public DoomMain DOOM() { + return DOOM; + } + + @Override + public SharedContext getContext() { + return traitsSharedContext; + } + + @Override + public ActionsThinkers getThinkers() { + return this; + } + + @Override + public ActionsEnemies getEnemies() { + return this; + } + + @Override + public ActionsAttacks getAttacks() { + return this; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionTrait.java b/doom/src/p/Actions/ActionTrait.java new file mode 100644 index 0000000..9d17743 --- /dev/null +++ b/doom/src/p/Actions/ActionTrait.java @@ -0,0 +1,561 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import automap.IAutoMap; +import static data.Limits.MAXRADIUS; +import static data.Limits.MAXSPECIALCROSS; +import data.sounds; +import defines.skill_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PIT_CheckLine; +import static doom.SourceCode.P_Map.P_CheckPosition; +import doom.SourceCode.P_MapUtl; +import static doom.SourceCode.P_MapUtl.P_BlockLinesIterator; +import static doom.SourceCode.P_MapUtl.P_BlockThingsIterator; +import doom.SourceCode.fixed_t; +import doom.player_t; +import hu.IHeadsUp; +import i.IDoomSystem; +import java.util.function.Predicate; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import p.AbstractLevelLoader; +import static p.AbstractLevelLoader.FIX_BLOCKMAP_512; +import p.ThinkerList; +import p.UnifiedGameMap; +import p.intercept_t; +import p.mobj_t; +import static p.mobj_t.MF_MISSILE; +import static p.mobj_t.MF_NOCLIP; +import rr.SceneRenderer; +import rr.line_t; +import static rr.line_t.ML_BLOCKING; +import static rr.line_t.ML_BLOCKMONSTERS; +import rr.sector_t; +import rr.subsector_t; +import s.ISoundOrigin; +import st.IDoomStatusBar; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import utils.TraitFactory; +import utils.TraitFactory.ContextKey; +import utils.TraitFactory.Trait; + +public interface ActionTrait extends Trait, ThinkerList { + + TraitFactory.KeyChain ACTION_KEY_CHAIN = new TraitFactory.KeyChain(); + + ContextKey KEY_SLIDEMOVE = ACTION_KEY_CHAIN.newKey(ActionTrait.class, SlideMove::new); + ContextKey KEY_SPECHITS = ACTION_KEY_CHAIN.newKey(ActionTrait.class, Spechits::new); + ContextKey KEY_MOVEMENT = ACTION_KEY_CHAIN.newKey(ActionTrait.class, Movement::new); + + AbstractLevelLoader levelLoader(); + + IHeadsUp headsUp(); + + IDoomSystem doomSystem(); + + IDoomStatusBar statusBar(); + + IAutoMap autoMap(); + + SceneRenderer sceneRenderer(); + + UnifiedGameMap.Specials getSpecials(); + + UnifiedGameMap.Switches getSwitches(); + + ActionsThinkers getThinkers(); + + ActionsEnemies getEnemies(); + + ActionsAttacks getAttacks(); + + void StopSound(ISoundOrigin origin); // DOOM.doomSound.StopSound + + void StartSound(ISoundOrigin origin, sounds.sfxenum_t s); // DOOM.doomSound.StartSound + + void StartSound(ISoundOrigin origin, int s); // DOOM.doomSound.StartSound + + player_t getPlayer(int number); //DOOM.players[] + + skill_t getGameSkill(); // DOOM.gameskill + + mobj_t createMobj(); // mobj_t.from(DOOM); + + int LevelTime(); // DOOM.leveltime + + int P_Random(); + + int ConsolePlayerNumber(); // DOOM.consoleplayer + + int MapNumber(); // DOOM.gamemap + + boolean PlayerInGame(int number); // DOOM.palyeringame + + boolean IsFastParm(); // DOOM.fastparm + + boolean IsPaused(); // DOOM.paused + + boolean IsNetGame(); // DOOM.netgame + + boolean IsDemoPlayback(); // DOOM.demoplayback + + boolean IsDeathMatch(); // DOOM.deathmatch + + boolean IsAutoMapActive(); // DOOM.automapactive + + boolean IsMenuActive(); // DOOM.menuactive + + boolean CheckThing(mobj_t m); + + boolean StompThing(mobj_t m); + + default void SetThingPosition(mobj_t mobj) { + levelLoader().SetThingPosition(mobj); + } + + /** + * Try to avoid. + */ + DoomMain DOOM(); + + final class SlideMove { + + // + // SLIDE MOVE + // Allows the player to slide along any angled walls. + // + mobj_t slidemo; + + @fixed_t + int bestslidefrac, secondslidefrac; + + line_t bestslideline, secondslideline; + + @fixed_t + int tmxmove, tmymove; + } + + final class Spechits { + + line_t[] spechit = new line_t[MAXSPECIALCROSS]; + int numspechit; + + // + // USE LINES + // + mobj_t usething; + } + + ///////////////// MOVEMENT'S ACTIONS //////////////////////// + final class Movement { + + /** + * If "floatok" true, move would be ok if within "tmfloorz - tmceilingz". + */ + public boolean floatok; + + @fixed_t + public int tmfloorz, + tmceilingz, + tmdropoffz; + + // keep track of the line that lowers the ceiling, + // so missiles don't explode against sky hack walls + public line_t ceilingline; + @fixed_t + int[] tmbbox = new int[4]; + + mobj_t tmthing; + + long tmflags; + + @fixed_t + int tmx, tmy; + + ////////////////////// FROM p_maputl.c //////////////////// + @fixed_t + int opentop, openbottom, openrange, lowfloor; + } + + /** + * P_LineOpening Sets opentop and openbottom to the window through a two + * sided line. OPTIMIZE: keep this precalculated + */ + default void LineOpening(line_t linedef) { + final Movement ma = contextRequire(KEY_MOVEMENT); + sector_t front; + sector_t back; + + if (linedef.sidenum[1] == line_t.NO_INDEX) { + // single sided line + ma.openrange = 0; + return; + } + + front = linedef.frontsector; + back = linedef.backsector; + + if (front.ceilingheight < back.ceilingheight) { + ma.opentop = front.ceilingheight; + } else { + ma.opentop = back.ceilingheight; + } + + if (front.floorheight > back.floorheight) { + ma.openbottom = front.floorheight; + ma.lowfloor = back.floorheight; + } else { + ma.openbottom = back.floorheight; + ma.lowfloor = front.floorheight; + } + + ma.openrange = ma.opentop - ma.openbottom; + } + + // + //P_BlockThingsIterator + // + @SourceCode.Exact + @P_MapUtl.C(P_BlockThingsIterator) + default boolean BlockThingsIterator(int x, int y, Predicate func) { + final AbstractLevelLoader ll = levelLoader(); + mobj_t mobj; + + if (x < 0 || y < 0 || x >= ll.bmapwidth || y >= ll.bmapheight) { + return true; + } + + for (mobj = ll.blocklinks[y * ll.bmapwidth + x]; mobj != null; mobj = (mobj_t) mobj.bnext) { + if (!func.test(mobj)) { + return false; + } + } + return true; + } + + // + // SECTOR HEIGHT CHANGING + // After modifying a sectors floor or ceiling height, + // call this routine to adjust the positions + // of all things that touch the sector. + // + // If anything doesn't fit anymore, true will be returned. + // If crunch is true, they will take damage + // as they are being crushed. + // If Crunch is false, you should set the sector height back + // the way it was and call P_ChangeSector again + // to undo the changes. + // + /** + * P_BlockLinesIterator The validcount flags are used to avoid checking lines that are marked in multiple mapblocks, + * so increment validcount before the first call to P_BlockLinesIterator, then make one or more calls to it. + */ + @P_MapUtl.C(P_BlockLinesIterator) + default boolean BlockLinesIterator(int x, int y, Predicate func) { + final AbstractLevelLoader ll = levelLoader(); + final SceneRenderer sr = sceneRenderer(); + int offset; + int lineinblock; + line_t ld; + + if (x < 0 || y < 0 || x >= ll.bmapwidth || y >= ll.bmapheight) { + return true; + } + + // This gives us the index to look up (in blockmap) + offset = y * ll.bmapwidth + x; + + // The index contains yet another offset, but this time + offset = ll.blockmap[offset]; + + // MAES: blockmap terminating marker is always -1 + @SourceCode.Compatible("validcount") + final int validcount = sr.getValidCount(); + + // [SYNC ISSUE]: don't skip offset+1 :-/ + for (@SourceCode.Compatible("list = blockmaplump+offset ; *list != -1 ; list++") int list = offset; (lineinblock = ll.blockmap[list]) != -1; list++) { + ld = ll.lines[lineinblock]; + //System.out.println(ld); + if (ld.validcount == validcount) { + continue; // line has already been checked + } + ld.validcount = validcount; + if (!func.test(ld)) { + return false; + } + } + return true; // everything was checked + } + + // keep track of the line that lowers the ceiling, + // so missiles don't explode against sky hack walls + default void ResizeSpechits() { + final Spechits spechits = contextRequire(KEY_SPECHITS); + spechits.spechit = C2JUtils.resize(spechits.spechit[0], spechits.spechit, spechits.spechit.length * 2); + } + + /** + * PIT_CheckLine Adjusts tmfloorz and tmceilingz as lines are contacted + * + */ + @P_Map.C(PIT_CheckLine) + default boolean CheckLine(line_t ld) { + final Spechits spechits = contextRequire(KEY_SPECHITS); + final Movement ma = contextRequire(KEY_MOVEMENT); + + if (ma.tmbbox[BOXRIGHT] <= ld.bbox[BOXLEFT] + || ma.tmbbox[BOXLEFT] >= ld.bbox[BOXRIGHT] + || ma.tmbbox[BOXTOP] <= ld.bbox[BOXBOTTOM] + || ma.tmbbox[BOXBOTTOM] >= ld.bbox[BOXTOP]) { + return true; + } + + if (ld.BoxOnLineSide(ma.tmbbox) != -1) { + return true; + } + + // A line has been hit + // The moving thing's destination position will cross + // the given line. + // If this should not be allowed, return false. + // If the line is special, keep track of it + // to process later if the move is proven ok. + // NOTE: specials are NOT sorted by order, + // so two special lines that are only 8 pixels apart + // could be crossed in either order. + if (ld.backsector == null) { + return false; // one sided line + } + if (!eval(ma.tmthing.flags & MF_MISSILE)) { + if (eval(ld.flags & ML_BLOCKING)) { + return false; // explicitly blocking everything + } + if ((ma.tmthing.player == null) && eval(ld.flags & ML_BLOCKMONSTERS)) { + return false; // block monsters only + } + } + + // set openrange, opentop, openbottom + LineOpening(ld); + + // adjust floor / ceiling heights + if (ma.opentop < ma.tmceilingz) { + ma.tmceilingz = ma.opentop; + ma.ceilingline = ld; + } + + if (ma.openbottom > ma.tmfloorz) { + ma.tmfloorz = ma.openbottom; + } + + if (ma.lowfloor < ma.tmdropoffz) { + ma.tmdropoffz = ma.lowfloor; + } + + // if contacted a special line, add it to the list + if (ld.special != 0) { + spechits.spechit[spechits.numspechit] = ld; + spechits.numspechit++; + // Let's be proactive about this. + if (spechits.numspechit >= spechits.spechit.length) { + this.ResizeSpechits(); + } + } + + return true; + } + + ; + + // + // MOVEMENT CLIPPING + // + /** + * P_CheckPosition This is purely informative, nothing is modified (except things picked up). + * + * in: a mobj_t (can be valid or invalid) a position to be checked (doesn't need to be related to the mobj_t.x,y) + * + * during: special things are touched if MF_PICKUP early out on solid lines? + * + * out: newsubsec floorz ceilingz tmdropoffz the lowest point contacted (monsters won't move to a dropoff) + * speciallines[] numspeciallines + * + * @param thing + * @param x fixed_t + * @param y fixed_t + */ + @SourceCode.Compatible + @P_Map.C(P_CheckPosition) + default boolean CheckPosition(mobj_t thing, @fixed_t int x, @fixed_t int y) { + final AbstractLevelLoader ll = levelLoader(); + final Spechits spechits = contextRequire(KEY_SPECHITS); + final Movement ma = contextRequire(KEY_MOVEMENT); + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + subsector_t newsubsec; + + ma.tmthing = thing; + ma.tmflags = thing.flags; + + ma.tmx = x; + ma.tmy = y; + + ma.tmbbox[BOXTOP] = y + ma.tmthing.radius; + ma.tmbbox[BOXBOTTOM] = y - ma.tmthing.radius; + ma.tmbbox[BOXRIGHT] = x + ma.tmthing.radius; + ma.tmbbox[BOXLEFT] = x - ma.tmthing.radius; + + R_PointInSubsector: + { + newsubsec = levelLoader().PointInSubsector(x, y); + } + ma.ceilingline = null; + + // The base floor / ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + ma.tmfloorz = ma.tmdropoffz = newsubsec.sector.floorheight; + ma.tmceilingz = newsubsec.sector.ceilingheight; + + sceneRenderer().increaseValidCount(1); + spechits.numspechit = 0; + + if (eval(ma.tmflags & MF_NOCLIP)) { + return true; + } + + // Check things first, possibly picking things up. + // The bounding box is extended by MAXRADIUS + // because mobj_ts are grouped into mapblocks + // based on their origin point, and can overlap + // into adjacent blocks by up to MAXRADIUS units. + xl = ll.getSafeBlockX(ma.tmbbox[BOXLEFT] - ll.bmaporgx - MAXRADIUS); + xh = ll.getSafeBlockX(ma.tmbbox[BOXRIGHT] - ll.bmaporgx + MAXRADIUS); + yl = ll.getSafeBlockY(ma.tmbbox[BOXBOTTOM] - ll.bmaporgy - MAXRADIUS); + yh = ll.getSafeBlockY(ma.tmbbox[BOXTOP] - ll.bmaporgy + MAXRADIUS); + + for (bx = xl; bx <= xh; bx++) { + for (by = yl; by <= yh; by++) { + P_BlockThingsIterator: + { + if (!BlockThingsIterator(bx, by, this::CheckThing)) { + return false; + } + } + } + } + + // check lines + xl = ll.getSafeBlockX(ma.tmbbox[BOXLEFT] - ll.bmaporgx); + xh = ll.getSafeBlockX(ma.tmbbox[BOXRIGHT] - ll.bmaporgx); + yl = ll.getSafeBlockY(ma.tmbbox[BOXBOTTOM] - ll.bmaporgy); + yh = ll.getSafeBlockY(ma.tmbbox[BOXTOP] - ll.bmaporgy); + + if (FIX_BLOCKMAP_512) { + // Maes's quick and dirty blockmap extension hack + // E.g. for an extension of 511 blocks, max negative is -1. + // A full 512x512 blockmap doesn't have negative indexes. + if (xl <= ll.blockmapxneg) { + xl = 0x1FF & xl; // Broke width boundary + } + if (xh <= ll.blockmapxneg) { + xh = 0x1FF & xh; // Broke width boundary + } + if (yl <= ll.blockmapyneg) { + yl = 0x1FF & yl; // Broke height boundary + } + if (yh <= ll.blockmapyneg) { + yh = 0x1FF & yh; // Broke height boundary + } + } + for (bx = xl; bx <= xh; bx++) { + for (by = yl; by <= yh; by++) { + P_BlockLinesIterator: + { + if (!this.BlockLinesIterator(bx, by, this::CheckLine)) { + return false; + } + } + } + } + + return true; + } + + // + // P_ThingHeightClip + // Takes a valid thing and adjusts the thing.floorz, + // thing.ceilingz, and possibly thing.z. + // This is called for all nearby monsters + // whenever a sector changes height. + // If the thing doesn't fit, + // the z will be set to the lowest value + // and false will be returned. + // + default boolean ThingHeightClip(mobj_t thing) { + final Movement ma = contextRequire(KEY_MOVEMENT); + boolean onfloor; + + onfloor = (thing.z == thing.floorz); + + this.CheckPosition(thing, thing.x, thing.y); + // what about stranding a monster partially off an edge? + + thing.floorz = ma.tmfloorz; + thing.ceilingz = ma.tmceilingz; + + if (onfloor) { + // walking monsters rise and fall with the floor + thing.z = thing.floorz; + } else { + // don't adjust a floating monster unless forced to + if (thing.z + thing.height > thing.ceilingz) { + thing.z = thing.ceilingz - thing.height; + } + } + + return thing.ceilingz - thing.floorz >= thing.height; + } + + default boolean isblocking(intercept_t in, line_t li) { + final SlideMove slideMove = contextRequire(KEY_SLIDEMOVE); + // the line does block movement, + // see if it is closer than best so far + + if (in.frac < slideMove.bestslidefrac) { + slideMove.secondslidefrac = slideMove.bestslidefrac; + slideMove.secondslideline = slideMove.bestslideline; + slideMove.bestslidefrac = in.frac; + slideMove.bestslideline = li; + } + + return false; // stop + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsAim.java b/doom/src/p/Actions/ActionsAim.java new file mode 100644 index 0000000..f486500 --- /dev/null +++ b/doom/src/p/Actions/ActionsAim.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.PT_ADDLINES; +import static data.Defines.PT_ADDTHINGS; +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PTR_AimTraverse; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import p.intercept_t; +import p.mobj_t; +import static p.mobj_t.MF_SHOOTABLE; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import static utils.C2JUtils.eval; + +public interface ActionsAim extends ActionsMissiles { + + /** + * P_AimLineAttack + * + * @param t1 + * @param angle long + * @param distance int + */ + @Override + default int AimLineAttack(mobj_t t1, long angle, int distance) { + final Spawn targ = contextRequire(KEY_SPAWN); + int x2, y2; + targ.shootthing = t1; + + x2 = t1.x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1.y + (distance >> FRACBITS) * finesine(angle); + targ.shootz = t1.z + (t1.height >> 1) + 8 * FRACUNIT; + + // can't shoot outside view angles + targ.topslope = 100 * FRACUNIT / 160; + targ.bottomslope = -100 * FRACUNIT / 160; + + targ.attackrange = distance; + targ.linetarget = null; + + PathTraverse(t1.x, t1.y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, this::AimTraverse); + + if (targ.linetarget != null) { + return targ.aimslope; + } + + return 0; + } + + // + // P_BulletSlope + // Sets a slope so a near miss is at aproximately + // the height of the intended target + // + default void P_BulletSlope(mobj_t mo) { + final Spawn targ = contextRequire(KEY_SPAWN); + long an; + + // see which target is to be aimed at + // FIXME: angle can already be negative here. + // Not a problem if it's just moving about (accumulation will work) + // but it needs to be sanitized before being used in any function. + an = mo.angle; + //_D_: &BITS32 will be used later in this function, by fine(co)sine() + targ.bulletslope = AimLineAttack(mo, an/*&BITS32*/, 16 * 64 * FRACUNIT); + + if (!eval(targ.linetarget)) { + an += 1 << 26; + targ.bulletslope = AimLineAttack(mo, an/*&BITS32*/, 16 * 64 * FRACUNIT); + if (!eval(targ.linetarget)) { + an -= 2 << 26; + targ.bulletslope = AimLineAttack(mo, an/*&BITS32*/, 16 * 64 * FRACUNIT); + } + + // Give it one more try, with freelook + if (mo.player.lookdir != 0 && !eval(targ.linetarget)) { + an += 2 << 26; + an &= BITS32; + targ.bulletslope = (mo.player.lookdir << FRACBITS) / 173; + } + } + } + + ////////////////// PTR Traverse Interception Functions /////////////////////// + // Height if not aiming up or down + // ???: use slope for monsters? + @P_Map.C(PTR_AimTraverse) + default boolean AimTraverse(intercept_t in) { + final Movement mov = contextRequire(KEY_MOVEMENT); + final Spawn targ = contextRequire(KEY_SPAWN); + + line_t li; + mobj_t th; + int slope; + int thingtopslope; + int thingbottomslope; + int dist; + + if (in.isaline) { + li = (line_t) in.d(); + + if (!eval(li.flags & ML_TWOSIDED)) { + return false; // stop + } + // Crosses a two sided line. + // A two sided line will restrict + // the possible target ranges. + LineOpening(li); + + if (mov.openbottom >= mov.opentop) { + return false; // stop + } + dist = FixedMul(targ.attackrange, in.frac); + + if (li.frontsector.floorheight != li.backsector.floorheight) { + slope = FixedDiv(mov.openbottom - targ.shootz, dist); + if (slope > targ.bottomslope) { + targ.bottomslope = slope; + } + } + + if (li.frontsector.ceilingheight != li.backsector.ceilingheight) { + slope = FixedDiv(mov.opentop - targ.shootz, dist); + if (slope < targ.topslope) { + targ.topslope = slope; + } + } + + // determine whether shot continues + return targ.topslope > targ.bottomslope; + } + + // shoot a thing + th = (mobj_t) in.d(); + if (th == targ.shootthing) { + return true; // can't shoot self + } + if (!eval(th.flags & MF_SHOOTABLE)) { + return true; // corpse or something + } + // check angles to see if the thing can be aimed at + dist = FixedMul(targ.attackrange, in.frac); + thingtopslope = FixedDiv(th.z + th.height - targ.shootz, dist); + + if (thingtopslope < targ.bottomslope) { + return true; // shot over the thing + } + thingbottomslope = FixedDiv(th.z - targ.shootz, dist); + + if (thingbottomslope > targ.topslope) { + return true; // shot under the thing + } + // this thing can be hit! + if (thingtopslope > targ.topslope) { + thingtopslope = targ.topslope; + } + + if (thingbottomslope < targ.bottomslope) { + thingbottomslope = targ.bottomslope; + } + + targ.aimslope = (thingtopslope + thingbottomslope) / 2; + targ.linetarget = th; + + return false; // don't go any farther + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsAttacks.java b/doom/src/p/Actions/ActionsAttacks.java new file mode 100644 index 0000000..b532760 --- /dev/null +++ b/doom/src/p/Actions/ActionsAttacks.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.MISSILERANGE; +import static data.Defines.PT_ADDLINES; +import static data.Defines.PT_ADDTHINGS; +import static data.Limits.MAXRADIUS; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.mobjinfo; +import data.mobjtype_t; +import defines.statenum_t; +import doom.SourceCode.P_Enemy; +import static doom.SourceCode.P_Enemy.PIT_VileCheck; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PIT_RadiusAttack; +import static doom.SourceCode.P_Map.PTR_ShootTraverse; +import doom.SourceCode.angle_t; +import doom.SourceCode.fixed_t; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import p.AbstractLevelLoader; +import p.intercept_t; +import p.mobj_t; +import static p.mobj_t.MF_CORPSE; +import static p.mobj_t.MF_NOBLOOD; +import static p.mobj_t.MF_SHOOTABLE; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import static utils.C2JUtils.eval; +import utils.TraitFactory.ContextKey; + +public interface ActionsAttacks extends ActionsAim, ActionsMobj, ActionsSight, ActionsShootEvents { + + ContextKey KEY_ATTACKS = ACTION_KEY_CHAIN.newKey(ActionsAttacks.class, Attacks::new); + + final class Attacks { + + // + // RADIUS ATTACK + // + public mobj_t bombsource; + public mobj_t bombspot; + public int bombdamage; + ///////////////////// PIT AND PTR FUNCTIONS ////////////////// + /** + * PIT_VileCheck Detect a corpse that could be raised. + */ + public mobj_t vileCorpseHit; + public mobj_t vileObj; + public int vileTryX; + public int vileTryY; + } + + // + // P_GunShot + // + default void P_GunShot(mobj_t mo, boolean accurate) { + final Spawn targ = contextRequire(KEY_SPAWN); + long angle; + int damage; + + damage = 5 * (P_Random() % 3 + 1); + angle = mo.angle; + + if (!accurate) { + angle += (P_Random() - P_Random()) << 18; + } + + this.LineAttack(mo, angle, MISSILERANGE, targ.bulletslope, damage); + } + + /** + * P_LineAttack If damage == 0, it is just a test trace that will leave linetarget set. + * + * @param t1 + * @param angle angle_t + * @param distance fixed_t + * @param slope fixed_t + * @param damage + */ + default void LineAttack(mobj_t t1, @angle_t long angle, @fixed_t int distance, @fixed_t int slope, int damage) { + final Spawn targ = contextRequire(KEY_SPAWN); + int x2, y2; + + targ.shootthing = t1; + targ.la_damage = damage; + x2 = t1.x + (distance >> FRACBITS) * finecosine(angle); + y2 = t1.y + (distance >> FRACBITS) * finesine(angle); + targ.shootz = t1.z + (t1.height >> 1) + 8 * FRACUNIT; + targ.attackrange = distance; + targ.aimslope = slope; + + PathTraverse(t1.x, t1.y, x2, y2, PT_ADDLINES | PT_ADDTHINGS, this::ShootTraverse); + } + + // + // RADIUS ATTACK + // + /** + * P_RadiusAttack Source is the creature that caused the explosion at spot. + */ + default void RadiusAttack(mobj_t spot, mobj_t source, int damage) { + final AbstractLevelLoader ll = levelLoader(); + final Attacks att = contextRequire(KEY_ATTACKS); + + int x; + int y; + + int xl; + int xh; + int yl; + int yh; + + @fixed_t + int dist; + + dist = (damage + MAXRADIUS) << FRACBITS; + yh = ll.getSafeBlockY(spot.y + dist - ll.bmaporgy); + yl = ll.getSafeBlockY(spot.y - dist - ll.bmaporgy); + xh = ll.getSafeBlockX(spot.x + dist - ll.bmaporgx); + xl = ll.getSafeBlockX(spot.x - dist - ll.bmaporgx); + att.bombspot = spot; + att.bombsource = source; + att.bombdamage = damage; + + for (y = yl; y <= yh; y++) { + for (x = xl; x <= xh; x++) { + BlockThingsIterator(x, y, this::RadiusAttack); + } + } + } + + ///////////////////// PIT AND PTR FUNCTIONS ////////////////// + /** + * PIT_VileCheck Detect a corpse that could be raised. + */ + @P_Enemy.C(PIT_VileCheck) + default boolean VileCheck(mobj_t thing) { + final Attacks att = contextRequire(KEY_ATTACKS); + + int maxdist; + boolean check; + + if (!eval(thing.flags & MF_CORPSE)) { + return true; // not a monster + } + if (thing.mobj_tics != -1) { + return true; // not lying still yet + } + if (thing.info.raisestate == statenum_t.S_NULL) { + return true; // monster doesn't have a raise state + } + maxdist = thing.info.radius + mobjinfo[mobjtype_t.MT_VILE.ordinal()].radius; + + if (Math.abs(thing.x - att.vileTryX) > maxdist + || Math.abs(thing.y - att.vileTryY) > maxdist) { + return true; // not actually touching + } + + att.vileCorpseHit = thing; + att.vileCorpseHit.momx = att.vileCorpseHit.momy = 0; + att.vileCorpseHit.height <<= 2; + check = CheckPosition(att.vileCorpseHit, att.vileCorpseHit.x, att.vileCorpseHit.y); + att.vileCorpseHit.height >>= 2; + + // check it doesn't fit here, or stop checking + return !check; + } + + /** + * PIT_RadiusAttack "bombsource" is the creature that caused the explosion at "bombspot". + */ + @P_Map.C(PIT_RadiusAttack) + default boolean RadiusAttack(mobj_t thing) { + final Attacks att = contextRequire(KEY_ATTACKS); + @fixed_t + int dx, dy, dist; + + if (!eval(thing.flags & MF_SHOOTABLE)) { + return true; + } + + // Boss spider and cyborg + // take no damage from concussion. + if (thing.type == mobjtype_t.MT_CYBORG || thing.type == mobjtype_t.MT_SPIDER) { + return true; + } + + dx = Math.abs(thing.x - att.bombspot.x); + dy = Math.abs(thing.y - att.bombspot.y); + + dist = dx > dy ? dx : dy; + dist = (dist - thing.radius) >> FRACBITS; + + if (dist < 0) { + dist = 0; + } + + if (dist >= att.bombdamage) { + return true; // out of range + } + if (CheckSight(thing, att.bombspot)) { + // must be in direct path + DamageMobj(thing, att.bombspot, att.bombsource, att.bombdamage - dist); + } + + return true; + } + + ; + + /** + * PTR_ShootTraverse + * + * 9/5/2011: Accepted _D_'s fix + */ + @P_Map.C(PTR_ShootTraverse) + default boolean ShootTraverse(intercept_t in) { + final Spawn targ = contextRequire(KEY_SPAWN); + final Movement mov = contextRequire(KEY_MOVEMENT); + @fixed_t + int x, y, z, frac; + line_t li; + mobj_t th; + + @fixed_t + int slope, dist, thingtopslope, thingbottomslope; + + if (in.isaline) { + li = (line_t) in.d(); + + if (li.special != 0) { + ShootSpecialLine(targ.shootthing, li); + } + + if (!eval(li.flags & ML_TWOSIDED)) { + return gotoHitLine(in, li); + } + + // crosses a two sided line + LineOpening(li); + + dist = FixedMul(targ.attackrange, in.frac); + + if (li.frontsector.floorheight != li.backsector.floorheight) { + slope = FixedDiv(mov.openbottom - targ.shootz, dist); + if (slope > targ.aimslope) { + return gotoHitLine(in, li); + } + } + + if (li.frontsector.ceilingheight != li.backsector.ceilingheight) { + slope = FixedDiv(mov.opentop - targ.shootz, dist); + if (slope < targ.aimslope) { + return gotoHitLine(in, li); + } + } + + // shot continues + return true; + + } + + // shoot a thing + th = (mobj_t) in.d(); + if (th == targ.shootthing) { + return true; // can't shoot self + } + if (!eval(th.flags & MF_SHOOTABLE)) { + return true; // corpse or something + } + // check angles to see if the thing can be aimed at + dist = FixedMul(targ.attackrange, in.frac); + thingtopslope = FixedDiv(th.z + th.height - targ.shootz, dist); + + if (thingtopslope < targ.aimslope) { + return true; // shot over the thing + } + thingbottomslope = FixedDiv(th.z - targ.shootz, dist); + + if (thingbottomslope > targ.aimslope) { + return true; // shot under the thing + } + + // hit thing + // position a bit closer + frac = in.frac - FixedDiv(10 * FRACUNIT, targ.attackrange); + + x = targ.trace.x + FixedMul(targ.trace.dx, frac); + y = targ.trace.y + FixedMul(targ.trace.dy, frac); + z = targ.shootz + FixedMul(targ.aimslope, FixedMul(frac, targ.attackrange)); + + // Spawn bullet puffs or blod spots, + // depending on target type. + if (eval(((mobj_t) in.d()).flags & MF_NOBLOOD)) { + SpawnPuff(x, y, z); + } else { + SpawnBlood(x, y, z, targ.la_damage); + } + + if (targ.la_damage != 0) { + DamageMobj(th, targ.shootthing, targ.shootthing, targ.la_damage); + } + + // don't go any farther + return false; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsCeilings.java b/doom/src/p/Actions/ActionsCeilings.java new file mode 100644 index 0000000..2eb40fc --- /dev/null +++ b/doom/src/p/Actions/ActionsCeilings.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Limits.CEILSPEED; +import static data.Limits.MAXCEILINGS; +import data.sounds; +import doom.SourceCode.P_Ceiling; +import static doom.SourceCode.P_Ceiling.EV_DoCeiling; +import doom.thinker_t; +import static m.fixed_t.FRACUNIT; +import p.ActiveStates; +import p.ceiling_e; +import p.ceiling_t; +import p.result_e; +import rr.line_t; +import rr.sector_t; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import utils.TraitFactory.ContextKey; + +public interface ActionsCeilings extends ActionsMoveEvents, ActionsUseEvents { + + ContextKey KEY_CEILINGS = ACTION_KEY_CHAIN.newKey(ActionsCeilings.class, Ceilings::new); + + void RemoveThinker(thinker_t activeCeiling); + + result_e MovePlane(sector_t sector, int speed, int bottomheight, boolean crush, int i, int direction); + + int FindSectorFromLineTag(line_t line, int secnum); + + final class Ceilings { + + ceiling_t[] activeceilings = new ceiling_t[MAXCEILINGS]; + } + + /** + * This needs to be called before loading, otherwise crushers won't be able to be restarted. + */ + default void ClearCeilingsBeforeLoading() { + contextRequire(KEY_CEILINGS).activeceilings = new ceiling_t[MAXCEILINGS]; + } + + /** + * T_MoveCeiling + */ + default void MoveCeiling(ceiling_t ceiling) { + result_e res; + + switch (ceiling.direction) { + case 0: + // IN STASIS + break; + case 1: + // UP + res = MovePlane(ceiling.sector, ceiling.speed, ceiling.topheight, false, 1, ceiling.direction); + + if (!eval(LevelTime() & 7)) { + switch (ceiling.type) { + case silentCrushAndRaise: + break; + default: + StartSound(ceiling.sector.soundorg, sounds.sfxenum_t.sfx_stnmov); + } + } + + if (res == result_e.pastdest) { + switch (ceiling.type) { + case raiseToHighest: + this.RemoveActiveCeiling(ceiling); + break; + case silentCrushAndRaise: + StartSound(ceiling.sector.soundorg, sounds.sfxenum_t.sfx_pstop); + case fastCrushAndRaise: + case crushAndRaise: + ceiling.direction = -1; + default: + break; + } + } + break; + + case -1: + // DOWN + res = MovePlane(ceiling.sector, ceiling.speed, ceiling.bottomheight, ceiling.crush, 1, ceiling.direction); + + if (!eval(LevelTime() & 7)) { + switch (ceiling.type) { + case silentCrushAndRaise: + break; + default: + StartSound(ceiling.sector.soundorg, sounds.sfxenum_t.sfx_stnmov); + } + } + + if (res == result_e.pastdest) { + switch (ceiling.type) { + case silentCrushAndRaise: + StartSound(ceiling.sector.soundorg, sounds.sfxenum_t.sfx_pstop); + case crushAndRaise: + ceiling.speed = CEILSPEED; + case fastCrushAndRaise: + ceiling.direction = 1; + break; + case lowerAndCrush: + case lowerToFloor: + RemoveActiveCeiling(ceiling); + break; + default: + break; + } + } else { // ( res != result_e.pastdest ) + if (res == result_e.crushed) { + switch (ceiling.type) { + case silentCrushAndRaise: + case crushAndRaise: + case lowerAndCrush: + ceiling.speed = CEILSPEED / 8; + break; + default: + break; + } + } + } + } + } + + // + // EV.DoCeiling + // Move a ceiling up/down and all around! + // + @Override + @P_Ceiling.C(EV_DoCeiling) + default boolean DoCeiling(line_t line, ceiling_e type) { + int secnum = -1; + boolean rtn = false; + sector_t sec; + ceiling_t ceiling; + + // Reactivate in-stasis ceilings...for certain types. + switch (type) { + case fastCrushAndRaise: + case silentCrushAndRaise: + case crushAndRaise: + ActivateInStasisCeiling(line); + default: + break; + } + + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = levelLoader().sectors[secnum]; + if (sec.specialdata != null) { + continue; + } + + // new door thinker + rtn = true; + ceiling = new ceiling_t(); + sec.specialdata = ceiling; + ceiling.thinkerFunction = ActiveStates.T_MoveCeiling; + AddThinker(ceiling); + ceiling.sector = sec; + ceiling.crush = false; + + switch (type) { + case fastCrushAndRaise: + ceiling.crush = true; + ceiling.topheight = sec.ceilingheight; + ceiling.bottomheight = sec.floorheight + (8 * FRACUNIT); + ceiling.direction = -1; + ceiling.speed = CEILSPEED * 2; + break; + + case silentCrushAndRaise: + case crushAndRaise: + ceiling.crush = true; + ceiling.topheight = sec.ceilingheight; + case lowerAndCrush: + case lowerToFloor: + ceiling.bottomheight = sec.floorheight; + if (type != ceiling_e.lowerToFloor) { + ceiling.bottomheight += 8 * FRACUNIT; + } + ceiling.direction = -1; + ceiling.speed = CEILSPEED; + break; + + case raiseToHighest: + ceiling.topheight = sec.FindHighestCeilingSurrounding(); + ceiling.direction = 1; + ceiling.speed = CEILSPEED; + break; + } + + ceiling.tag = sec.tag; + ceiling.type = type; + AddActiveCeiling(ceiling); + } + return rtn; + } + + // + // Add an active ceiling + // + default void AddActiveCeiling(ceiling_t c) { + final ceiling_t[] activeCeilings = getActiveCeilings(); + for (int i = 0; i < activeCeilings.length; ++i) { + if (activeCeilings[i] == null) { + activeCeilings[i] = c; + return; + } + } + // Needs rezising + setActiveceilings(C2JUtils.resize(c, activeCeilings, 2 * activeCeilings.length)); + } + + // + // Remove a ceiling's thinker + // + default void RemoveActiveCeiling(ceiling_t c) { + final ceiling_t[] activeCeilings = getActiveCeilings(); + for (int i = 0; i < activeCeilings.length; ++i) { + if (activeCeilings[i] == c) { + activeCeilings[i].sector.specialdata = null; + RemoveThinker(activeCeilings[i]); + activeCeilings[i] = null; + break; + } + } + } + + // + // Restart a ceiling that's in-stasis + // + default void ActivateInStasisCeiling(line_t line) { + final ceiling_t[] activeCeilings = getActiveCeilings(); + for (int i = 0; i < activeCeilings.length; ++i) { + if (activeCeilings[i] != null + && (activeCeilings[i].tag == line.tag) + && (activeCeilings[i].direction == 0)) { + activeCeilings[i].direction = activeCeilings[i].olddirection; + activeCeilings[i].thinkerFunction = ActiveStates.T_MoveCeiling; + } + } + } + + // + // EV_CeilingCrushStop + // Stop a ceiling from crushing! + // + @Override + default int CeilingCrushStop(line_t line) { + int i; + int rtn; + + rtn = 0; + final ceiling_t[] activeCeilings = getActiveCeilings(); + for (i = 0; i < activeCeilings.length; ++i) { + if (activeCeilings[i] != null + && (activeCeilings[i].tag == line.tag) + && (activeCeilings[i].direction != 0)) { + activeCeilings[i].olddirection = activeCeilings[i].direction; + activeCeilings[i].thinkerFunction = ActiveStates.NOP; + activeCeilings[i].direction = 0; // in-stasis + rtn = 1; + } + } + + return rtn; + } + + default void setActiveceilings(ceiling_t[] activeceilings) { + contextRequire(KEY_CEILINGS).activeceilings = activeceilings; + } + + default ceiling_t[] getActiveCeilings() { + return contextRequire(KEY_CEILINGS).activeceilings; + } + + default int getMaxCeilings() { + return contextRequire(KEY_CEILINGS).activeceilings.length; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsDoors.java b/doom/src/p/Actions/ActionsDoors.java new file mode 100644 index 0000000..920e462 --- /dev/null +++ b/doom/src/p/Actions/ActionsDoors.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import data.sounds; +import defines.card_t; +import doom.SourceCode; +import doom.SourceCode.P_Doors; +import static doom.SourceCode.P_Doors.P_SpawnDoorCloseIn30; +import static doom.SourceCode.P_Doors.P_SpawnDoorRaiseIn5Mins; +import static doom.englsh.PD_BLUEK; +import static doom.englsh.PD_BLUEO; +import static doom.englsh.PD_REDK; +import static doom.englsh.PD_REDO; +import static doom.englsh.PD_YELLOWK; +import static doom.englsh.PD_YELLOWO; +import doom.player_t; +import doom.thinker_t; +import static m.fixed_t.FRACUNIT; +import p.ActiveStates; +import static p.ActiveStates.T_VerticalDoor; +import static p.DoorDefines.VDOORSPEED; +import static p.DoorDefines.VDOORWAIT; +import p.mobj_t; +import p.plat_t; +import p.result_e; +import p.vldoor_e; +import p.vldoor_t; +import rr.line_t; +import rr.sector_t; +import static utils.C2JUtils.eval; + +public interface ActionsDoors extends ActionsMoveEvents, ActionsUseEvents { + + result_e MovePlane(sector_t sector, int speed, int floorheight, boolean b, int i, int direction); + + void RemoveThinker(thinker_t door); + + int FindSectorFromLineTag(line_t line, int secnum); + + // + // VERTICAL DOORS + // + /** + * T_VerticalDoor + */ + default void VerticalDoor(vldoor_t door) { + switch (door.direction) { + case 0: + // WAITING + if (!eval(--door.topcountdown)) { + switch (door.type) { + case blazeRaise: + door.direction = -1; // time to go back down + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_bdcls); + break; + case normal: + door.direction = -1; // time to go back down + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_dorcls); + break; + case close30ThenOpen: + door.direction = 1; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_doropn); + break; + default: + break; + } + } + break; + + case 2: + // INITIAL WAIT + if (!eval(--door.topcountdown)) { + switch (door.type) { + case raiseIn5Mins: + door.direction = 1; + door.type = vldoor_e.normal; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_doropn); + break; + default: + break; + } + } + break; + + case -1: { + // DOWN + final result_e res = MovePlane(door.sector, door.speed, door.sector.floorheight, false, 1, door.direction); + if (res == result_e.pastdest) { + switch (door.type) { + case blazeRaise: + case blazeClose: + door.sector.specialdata = null; + RemoveThinker(door); // unlink and free + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_bdcls); + break; + case normal: + case close: + door.sector.specialdata = null; + RemoveThinker(door); // unlink and free + break; + case close30ThenOpen: + door.direction = 0; + door.topcountdown = 35 * 30; + break; + default: + break; + } + } else if (res == result_e.crushed) { + switch (door.type) { + case blazeClose: + case close: // DO NOT GO BACK UP! + break; + default: + door.direction = 1; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_doropn); + } + } + break; + } + case 1: { + // UP + final result_e res = this.MovePlane(door.sector, door.speed, door.topheight, false, 1, door.direction); + + if (res == result_e.pastdest) { + switch (door.type) { + case blazeRaise: + case normal: + door.direction = 0; // wait at top + door.topcountdown = door.topwait; + break; + case close30ThenOpen: + case blazeOpen: + case open: + door.sector.specialdata = null; + RemoveThinker(door); // unlink and free + break; + default: + break; + } + } + break; + } + } + } + + /** + * EV_DoLockedDoor Move a locked door up/down + */ + @Override + default boolean DoLockedDoor(line_t line, vldoor_e type, mobj_t thing) { + player_t p; + + p = thing.player; + + if (p == null) { + return false; + } + + switch (line.special) { + case 99: // Blue Lock + case 133: + /* if ( p==null ) + return false; */ + if (!p.cards[card_t.it_bluecard.ordinal()] && !p.cards[card_t.it_blueskull.ordinal()]) { + p.message = PD_BLUEO; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return false; + } + break; + + case 134: // Red Lock + case 135: + /* if ( p==null ) + return false; */ + if (!p.cards[card_t.it_redcard.ordinal()] && !p.cards[card_t.it_redskull.ordinal()]) { + p.message = PD_REDO; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return false; + } + break; + + case 136: // Yellow Lock + case 137: + /* if ( p==null ) + return false; */ + if (!p.cards[card_t.it_yellowcard.ordinal()] + && !p.cards[card_t.it_yellowskull.ordinal()]) { + p.message = PD_YELLOWO; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return false; + } + break; + } + + return DoDoor(line, type); + } + + @Override + default boolean DoDoor(line_t line, vldoor_e type) { + int secnum; + boolean rtn = false; + sector_t sec; + vldoor_t door; + + secnum = -1; + + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = levelLoader().sectors[secnum]; + if (sec.specialdata != null) { + continue; + } + + // new door thinker + rtn = true; + door = new vldoor_t(); + sec.specialdata = door; + door.thinkerFunction = ActiveStates.T_VerticalDoor; + AddThinker(door); + door.sector = sec; + door.type = type; + door.topwait = VDOORWAIT; + door.speed = VDOORSPEED; + + switch (type) { + case blazeClose: + door.topheight = sec.FindLowestCeilingSurrounding(); + door.topheight -= 4 * FRACUNIT; + door.direction = -1; + door.speed = VDOORSPEED * 4; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_bdcls); + break; + case close: + door.topheight = sec.FindLowestCeilingSurrounding(); + door.topheight -= 4 * FRACUNIT; + door.direction = -1; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_dorcls); + break; + case close30ThenOpen: + door.topheight = sec.ceilingheight; + door.direction = -1; + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_dorcls); + break; + case blazeRaise: + case blazeOpen: + door.direction = 1; + door.topheight = sec.FindLowestCeilingSurrounding(); + door.topheight -= 4 * FRACUNIT; + door.speed = VDOORSPEED * 4; + if (door.topheight != sec.ceilingheight) { + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_bdopn); + } + break; + case normal: + case open: + door.direction = 1; + door.topheight = sec.FindLowestCeilingSurrounding(); + door.topheight -= 4 * FRACUNIT; + if (door.topheight != sec.ceilingheight) { + StartSound(door.sector.soundorg, sounds.sfxenum_t.sfx_doropn); + } + default: + break; + } + + } + return rtn; + } + + /** + * EV_VerticalDoor : open a door manually, no tag value + */ + @Override + default void VerticalDoor(line_t line, mobj_t thing) { + player_t player; + //int secnum; + sector_t sec; + vldoor_t door; + int side; + + side = 0; // only front sides can be used + + // Check for locks + player = thing.player; + + switch (line.special) { + case 26: // Blue Lock + case 32: + if (player == null) { + return; + } + + if (!player.cards[card_t.it_bluecard.ordinal()] && !player.cards[card_t.it_blueskull.ordinal()]) { + player.message = PD_BLUEK; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return; + } + break; + + case 27: // Yellow Lock + case 34: + if (player == null) { + return; + } + + if (!player.cards[card_t.it_yellowcard.ordinal()] && !player.cards[card_t.it_yellowskull.ordinal()]) { + player.message = PD_YELLOWK; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return; + } + break; + + case 28: // Red Lock + case 33: + if (player == null) { + return; + } + + if (!player.cards[card_t.it_redcard.ordinal()] && !player.cards[card_t.it_redskull.ordinal()]) { + player.message = PD_REDK; + StartSound(null, sounds.sfxenum_t.sfx_oof); + return; + } + break; + } + + // if the sector has an active thinker, use it + sec = levelLoader().sides[line.sidenum[side ^ 1]].sector; + // secnum = sec.id; + + if (sec.specialdata != null) { + if (sec.specialdata instanceof plat_t) { + /** + * [MAES]: demo sync for e1nm0646: emulates active plat_t interpreted + * as door. TODO: add our own overflow handling class. + */ + door = ((plat_t) sec.specialdata).asVlDoor(levelLoader().sectors); + } else { + door = (vldoor_t) sec.specialdata; + } + switch (line.special) { + case 1: // ONLY FOR "RAISE" DOORS, NOT "OPEN"s + case 26: + case 27: + case 28: + case 117: + if (door.direction == -1) { + door.direction = 1; // go back up + } else { + if (thing.player == null) { + return; // JDC: bad guys never close doors + } + door.direction = -1; // start going down immediately + } + return; + } + } + + // for proper sound + switch (line.special) { + case 117: // BLAZING DOOR RAISE + case 118: // BLAZING DOOR OPEN + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_bdopn); + break; + + case 1: // NORMAL DOOR SOUND + case 31: + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_doropn); + break; + + default: // LOCKED DOOR SOUND + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_doropn); + break; + } + + // new door thinker + door = new vldoor_t(); + sec.specialdata = door; + door.thinkerFunction = ActiveStates.T_VerticalDoor; + AddThinker(door); + door.sector = sec; + door.direction = 1; + door.speed = VDOORSPEED; + door.topwait = VDOORWAIT; + + switch (line.special) { + case 1: + case 26: + case 27: + case 28: + door.type = vldoor_e.normal; + break; + case 31: + case 32: + case 33: + case 34: + door.type = vldoor_e.open; + line.special = 0; + break; + case 117: // blazing door raise + door.type = vldoor_e.blazeRaise; + door.speed = VDOORSPEED * 4; + break; + case 118: // blazing door open + door.type = vldoor_e.blazeOpen; + line.special = 0; + door.speed = VDOORSPEED * 4; + } + + // find the top and bottom of the movement range + door.topheight = sec.FindLowestCeilingSurrounding(); + door.topheight -= 4 * FRACUNIT; + } + + // + // Spawn a door that closes after 30 seconds + // + @SourceCode.Exact + @P_Doors.C(P_SpawnDoorCloseIn30) + default void SpawnDoorCloseIn30(sector_t sector) { + vldoor_t door; + + Z_Malloc: + { + door = new vldoor_t(); + } + + P_AddThinker: + { + AddThinker(door); + } + + sector.specialdata = door; + sector.special = 0; + + door.thinkerFunction = T_VerticalDoor; + door.sector = sector; + door.direction = 0; + door.type = vldoor_e.normal; + door.speed = VDOORSPEED; + door.topcountdown = 30 * 35; + } + + /** + * Spawn a door that opens after 5 minutes + */ + @SourceCode.Exact + @P_Doors.C(P_SpawnDoorRaiseIn5Mins) + default void SpawnDoorRaiseIn5Mins(sector_t sector, int secnum) { + vldoor_t door; + + Z_Malloc: + { + door = new vldoor_t(); + } + + P_AddThinker: + { + AddThinker(door); + } + + sector.specialdata = door; + sector.special = 0; + + door.thinkerFunction = T_VerticalDoor; + door.sector = sector; + door.direction = 2; + door.type = vldoor_e.raiseIn5Mins; + door.speed = VDOORSPEED; + P_FindLowestCeilingSurrounding: + { + door.topheight = sector.FindLowestCeilingSurrounding(); + } + door.topheight -= 4 * FRACUNIT; + door.topwait = VDOORWAIT; + door.topcountdown = 5 * 60 * 35; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsEnemies.java b/doom/src/p/Actions/ActionsEnemies.java new file mode 100644 index 0000000..3f76bc1 --- /dev/null +++ b/doom/src/p/Actions/ActionsEnemies.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.MELEERANGE; +import static data.Limits.MAXSPECIALCROSS; +import static data.Tables.ANG270; +import static data.Tables.ANG90; +import static data.Tables.BITS32; +import data.mobjtype_t; +import defines.statenum_t; +import doom.SourceCode.fixed_t; +import static doom.items.weaponinfo; +import doom.player_t; +import static m.fixed_t.FRACUNIT; +import static p.MapUtils.AproxDistance; +import static p.MobjFlags.MF_JUSTHIT; +import p.mobj_t; +import rr.SceneRenderer; +import rr.line_t; +import static rr.line_t.ML_SOUNDBLOCK; +import static rr.line_t.ML_TWOSIDED; +import rr.sector_t; +import rr.side_t; +import utils.TraitFactory.ContextKey; + +public interface ActionsEnemies extends ActionsSight, ActionsSpawns { + + ContextKey KEY_ENEMIES = ACTION_KEY_CHAIN.newKey(ActionsEnemies.class, Enemies::new); + + class Enemies { + + mobj_t soundtarget; + // Peg to map movement + line_t[] spechitp = new line_t[MAXSPECIALCROSS]; + int numspechit; + } + + // + // ENEMY THINKING + // Enemies are allways spawned + // with targetplayer = -1, threshold = 0 + // Most monsters are spawned unaware of all players, + // but some can be made preaware + // + /** + * P_CheckMeleeRange + */ + default boolean CheckMeleeRange(mobj_t actor) { + mobj_t pl; + @fixed_t + int dist; + + if (actor.target == null) { + return false; + } + + pl = actor.target; + dist = AproxDistance(pl.x - actor.x, pl.y - actor.y); + + if (dist >= MELEERANGE - 20 * FRACUNIT + pl.info.radius) { + return false; + } + + return CheckSight(actor, actor.target); + } + + /** + * P_CheckMissileRange + */ + default boolean CheckMissileRange(mobj_t actor) { + @fixed_t + int dist; + + if (!CheckSight(actor, actor.target)) { + return false; + } + + if ((actor.flags & MF_JUSTHIT) != 0) { + // the target just hit the enemy, + // so fight back! + actor.flags &= ~MF_JUSTHIT; + return true; + } + + if (actor.reactiontime != 0) { + return false; // do not attack yet + } + + // OPTIMIZE: get this from a global checksight + dist = AproxDistance(actor.x - actor.target.x, actor.y - actor.target.y) - 64 * FRACUNIT; + + // [SYNC}: Major desync cause of desyncs. + // DO NOT compare with null! + if (actor.info.meleestate == statenum_t.S_NULL) { + dist -= 128 * FRACUNIT; // no melee attack, so fire more + } + + dist >>= 16; + + if (actor.type == mobjtype_t.MT_VILE) { + if (dist > 14 * 64) { + return false; // too far away + } + } + + if (actor.type == mobjtype_t.MT_UNDEAD) { + if (dist < 196) { + return false; // close for fist attack + } + dist >>= 1; + } + + if (actor.type == mobjtype_t.MT_CYBORG || actor.type == mobjtype_t.MT_SPIDER || actor.type == mobjtype_t.MT_SKULL) { + dist >>= 1; + } + + if (dist > 200) { + dist = 200; + } + + if (actor.type == mobjtype_t.MT_CYBORG && dist > 160) { + dist = 160; + } + + return P_Random() >= dist; + } + + // + // Called by P_NoiseAlert. + // Recursively traverse adjacent sectors, + // sound blocking lines cut off traversal. + // + default void RecursiveSound(sector_t sec, int soundblocks) { + final SceneRenderer sr = sceneRenderer(); + final Enemies en = contextRequire(KEY_ENEMIES); + final Movement mov = contextRequire(KEY_MOVEMENT); + int i; + line_t check; + sector_t other; + + // wake up all monsters in this sector + if (sec.validcount == sr.getValidCount() && sec.soundtraversed <= soundblocks + 1) { + return; // already flooded + } + + sec.validcount = sr.getValidCount(); + sec.soundtraversed = soundblocks + 1; + sec.soundtarget = en.soundtarget; + + // "peg" to the level loader for syntactic sugar + side_t[] sides = levelLoader().sides; + + for (i = 0; i < sec.linecount; i++) { + check = sec.lines[i]; + + if ((check.flags & ML_TWOSIDED) == 0) { + continue; + } + + LineOpening(check); + + if (mov.openrange <= 0) { + continue; // closed door + } + + if (sides[check.sidenum[0]].sector == sec) { + other = sides[check.sidenum[1]].sector; + } else { + other = sides[check.sidenum[0]].sector; + } + + if ((check.flags & ML_SOUNDBLOCK) != 0) { + if (soundblocks == 0) { + RecursiveSound(other, 1); + } + } else { + RecursiveSound(other, soundblocks); + } + } + } + + /** + * P_NoiseAlert + * If a monster yells at a player, + * it will alert other monsters to the player. + */ + default void NoiseAlert(mobj_t target, mobj_t emmiter) { + final Enemies en = contextRequire(KEY_ENEMIES); + en.soundtarget = target; + sceneRenderer().increaseValidCount(1); + RecursiveSound(emmiter.subsector.sector, 0); + } + + /** + * P_FireWeapon. Originally in pspr + */ + default void FireWeapon(player_t player) { + statenum_t newstate; + + if (!player.CheckAmmo()) { + return; + } + + player.mo.SetMobjState(statenum_t.S_PLAY_ATK1); + newstate = weaponinfo[player.readyweapon.ordinal()].atkstate; + player.SetPsprite(player_t.ps_weapon, newstate); + NoiseAlert(player.mo, player.mo); + } + + /** + * P_LookForPlayers If allaround is false, only look 180 degrees in + * front. Returns true if a player is targeted. + */ + default boolean LookForPlayers(mobj_t actor, boolean allaround) { + final SceneRenderer sr = sceneRenderer(); + + int c; + int stop; + player_t player; + // sector_t sector; + long an; // angle + int dist; // fixed + + // sector = actor.subsector.sector; + c = 0; + stop = (actor.lastlook - 1) & 3; + + for (;; actor.lastlook = (actor.lastlook + 1) & 3) { + if (!PlayerInGame(actor.lastlook)) { + continue; + } + + if (c++ == 2 || actor.lastlook == stop) { + // done looking + return false; + } + + player = getPlayer(actor.lastlook); + + if (player.health[0] <= 0) { + continue; // dead + } + + if (!CheckSight(actor, player.mo)) { + continue; // out of sight + } + + if (!allaround) { + an = (sr.PointToAngle2(actor.x, actor.y, player.mo.x, player.mo.y) - actor.angle) & BITS32; + + if (an > ANG90 && an < ANG270) { + dist = AproxDistance(player.mo.x - actor.x, player.mo.y - actor.y); + + // if real close, react anyway + if (dist > MELEERANGE) { + continue; // behind back + } + } + } + + actor.target = player.mo; + return true; + } + // The compiler complains that this is unreachable + // return false; + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsFloors.java b/doom/src/p/Actions/ActionsFloors.java new file mode 100644 index 0000000..f25b890 --- /dev/null +++ b/doom/src/p/Actions/ActionsFloors.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Limits.MAXINT; +import data.sounds; +import m.fixed_t; +import static m.fixed_t.FRACUNIT; +import p.ActiveStates; +import p.floor_e; +import p.floormove_t; +import p.plat_e; +import p.plat_t; +import p.plattype_e; +import p.result_e; +import p.stair_e; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import rr.sector_t; +import rr.side_t; +import static utils.C2JUtils.eval; + +public interface ActionsFloors extends ActionsPlats { + + result_e MovePlane(sector_t sector, int speed, int floordestheight, boolean crush, int i, int direction); + + boolean twoSided(int secnum, int i); + + side_t getSide(int secnum, int i, int s); + + sector_t getSector(int secnum, int i, int i0); + + // + // FLOORS + // + int FLOORSPEED = fixed_t.MAPFRACUNIT; + + /** + * MOVE A FLOOR TO IT'S DESTINATION (UP OR DOWN) + */ + default void MoveFloor(floormove_t floor) { + final result_e res = MovePlane(floor.sector, floor.speed, floor.floordestheight, floor.crush, 0, floor.direction); + + if (!eval(LevelTime() & 7)) { + StartSound(floor.sector.soundorg, sounds.sfxenum_t.sfx_stnmov); + } + + if (res == result_e.pastdest) { + floor.sector.specialdata = null; + + if (floor.direction == 1) { + switch (floor.type) { + case donutRaise: + floor.sector.special = (short) floor.newspecial; + floor.sector.floorpic = floor.texture; + default: + break; + } + } else if (floor.direction == -1) { + switch (floor.type) //TODO: check if a null floor.type is valid or a bug + // MAES: actually, type should always be set to something. + // In C, this means "zero" or "null". In Java, we must make sure + // it's actually set to something all the time. + { + case lowerAndChange: + floor.sector.special = (short) floor.newspecial; + floor.sector.floorpic = floor.texture; + default: + break; + } + } + + RemoveThinker(floor); + StartSound(floor.sector.soundorg, sounds.sfxenum_t.sfx_pstop); + } + } + + // + // HANDLE FLOOR TYPES + // + @Override + default boolean DoFloor(line_t line, floor_e floortype) { + int secnum = -1; + boolean rtn = false; + sector_t sec; + floormove_t floor; + + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = levelLoader().sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec.specialdata != null) { + continue; + } + + // new floor thinker + rtn = true; + floor = new floormove_t(); + sec.specialdata = floor; + floor.thinkerFunction = ActiveStates.T_MoveFloor; + AddThinker(floor); + floor.type = floortype; + floor.crush = false; + + switch (floortype) { + case lowerFloor: + floor.direction = -1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = sec.FindHighestFloorSurrounding(); + break; + + case lowerFloorToLowest: + floor.direction = -1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = sec.FindLowestFloorSurrounding(); + break; + + case turboLower: + floor.direction = -1; + floor.sector = sec; + floor.speed = FLOORSPEED * 4; + floor.floordestheight = sec.FindHighestFloorSurrounding(); + if (floor.floordestheight != sec.floorheight) { + floor.floordestheight += 8 * FRACUNIT; + } + break; + + case raiseFloorCrush: + floor.crush = true; + case raiseFloor: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = sec.FindLowestCeilingSurrounding(); + if (floor.floordestheight > sec.ceilingheight) { + floor.floordestheight = sec.ceilingheight; + } + floor.floordestheight -= (8 * FRACUNIT) + * eval(floortype == floor_e.raiseFloorCrush); + break; + + case raiseFloorTurbo: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED * 4; + floor.floordestheight = sec.FindNextHighestFloor(sec.floorheight); + break; + + case raiseFloorToNearest: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = sec.FindNextHighestFloor(sec.floorheight); + break; + + case raiseFloor24: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = floor.sector.floorheight + 24 * FRACUNIT; + break; + case raiseFloor512: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = floor.sector.floorheight + 512 * FRACUNIT; + break; + + case raiseFloor24AndChange: + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = floor.sector.floorheight + 24 * FRACUNIT; + sec.floorpic = line.frontsector.floorpic; + sec.special = line.frontsector.special; + break; + + case raiseToTexture: { + int minsize = MAXINT; + side_t side; + + floor.direction = 1; + floor.sector = sec; + floor.speed = FLOORSPEED; + for (int i = 0; i < sec.linecount; ++i) { + if (twoSided(secnum, i)) { + for (int s = 0; s < 2; ++s) { + side = getSide(secnum, i, s); + if (side.bottomtexture >= 0) { + if (DOOM().textureManager.getTextureheight(side.bottomtexture) < minsize) { + minsize = DOOM().textureManager.getTextureheight(side.bottomtexture); + } + } + } + } + } + floor.floordestheight = floor.sector.floorheight + minsize; + } + break; + + case lowerAndChange: + floor.direction = -1; + floor.sector = sec; + floor.speed = FLOORSPEED; + floor.floordestheight = sec.FindLowestFloorSurrounding(); + floor.texture = sec.floorpic; + + for (int i = 0; i < sec.linecount; i++) { + if (twoSided(secnum, i)) { + if (getSide(secnum, i, 0).sector.id == secnum) { + sec = getSector(secnum, i, 1); + if (sec.floorheight == floor.floordestheight) { + floor.texture = sec.floorpic; + floor.newspecial = sec.special; + break; + } + } else { + sec = getSector(secnum, i, 0); + if (sec.floorheight == floor.floordestheight) { + floor.texture = sec.floorpic; + floor.newspecial = sec.special; + break; + } + } + } + } + default: + break; + } + } + return rtn; + } + + /** + * BUILD A STAIRCASE! + */ + @Override + default boolean BuildStairs(line_t line, stair_e type) { + int secnum; + int height; + int i; + int newsecnum; + int texture; + boolean ok; + boolean rtn; + + sector_t sec; + sector_t tsec; + + floormove_t floor; + + int stairsize = 0; + int speed = 0; // shut up compiler + + secnum = -1; + rtn = false; + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = levelLoader().sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (sec.specialdata != null) { + continue; + } + + // new floor thinker + rtn = true; + floor = new floormove_t(); + sec.specialdata = floor; + floor.thinkerFunction = ActiveStates.T_MoveFloor; + AddThinker(floor); + floor.direction = 1; + floor.sector = sec; + switch (type) { + case build8: + speed = FLOORSPEED / 4; + stairsize = 8 * FRACUNIT; + break; + case turbo16: + speed = FLOORSPEED * 4; + stairsize = 16 * FRACUNIT; + break; + } + floor.speed = speed; + height = sec.floorheight + stairsize; + floor.floordestheight = height; + + texture = sec.floorpic; + + // Find next sector to raise + // 1. Find 2-sided line with same sector side[0] + // 2. Other side is the next sector to raise + do { + ok = false; + for (i = 0; i < sec.linecount; i++) { + if (!eval((sec.lines[i]).flags & ML_TWOSIDED)) { + continue; + } + + tsec = (sec.lines[i]).frontsector; + newsecnum = tsec.id; + + if (secnum != newsecnum) { + continue; + } + + tsec = (sec.lines[i]).backsector; + newsecnum = tsec.id; + + if (tsec.floorpic != texture) { + continue; + } + + height += stairsize; + + if (tsec.specialdata != null) { + continue; + } + + sec = tsec; + secnum = newsecnum; + floor = new floormove_t(); + sec.specialdata = floor; + floor.thinkerFunction = ActiveStates.T_MoveFloor; + AddThinker(floor); + floor.direction = 1; + floor.sector = sec; + floor.speed = speed; + floor.floordestheight = height; + ok = true; + break; + } + } while (ok); + } + return rtn; + } + + /** + * Move a plat up and down + */ + default void PlatRaise(plat_t plat) { + result_e res; + + switch (plat.status) { + case up: + res = MovePlane(plat.sector, plat.speed, plat.high, plat.crush, 0, 1); + + if (plat.type == plattype_e.raiseAndChange + || plat.type == plattype_e.raiseToNearestAndChange) { + if (!eval(LevelTime() & 7)) { + StartSound(plat.sector.soundorg, sounds.sfxenum_t.sfx_stnmov); + } + } + + if (res == result_e.crushed && (!plat.crush)) { + plat.count = plat.wait; + plat.status = plat_e.down; + StartSound(plat.sector.soundorg, sounds.sfxenum_t.sfx_pstart); + } else { + if (res == result_e.pastdest) { + plat.count = plat.wait; + plat.status = plat_e.waiting; + StartSound(plat.sector.soundorg, sounds.sfxenum_t.sfx_pstop); + + switch (plat.type) { + case blazeDWUS: + case downWaitUpStay: + RemoveActivePlat(plat); + break; + + case raiseAndChange: + case raiseToNearestAndChange: + RemoveActivePlat(plat); + break; + + default: + break; + } + } + } + break; + + case down: + res = MovePlane(plat.sector, plat.speed, plat.low, false, 0, -1); + + if (res == result_e.pastdest) { + plat.count = plat.wait; + plat.status = plat_e.waiting; + StartSound(plat.sector.soundorg, sounds.sfxenum_t.sfx_pstop); + } + break; + + case waiting: + if (--plat.count == 0) { + if (plat.sector.floorheight == plat.low) { + plat.status = plat_e.up; + } else { + plat.status = plat_e.down; + } + StartSound(plat.sector.soundorg, sounds.sfxenum_t.sfx_pstart); + } + case in_stasis: + break; + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsLights.java b/doom/src/p/Actions/ActionsLights.java new file mode 100644 index 0000000..67643e9 --- /dev/null +++ b/doom/src/p/Actions/ActionsLights.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import doom.SourceCode; +import doom.SourceCode.P_Lights; +import static doom.SourceCode.P_Lights.P_SpawnFireFlicker; +import static doom.SourceCode.P_Lights.P_SpawnGlowingLight; +import static doom.SourceCode.P_Lights.P_SpawnLightFlash; +import static doom.SourceCode.P_Lights.P_SpawnStrobeFlash; +import doom.SourceCode.P_Spec; +import static doom.SourceCode.P_Spec.P_FindMinSurroundingLight; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import p.AbstractLevelLoader; +import static p.ActiveStates.T_FireFlicker; +import static p.ActiveStates.T_Glow; +import static p.ActiveStates.T_LightFlash; +import static p.ActiveStates.T_StrobeFlash; +import static p.DoorDefines.SLOWDARK; +import static p.DoorDefines.STROBEBRIGHT; +import p.strobe_t; +import rr.SectorAction; +import rr.line_t; +import rr.sector_t; +import w.DoomIO; + +public interface ActionsLights extends ActionsMoveEvents, ActionsUseEvents { + + int FindSectorFromLineTag(line_t line, int secnum); + + // + // P_LIGHTS + // + public class fireflicker_t extends SectorAction { + + public int count; + public int maxlight; + public int minlight; + } + + // + // BROKEN LIGHT EFFECT + // + public class lightflash_t extends SectorAction { + + public int count; + public int maxlight; + public int minlight; + public int maxtime; + public int mintime; + + @Override + public void read(DataInputStream f) throws IOException { + super.read(f); // Call thinker reader first + super.sectorid = DoomIO.readLEInt(f); // Sector index + count = DoomIO.readLEInt(f); + maxlight = DoomIO.readLEInt(f); + minlight = DoomIO.readLEInt(f); + maxtime = DoomIO.readLEInt(f); + mintime = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(super.sectorid); // 16 + b.putInt(count); //20 + b.putInt(maxlight);//24 + b.putInt(minlight);//28 + b.putInt(maxtime);//32 + b.putInt(mintime);//36 + } + } + + public class glow_t extends SectorAction { + + public int minlight; + public int maxlight; + public int direction; + + @Override + public void read(DataInputStream f) throws IOException { + + super.read(f); // Call thinker reader first + super.sectorid = DoomIO.readLEInt(f); // Sector index + minlight = DoomIO.readLEInt(f); + maxlight = DoomIO.readLEInt(f); + direction = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(super.sectorid); // 16 + b.putInt(minlight);//20 + b.putInt(maxlight);//24 + b.putInt(direction);//38 + } + } + + // + // Find minimum light from an adjacent sector + // + @SourceCode.Exact + @P_Spec.C(P_FindMinSurroundingLight) + default int FindMinSurroundingLight(sector_t sector, int max) { + int min; + line_t line; + sector_t check; + + min = max; + for (int i = 0; i < sector.linecount; i++) { + line = sector.lines[i]; + getNextSector: + { + check = line.getNextSector(sector); + } + + if (check == null) { + continue; + } + + if (check.lightlevel < min) { + min = check.lightlevel; + } + } + return min; + } + + /** + * P_SpawnLightFlash After the map has been loaded, scan each sector for + * specials that spawn thinkers + */ + @SourceCode.Exact + @P_Lights.C(P_SpawnLightFlash) + default void SpawnLightFlash(sector_t sector) { + lightflash_t flash; + + // nothing special about it during gameplay + sector.special = 0; + + Z_Malloc: + { + flash = new lightflash_t(); + } + + P_AddThinker: + { + AddThinker(flash); + } + + flash.thinkerFunction = T_LightFlash; + flash.sector = sector; + flash.maxlight = sector.lightlevel; + + flash.minlight = FindMinSurroundingLight(sector, sector.lightlevel); + flash.maxtime = 64; + flash.mintime = 7; + flash.count = (P_Random() & flash.maxtime) + 1; + } + + // + // P_SpawnStrobeFlash + // After the map has been loaded, scan each sector + // for specials that spawn thinkers + // + @SourceCode.Exact + @P_Lights.C(P_SpawnStrobeFlash) + default void SpawnStrobeFlash(sector_t sector, int fastOrSlow, int inSync) { + strobe_t flash; + + Z_Malloc: + { + flash = new strobe_t(); + } + + P_AddThinker: + { + AddThinker(flash); + } + + flash.sector = sector; + flash.darktime = fastOrSlow; + flash.brighttime = STROBEBRIGHT; + flash.thinkerFunction = T_StrobeFlash; + flash.maxlight = sector.lightlevel; + flash.minlight = FindMinSurroundingLight(sector, sector.lightlevel); + + if (flash.minlight == flash.maxlight) { + flash.minlight = 0; + } + + // nothing special about it during gameplay + sector.special = 0; + + if (inSync == 0) { + flash.count = (P_Random() & 7) + 1; + } else { + flash.count = 1; + } + } + + @SourceCode.Exact + @P_Lights.C(P_SpawnGlowingLight) + default void SpawnGlowingLight(sector_t sector) { + glow_t g; + + Z_Malloc: + { + g = new glow_t(); + } + + P_AddThinker: + { + AddThinker(g); + } + + g.sector = sector; + P_FindMinSurroundingLight: + { + g.minlight = FindMinSurroundingLight(sector, sector.lightlevel); + } + g.maxlight = sector.lightlevel; + g.thinkerFunction = T_Glow; + g.direction = -1; + + sector.special = 0; + } + + // + // Start strobing lights (usually from a trigger) + // + @Override + default void StartLightStrobing(line_t line) { + final AbstractLevelLoader ll = levelLoader(); + + int secnum; + sector_t sec; + + secnum = -1; + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = ll.sectors[secnum]; + if (sec.specialdata != null) { + continue; + } + + SpawnStrobeFlash(sec, SLOWDARK, 0); + } + } + + // + // P_SpawnFireFlicker + // + @SourceCode.Exact + @P_Lights.C(P_SpawnFireFlicker) + default void SpawnFireFlicker(sector_t sector) { + fireflicker_t flick; + + // Note that we are resetting sector attributes. + // Nothing special about it during gameplay. + sector.special = 0; + + Z_Malloc: + { + flick = new fireflicker_t(); + } + + P_AddThinker: + { + AddThinker(flick); + } + + flick.thinkerFunction = T_FireFlicker; + flick.sector = sector; + flick.maxlight = sector.lightlevel; + flick.minlight = FindMinSurroundingLight(sector, sector.lightlevel) + 16; + flick.count = 4; + } + + // + // TURN LINE'S TAG LIGHTS OFF + // + @Override + default void TurnTagLightsOff(line_t line) { + final AbstractLevelLoader ll = levelLoader(); + + int i; + int min; + sector_t sector; + sector_t tsec; + line_t templine; + + for (int j = 0; j < ll.numsectors; j++) { + sector = ll.sectors[j]; + if (sector.tag == line.tag) { + + min = sector.lightlevel; + for (i = 0; i < sector.linecount; i++) { + templine = sector.lines[i]; + tsec = templine.getNextSector(sector); + if (tsec == null) { + continue; + } + if (tsec.lightlevel < min) { + min = tsec.lightlevel; + } + } + sector.lightlevel = (short) min; + } + } + } + + // + // TURN LINE'S TAG LIGHTS ON + // + @Override + default void LightTurnOn(line_t line, int bright) { + final AbstractLevelLoader ll = levelLoader(); + + sector_t sector; + sector_t temp; + line_t templine; + + for (int i = 0; i < ll.numsectors; i++) { + sector = ll.sectors[i]; + if (sector.tag == line.tag) { + // bright = 0 means to search + // for highest light level + // surrounding sector + if (bright == 0) { + for (int j = 0; j < sector.linecount; j++) { + templine = sector.lines[j]; + temp = templine.getNextSector(sector); + + if (temp == null) { + continue; + } + + if (temp.lightlevel > bright) { + bright = temp.lightlevel; + } + } + } + sector.lightlevel = (short) bright; + } + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsMissiles.java b/doom/src/p/Actions/ActionsMissiles.java new file mode 100644 index 0000000..35052fb --- /dev/null +++ b/doom/src/p/Actions/ActionsMissiles.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.mobjinfo; +import data.mobjtype_t; +import doom.SourceCode.angle_t; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static p.MapUtils.AproxDistance; +import p.mobj_t; +import static p.mobj_t.MF_MISSILE; +import static p.mobj_t.MF_SHADOW; +import static utils.C2JUtils.eval; + +public interface ActionsMissiles extends ActionsMobj { + + int AimLineAttack(mobj_t source, long an, int i); + + /** + * P_CheckMissileSpawn Moves the missile forward a bit and possibly explodes it right there. + * + * @param th + */ + default void CheckMissileSpawn(mobj_t th) { + th.mobj_tics -= P_Random() & 3; + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + + // move a little forward so an angle can + // be computed if it immediately explodes + th.x += (th.momx >> 1); + th.y += (th.momy >> 1); + th.z += (th.momz >> 1); + + if (!TryMove(th, th.x, th.y)) { + ExplodeMissile(th); + } + } + + /** + * P_SpawnMissile + */ + default mobj_t SpawnMissile(mobj_t source, mobj_t dest, mobjtype_t type) { + mobj_t th; + @angle_t + long an; + int dist; + + th = SpawnMobj(source.x, source.y, source.z + 4 * 8 * FRACUNIT, type); + + if (th.info.seesound != null) { + StartSound(th, th.info.seesound); + } + + th.target = source; // where it came from + an = sceneRenderer().PointToAngle2(source.x, source.y, dest.x, dest.y) & BITS32; + + // fuzzy player + if (eval(dest.flags & MF_SHADOW)) { + an += (P_Random() - P_Random()) << 20; + } + + th.angle = an & BITS32; + //an >>= ANGLETOFINESHIFT; + th.momx = FixedMul(th.info.speed, finecosine(an)); + th.momy = FixedMul(th.info.speed, finesine(an)); + + dist = AproxDistance(dest.x - source.x, dest.y - source.y); + dist /= th.info.speed; + + if (dist < 1) { + dist = 1; + } + + th.momz = (dest.z - source.z) / dist; + CheckMissileSpawn(th); + + return th; + } + + /** + * P_SpawnPlayerMissile Tries to aim at a nearby monster + */ + default void SpawnPlayerMissile(mobj_t source, mobjtype_t type) { + final Spawn targ = contextRequire(KEY_SPAWN); + + mobj_t th; + @angle_t + long an; + int x, y, z, slope; // ActionFunction + + // see which target is to be aimed at + an = source.angle; + slope = AimLineAttack(source, an, 16 * 64 * FRACUNIT); + + if (targ.linetarget == null) { + an += 1 << 26; + an &= BITS32; + slope = AimLineAttack(source, an, 16 * 64 * FRACUNIT); + + if (targ.linetarget == null) { + an -= 2 << 26; + an &= BITS32; + slope = AimLineAttack(source, an, 16 * 64 * FRACUNIT); + } + + if (targ.linetarget == null) { + an = source.angle & BITS32; + // angle should be "sane"..right? + // Just this line allows freelook. + slope = ((source.player.lookdir) << FRACBITS) / 173; + } + } + + x = source.x; + y = source.y; + z = source.z + 4 * 8 * FRACUNIT + slope; + + th = this.SpawnMobj(x, y, z, type); + + if (th.info.seesound != null) { + StartSound(th, th.info.seesound); + } + + th.target = source; + th.angle = an; + th.momx = FixedMul(th.info.speed, finecosine(an)); + th.momy = FixedMul(th.info.speed, finesine(an)); + th.momz = FixedMul(th.info.speed, slope); + + CheckMissileSpawn(th); + } + + /** + * P_ExplodeMissile + */ + @Override + default void ExplodeMissile(mobj_t mo) { + mo.momx = mo.momy = mo.momz = 0; + + // MAES 9/5/2011: using mobj code for that. + mo.SetMobjState(mobjinfo[mo.type.ordinal()].deathstate); + + mo.mobj_tics -= P_Random() & 3; + + if (mo.mobj_tics < 1) { + mo.mobj_tics = 1; + } + + mo.flags &= ~MF_MISSILE; + + if (mo.info.deathsound != null) { + StartSound(mo, mo.info.deathsound); + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsMobj.java b/doom/src/p/Actions/ActionsMobj.java new file mode 100644 index 0000000..c7cc6e6 --- /dev/null +++ b/doom/src/p/Actions/ActionsMobj.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.BASETHRESHOLD; +import static data.Defines.ITEMQUESIZE; +import static data.Defines.ONFLOORZ; +import static data.Defines.PST_DEAD; +import static data.Defines.pw_invulnerability; +import static data.Tables.ANG180; +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.states; +import data.mobjtype_t; +import defines.skill_t; +import defines.statenum_t; +import doom.SourceCode; +import doom.SourceCode.P_MapUtl; +import static doom.SourceCode.P_MapUtl.P_UnsetThingPosition; +import static doom.SourceCode.P_Mobj.P_RemoveMobj; +import doom.player_t; +import doom.weapontype_t; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static m.fixed_t.MAPFRACUNIT; +import p.AbstractLevelLoader; +import static p.MobjFlags.MF_DROPPED; +import static p.MobjFlags.MF_NOBLOCKMAP; +import static p.MobjFlags.MF_NOSECTOR; +import static p.MobjFlags.MF_SPECIAL; +import p.mobj_t; +import static p.mobj_t.MF_CORPSE; +import static p.mobj_t.MF_COUNTKILL; +import static p.mobj_t.MF_DROPOFF; +import static p.mobj_t.MF_FLOAT; +import static p.mobj_t.MF_JUSTHIT; +import static p.mobj_t.MF_NOCLIP; +import static p.mobj_t.MF_NOGRAVITY; +import static p.mobj_t.MF_SHOOTABLE; +import static p.mobj_t.MF_SKULLFLY; +import static p.mobj_t.MF_SOLID; +import static utils.C2JUtils.eval; + +public interface ActionsMobj extends ActionsThings, ActionsMovement, ActionsTeleportation { + + // + // P_DamageMobj + // Damages both enemies and players + // "inflictor" is the thing that caused the damage + // creature or missile, can be NULL (slime, etc) + // "source" is the thing to target after taking damage + // creature or NULL + // Source and inflictor are the same for melee attacks. + // Source can be NULL for slime, barrel explosions + // and other environmental stuff. + // + @Override + default void DamageMobj(mobj_t target, mobj_t inflictor, mobj_t source, int damage) { + long ang; // unsigned + int saved; + player_t player; + @SourceCode.fixed_t + int thrust; + int temp; + + if (!eval(target.flags & MF_SHOOTABLE)) { + return; // shouldn't happen... + } + if (target.health <= 0) { + return; + } + + if (eval(target.flags & MF_SKULLFLY)) { + target.momx = target.momy = target.momz = 0; + } + + player = target.player; + if ((player != null) && getGameSkill() == skill_t.sk_baby) { + damage >>= 1; // take half damage in trainer mode + } + + // Some close combat weapons should not + // inflict thrust and push the victim out of reach, + // thus kick away unless using the chainsaw. + if ((inflictor != null) + && !eval(target.flags & MF_NOCLIP) + && (source == null + || source.player == null + || source.player.readyweapon != weapontype_t.wp_chainsaw)) { + ang = sceneRenderer().PointToAngle2(inflictor.x, + inflictor.y, + target.x, + target.y) & BITS32; + + thrust = damage * (MAPFRACUNIT >> 3) * 100 / target.info.mass; + + // make fall forwards sometimes + if ((damage < 40) + && (damage > target.health) + && (target.z - inflictor.z > 64 * FRACUNIT) + && eval(P_Random() & 1)) { + ang += ANG180; + thrust *= 4; + } + + //ang >>= ANGLETOFINESHIFT; + target.momx += FixedMul(thrust, finecosine(ang)); + target.momy += FixedMul(thrust, finesine(ang)); + } + + // player specific + if (player != null) { + // end of game hell hack + if (target.subsector.sector.special == 11 + && damage >= target.health) { + damage = target.health - 1; + } + + // Below certain threshold, + // ignore damage in GOD mode, or with INVUL power. + if (damage < 1000 + && (eval(player.cheats & player_t.CF_GODMODE)) + || player.powers[pw_invulnerability] != 0) { + return; + } + + if (player.armortype != 0) { + if (player.armortype == 1) { + saved = damage / 3; + } else { + saved = damage / 2; + } + + if (player.armorpoints[0] <= saved) { + // armor is used up + saved = player.armorpoints[0]; + player.armortype = 0; + } + player.armorpoints[0] -= saved; + damage -= saved; + } + player.health[0] -= damage; // mirror mobj health here for Dave + if (player.health[0] < 0) { + player.health[0] = 0; + } + + player.attacker = source; + player.damagecount += damage; // add damage after armor / invuln + + if (player.damagecount > 100) { + player.damagecount = 100; // teleport stomp does 10k points... + } + temp = damage < 100 ? damage : 100; + + if (player == getPlayer(ConsolePlayerNumber())) { + doomSystem().Tactile(40, 10, 40 + temp * 2); + } + } + + // do the damage + target.health -= damage; + if (target.health <= 0) { + this.KillMobj(source, target); + return; + } + + if ((P_Random() < target.info.painchance) + && !eval(target.flags & MF_SKULLFLY)) { + target.flags |= MF_JUSTHIT; // fight back! + + target.SetMobjState(target.info.painstate); + } + + target.reactiontime = 0; // we're awake now... + + if (((target.threshold == 0) || (target.type == mobjtype_t.MT_VILE)) + && (source != null) && (source != target) + && (source.type != mobjtype_t.MT_VILE)) { + // if not intent on another player, + // chase after this one + target.target = source; + target.threshold = BASETHRESHOLD; + if (target.mobj_state == states[target.info.spawnstate.ordinal()] + && target.info.seestate != statenum_t.S_NULL) { + target.SetMobjState(target.info.seestate); + } + } + + } + + // + // KillMobj + // + default void KillMobj(mobj_t source, mobj_t target) { + mobjtype_t item; + mobj_t mo; + + // Maes: this seems necessary in order for barrel damage + // to propagate inflictors. + target.target = source; + + target.flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY); + + if (target.type != mobjtype_t.MT_SKULL) { + target.flags &= ~MF_NOGRAVITY; + } + + target.flags |= MF_CORPSE | MF_DROPOFF; + target.height >>= 2; + + if (source != null && source.player != null) { + // count for intermission + if ((target.flags & MF_COUNTKILL) != 0) { + source.player.killcount++; + } + + if (target.player != null) //; <-- _D_: that semicolon caused a bug! + { + source.player.frags[target.player.identify()]++; + } + // It's probably intended to increment the frags of source player vs target player. Lookup? + } else if (!IsNetGame() && ((target.flags & MF_COUNTKILL) != 0)) { + // count all monster deaths, + // even those caused by other monsters + getPlayer(0).killcount++; + } + + if (target.player != null) { + // count environment kills against you + if (source == null) // TODO: some way to indentify which one of the + // four possiblelayers is the current player + { + target.player.frags[target.player.identify()]++; + } + + target.flags &= ~MF_SOLID; + target.player.playerstate = PST_DEAD; + target.player.DropWeapon(); // in PSPR + + if (target.player == getPlayer(ConsolePlayerNumber()) && IsAutoMapActive()) { + // don't die in auto map, + // switch view prior to dying + autoMap().Stop(); + } + + } + + if (target.health < -target.info.spawnhealth && target.info.xdeathstate != statenum_t.S_NULL) { + target.SetMobjState(target.info.xdeathstate); + } else { + target.SetMobjState(target.info.deathstate); + } + target.mobj_tics -= P_Random() & 3; + + if (target.mobj_tics < 1) { + target.mobj_tics = 1; + } + + // I_StartSound (&actor.r, actor.info.deathsound); + // Drop stuff. + // This determines the kind of object spawned + // during the death frame of a thing. + switch (target.type) { + case MT_WOLFSS: + case MT_POSSESSED: + item = mobjtype_t.MT_CLIP; + break; + + case MT_SHOTGUY: + item = mobjtype_t.MT_SHOTGUN; + break; + + case MT_CHAINGUY: + item = mobjtype_t.MT_CHAINGUN; + break; + + default: + return; + } + + mo = SpawnMobj(target.x, target.y, ONFLOORZ, item); + mo.flags |= MF_DROPPED; // special versions of items + } + + @Override + @SourceCode.Exact + @SourceCode.P_Mobj.C(P_RemoveMobj) + default void RemoveMobj(mobj_t mobj) { + if (eval(mobj.flags & MF_SPECIAL) + && !eval(mobj.flags & MF_DROPPED) + && (mobj.type != mobjtype_t.MT_INV) + && (mobj.type != mobjtype_t.MT_INS)) { + final RespawnQueue resp = contextRequire(KEY_RESP_QUEUE); + resp.itemrespawnque[resp.iquehead] = mobj.spawnpoint; + resp.itemrespawntime[resp.iquehead] = LevelTime(); + resp.iquehead = (resp.iquehead + 1) & (ITEMQUESIZE - 1); + + // lose one off the end? + if (resp.iquehead == resp.iquetail) { + resp.iquetail = (resp.iquetail + 1) & (ITEMQUESIZE - 1); + } + } + + // unlink from sector and block lists + P_UnsetThingPosition: + { + UnsetThingPosition(mobj); + } + + // stop any playing sound + S_StopSound: + { + StopSound(mobj); + } + + // free block + P_RemoveThinker: + { + RemoveThinker(mobj); + } + } + + /** + * P_UnsetThingPosition Unlinks a thing from block map and sectors. On each + * position change, BLOCKMAP and other lookups maintaining lists ot things + * inside these structures need to be updated. + */ + @Override + @SourceCode.Exact + @P_MapUtl.C(P_UnsetThingPosition) + default void UnsetThingPosition(mobj_t thing) { + final AbstractLevelLoader ll = levelLoader(); + final int blockx; + final int blocky; + + if (!eval(thing.flags & MF_NOSECTOR)) { + // inert things don't need to be in blockmap? + // unlink from subsector + if (thing.snext != null) { + ((mobj_t) thing.snext).sprev = thing.sprev; + } + + if (thing.sprev != null) { + ((mobj_t) thing.sprev).snext = thing.snext; + } else { + thing.subsector.sector.thinglist = (mobj_t) thing.snext; + } + } + + if (!eval(thing.flags & MF_NOBLOCKMAP)) { + // inert things don't need to be in blockmap + // unlink from block map + if (thing.bnext != null) { + ((mobj_t) thing.bnext).bprev = thing.bprev; + } + + if (thing.bprev != null) { + ((mobj_t) thing.bprev).bnext = thing.bnext; + } else { + blockx = ll.getSafeBlockX(thing.x - ll.bmaporgx); + blocky = ll.getSafeBlockY(thing.y - ll.bmaporgy); + + if (blockx >= 0 && blockx < ll.bmapwidth + && blocky >= 0 && blocky < ll.bmapheight) { + ll.blocklinks[blocky * ll.bmapwidth + blockx] = (mobj_t) thing.bnext; + } + } + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsMoveEvents.java b/doom/src/p/Actions/ActionsMoveEvents.java new file mode 100644 index 0000000..35b3442 --- /dev/null +++ b/doom/src/p/Actions/ActionsMoveEvents.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import p.ceiling_e; +import p.floor_e; +import p.mobj_t; +import p.plattype_e; +import p.stair_e; +import p.vldoor_e; +import rr.line_t; + +public interface ActionsMoveEvents extends ActionTrait { + + boolean DoDoor(line_t line, vldoor_e type); + + boolean DoFloor(line_t line, floor_e floor_e); + + boolean DoPlat(line_t line, plattype_e plattype_e, int i); + + boolean BuildStairs(line_t line, stair_e stair_e); + + boolean DoCeiling(line_t line, ceiling_e ceiling_e); + + void StopPlat(line_t line); + + void LightTurnOn(line_t line, int i); + + void StartLightStrobing(line_t line); + + void TurnTagLightsOff(line_t line); + + int Teleport(line_t line, int side, mobj_t thing); + + int CeilingCrushStop(line_t line); + + // + //EVENTS + //Events are operations triggered by using, crossing, + //or shooting special lines, or by timed thinkers. + // + /** + * P_CrossSpecialLine - TRIGGER Called every time a thing origin is about to cross a line with a non 0 special. + */ + default void CrossSpecialLine(line_t line, int side, mobj_t thing) { + //line_t line; + boolean ok; + + //line = LL.lines[linenum]; + // Triggers that other things can activate + if (thing.player == null) { + // Things that should NOT trigger specials... + switch (thing.type) { + case MT_ROCKET: + case MT_PLASMA: + case MT_BFG: + case MT_TROOPSHOT: + case MT_HEADSHOT: + case MT_BRUISERSHOT: + return; + // break; + + default: + break; + } + + ok = false; + switch (line.special) { + case 39: // TELEPORT TRIGGER + case 97: // TELEPORT RETRIGGER + case 125: // TELEPORT MONSTERONLY TRIGGER + case 126: // TELEPORT MONSTERONLY RETRIGGER + case 4: // RAISE DOOR + case 10: // PLAT DOWN-WAIT-UP-STAY TRIGGER + case 88: // PLAT DOWN-WAIT-UP-STAY RETRIGGER + ok = true; + break; + } + if (!ok) { + return; + } + } + + // TODO: enum! + // Note: could use some const's here. + switch (line.special) { + // TRIGGERS. + // All from here to RETRIGGERS. + case 2: + // Open Door + DoDoor(line, vldoor_e.open); + line.special = 0; + break; + + case 3: + // Close Door + DoDoor(line, vldoor_e.close); + line.special = 0; + break; + + case 4: + // Raise Door + DoDoor(line, vldoor_e.normal); + line.special = 0; + break; + + case 5: + // Raise Floor + DoFloor(line, floor_e.raiseFloor); + line.special = 0; + break; + + case 6: + // Fast Ceiling Crush & Raise + DoCeiling(line, ceiling_e.fastCrushAndRaise); + line.special = 0; + break; + + case 8: + // Build Stairs + BuildStairs(line, stair_e.build8); + line.special = 0; + break; + + case 10: + // PlatDownWaitUp + DoPlat(line, plattype_e.downWaitUpStay, 0); + line.special = 0; + break; + + case 12: + // Light Turn On - brightest near + LightTurnOn(line, 0); + line.special = 0; + break; + + case 13: + // Light Turn On 255 + LightTurnOn(line, 255); + line.special = 0; + break; + + case 16: + // Close Door 30 + DoDoor(line, vldoor_e.close30ThenOpen); + line.special = 0; + break; + + case 17: + // Start Light Strobing + StartLightStrobing(line); + line.special = 0; + break; + + case 19: + // Lower Floor + DoFloor(line, floor_e.lowerFloor); + line.special = 0; + break; + + case 22: + // Raise floor to nearest height and change texture + DoPlat(line, plattype_e.raiseToNearestAndChange, 0); + line.special = 0; + break; + + case 25: + // Ceiling Crush and Raise + DoCeiling(line, ceiling_e.crushAndRaise); + line.special = 0; + break; + + case 30: + // Raise floor to shortest texture height + // on either side of lines. + DoFloor(line, floor_e.raiseToTexture); + line.special = 0; + break; + + case 35: + // Lights Very Dark + LightTurnOn(line, 35); + line.special = 0; + break; + + case 36: + // Lower Floor (TURBO) + DoFloor(line, floor_e.turboLower); + line.special = 0; + break; + + case 37: + // LowerAndChange + DoFloor(line, floor_e.lowerAndChange); + line.special = 0; + break; + + case 38: + // Lower Floor To Lowest + DoFloor(line, floor_e.lowerFloorToLowest); + line.special = 0; + break; + + case 39: + // TELEPORT! + Teleport(line, side, thing); + line.special = 0; + break; + + case 40: + // RaiseCeilingLowerFloor + DoCeiling(line, ceiling_e.raiseToHighest); + DoFloor(line, floor_e.lowerFloorToLowest); + line.special = 0; + break; + + case 44: + // Ceiling Crush + DoCeiling(line, ceiling_e.lowerAndCrush); + line.special = 0; + break; + + case 52: + // EXIT! + DOOM().ExitLevel(); + break; + + case 53: + // Perpetual Platform Raise + DoPlat(line, plattype_e.perpetualRaise, 0); + line.special = 0; + break; + + case 54: + // Platform Stop + StopPlat(line); + line.special = 0; + break; + + case 56: + // Raise Floor Crush + DoFloor(line, floor_e.raiseFloorCrush); + line.special = 0; + break; + + case 57: + // Ceiling Crush Stop + CeilingCrushStop(line); + line.special = 0; + break; + + case 58: + // Raise Floor 24 + DoFloor(line, floor_e.raiseFloor24); + line.special = 0; + break; + + case 59: + // Raise Floor 24 And Change + DoFloor(line, floor_e.raiseFloor24AndChange); + line.special = 0; + break; + + case 104: + // Turn lights off in sector(tag) + TurnTagLightsOff(line); + line.special = 0; + break; + + case 108: + // Blazing Door Raise (faster than TURBO!) + DoDoor(line, vldoor_e.blazeRaise); + line.special = 0; + break; + + case 109: + // Blazing Door Open (faster than TURBO!) + DoDoor(line, vldoor_e.blazeOpen); + line.special = 0; + break; + + case 100: + // Build Stairs Turbo 16 + BuildStairs(line, stair_e.turbo16); + line.special = 0; + break; + + case 110: + // Blazing Door Close (faster than TURBO!) + DoDoor(line, vldoor_e.blazeClose); + line.special = 0; + break; + + case 119: + // Raise floor to nearest surr. floor + DoFloor(line, floor_e.raiseFloorToNearest); + line.special = 0; + break; + + case 121: + // Blazing PlatDownWaitUpStay + DoPlat(line, plattype_e.blazeDWUS, 0); + line.special = 0; + break; + + case 124: + // Secret EXIT + DOOM().SecretExitLevel(); + break; + + case 125: + // TELEPORT MonsterONLY + if (thing.player == null) { + Teleport(line, side, thing); + line.special = 0; + } + break; + + case 130: + // Raise Floor Turbo + DoFloor(line, floor_e.raiseFloorTurbo); + line.special = 0; + break; + + case 141: + // Silent Ceiling Crush & Raise + DoCeiling(line, ceiling_e.silentCrushAndRaise); + line.special = 0; + break; + + // RETRIGGERS. All from here till end. + case 72: + // Ceiling Crush + DoCeiling(line, ceiling_e.lowerAndCrush); + break; + + case 73: + // Ceiling Crush and Raise + DoCeiling(line, ceiling_e.crushAndRaise); + break; + + case 74: + // Ceiling Crush Stop + CeilingCrushStop(line); + break; + + case 75: + // Close Door + DoDoor(line, vldoor_e.close); + break; + + case 76: + // Close Door 30 + DoDoor(line, vldoor_e.close30ThenOpen); + break; + + case 77: + // Fast Ceiling Crush & Raise + DoCeiling(line, ceiling_e.fastCrushAndRaise); + break; + + case 79: + // Lights Very Dark + LightTurnOn(line, 35); + break; + + case 80: + // Light Turn On - brightest near + LightTurnOn(line, 0); + break; + + case 81: + // Light Turn On 255 + LightTurnOn(line, 255); + break; + + case 82: + // Lower Floor To Lowest + DoFloor(line, floor_e.lowerFloorToLowest); + break; + + case 83: + // Lower Floor + DoFloor(line, floor_e.lowerFloor); + break; + + case 84: + // LowerAndChange + DoFloor(line, floor_e.lowerAndChange); + break; + + case 86: + // Open Door + DoDoor(line, vldoor_e.open); + break; + + case 87: + // Perpetual Platform Raise + DoPlat(line, plattype_e.perpetualRaise, 0); + break; + + case 88: + // PlatDownWaitUp + DoPlat(line, plattype_e.downWaitUpStay, 0); + break; + + case 89: + // Platform Stop + StopPlat(line); + break; + + case 90: + // Raise Door + DoDoor(line, vldoor_e.normal); + break; + + case 91: + // Raise Floor + DoFloor(line, floor_e.raiseFloor); + break; + + case 92: + // Raise Floor 24 + DoFloor(line, floor_e.raiseFloor24); + break; + + case 93: + // Raise Floor 24 And Change + DoFloor(line, floor_e.raiseFloor24AndChange); + break; + + case 94: + // Raise Floor Crush + DoFloor(line, floor_e.raiseFloorCrush); + break; + + case 95: + // Raise floor to nearest height + // and change texture. + DoPlat(line, plattype_e.raiseToNearestAndChange, 0); + break; + + case 96: + // Raise floor to shortest texture height + // on either side of lines. + DoFloor(line, floor_e.raiseToTexture); + break; + + case 97: + // TELEPORT! + Teleport(line, side, thing); + break; + + case 98: + // Lower Floor (TURBO) + DoFloor(line, floor_e.turboLower); + break; + + case 105: + // Blazing Door Raise (faster than TURBO!) + DoDoor(line, vldoor_e.blazeRaise); + break; + + case 106: + // Blazing Door Open (faster than TURBO!) + DoDoor(line, vldoor_e.blazeOpen); + break; + + case 107: + // Blazing Door Close (faster than TURBO!) + DoDoor(line, vldoor_e.blazeClose); + break; + + case 120: + // Blazing PlatDownWaitUpStay. + DoPlat(line, plattype_e.blazeDWUS, 0); + break; + + case 126: + // TELEPORT MonsterONLY. + if (thing.player == null) { + Teleport(line, side, thing); + } + break; + + case 128: + // Raise To Nearest Floor + DoFloor(line, floor_e.raiseFloorToNearest); + break; + + case 129: + // Raise Floor Turbo + DoFloor(line, floor_e.raiseFloorTurbo); + break; + } + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsMovement.java b/doom/src/p/Actions/ActionsMovement.java new file mode 100644 index 0000000..999e540 --- /dev/null +++ b/doom/src/p/Actions/ActionsMovement.java @@ -0,0 +1,681 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.FLOATSPEED; +import static data.Defines.PT_ADDLINES; +import static data.Limits.MAXMOVE; +import static data.Tables.ANG180; +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import defines.slopetype_t; +import defines.statenum_t; +import doom.SourceCode; +import static doom.SourceCode.P_Map.PTR_SlideTraverse; +import doom.SourceCode.fixed_t; +import doom.player_t; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static p.ChaseDirections.DI_EAST; +import static p.ChaseDirections.DI_NODIR; +import static p.ChaseDirections.DI_NORTH; +import static p.ChaseDirections.DI_SOUTH; +import static p.ChaseDirections.DI_SOUTHEAST; +import static p.ChaseDirections.DI_WEST; +import static p.ChaseDirections.diags; +import static p.ChaseDirections.opposite; +import static p.ChaseDirections.xspeed; +import static p.ChaseDirections.yspeed; +import static p.MapUtils.AproxDistance; +import p.intercept_t; +import p.mobj_t; +import static p.mobj_t.MF_CORPSE; +import static p.mobj_t.MF_DROPOFF; +import static p.mobj_t.MF_FLOAT; +import static p.mobj_t.MF_INFLOAT; +import static p.mobj_t.MF_MISSILE; +import static p.mobj_t.MF_NOCLIP; +import static p.mobj_t.MF_SKULLFLY; +import static p.mobj_t.MF_TELEPORT; +import rr.SceneRenderer; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import static utils.C2JUtils.eval; +import utils.TraitFactory.ContextKey; + +public interface ActionsMovement extends ActionsPathTraverse { + + ContextKey KEY_DIRTYPE = ACTION_KEY_CHAIN.newKey(ActionsMovement.class, DirType::new); + + // + // P_XYMovement + // + int STOPSPEED = 4096; + int FRICTION = 59392; + int FUDGE = 2048; ///(FRACUNIT/MAPFRACUNIT); + + void UnsetThingPosition(mobj_t thing); + + void ExplodeMissile(mobj_t mo); + + final class DirType { + + //dirtype + int d1; + int d2; + } + + ///////////////// MOVEMENT'S ACTIONS //////////////////////// + /** + * If "floatok" true, move would be ok if within "tmfloorz - tmceilingz". + */ + // + // P_Move + // Move in the current direction, + // returns false if the move is blocked. + // + default boolean Move(mobj_t actor) { + final Movement mov = contextRequire(KEY_MOVEMENT); + final Spechits sp = contextRequire(KEY_SPECHITS); + + @fixed_t + int tryx, tryy; + line_t ld; + + // warning: 'catch', 'throw', and 'try' + // are all C++ reserved words + boolean try_ok; + boolean good; + + if (actor.movedir == DI_NODIR) { + return false; + } + + if (actor.movedir >= 8) { + doomSystem().Error("Weird actor.movedir!"); + } + + tryx = actor.x + actor.info.speed * xspeed[actor.movedir]; + tryy = actor.y + actor.info.speed * yspeed[actor.movedir]; + + try_ok = this.TryMove(actor, tryx, tryy); + + if (!try_ok) { + // open any specials + if (eval(actor.flags & MF_FLOAT) && mov.floatok) { + // must adjust height + if (actor.z < mov.tmfloorz) { + actor.z += FLOATSPEED; + } else { + actor.z -= FLOATSPEED; + } + + actor.flags |= MF_INFLOAT; + return true; + } + + if (sp.numspechit == 0) { + return false; + } + + actor.movedir = DI_NODIR; + good = false; + while ((sp.numspechit--) > 0) { + ld = sp.spechit[sp.numspechit]; + // if the special is not a door + // that can be opened, + // return false + if (UseSpecialLine(actor, ld, false)) { + good = true; + } + } + return good; + } else { + actor.flags &= ~MF_INFLOAT; + } + + if (!eval(actor.flags & MF_FLOAT)) { + actor.z = actor.floorz; + } + return true; + } + + /** + * // P_TryMove // Attempt to move to a new position, // crossing special lines unless MF_TELEPORT is set. + * + * @param x fixed_t + * @param y fixed_t + * + */ + default boolean TryMove(mobj_t thing, @fixed_t int x, @fixed_t int y) { + final Movement mov = contextRequire(KEY_MOVEMENT); + final Spechits sp = contextRequire(KEY_SPECHITS); + + @fixed_t + int oldx, oldy; + boolean side, oldside; // both were int + line_t ld; + + mov.floatok = false; + if (!this.CheckPosition(thing, x, y)) { + return false; // solid wall or thing + } + if (!eval(thing.flags & MF_NOCLIP)) { + if (mov.tmceilingz - mov.tmfloorz < thing.height) { + return false; // doesn't fit + } + mov.floatok = true; + + if (!eval(thing.flags & MF_TELEPORT) && mov.tmceilingz - thing.z < thing.height) { + return false; // mobj must lower itself to fit + } + if (!eval(thing.flags & MF_TELEPORT) && mov.tmfloorz - thing.z > 24 * FRACUNIT) { + return false; // too big a step up + } + if (!eval(thing.flags & (MF_DROPOFF | MF_FLOAT)) && mov.tmfloorz - mov.tmdropoffz > 24 * FRACUNIT) { + return false; // don't stand over a dropoff + } + } + + // the move is ok, + // so link the thing into its new position + UnsetThingPosition(thing); + + oldx = thing.x; + oldy = thing.y; + thing.floorz = mov.tmfloorz; + thing.ceilingz = mov.tmceilingz; + thing.x = x; + thing.y = y; + + levelLoader().SetThingPosition(thing); + + // if any special lines were hit, do the effect + if (!eval(thing.flags & (MF_TELEPORT | MF_NOCLIP))) { + while (sp.numspechit-- > 0) { + // see if the line was crossed + ld = sp.spechit[sp.numspechit]; + side = ld.PointOnLineSide(thing.x, thing.y); + oldside = ld.PointOnLineSide(oldx, oldy); + if (side != oldside) { + if (ld.special != 0) { + CrossSpecialLine(ld, oldside ? 1 : 0, thing); + } + } + } + } + + return true; + } + + default void NewChaseDir(mobj_t actor) { + final DirType dirtype = contextRequire(KEY_DIRTYPE); + + @fixed_t + int deltax, deltay; + + int tdir; + int olddir; + // dirtypes + int turnaround; + + if (actor.target == null) { + doomSystem().Error("P_NewChaseDir: called with no target"); + } + + olddir = actor.movedir; + turnaround = opposite[olddir]; + + deltax = actor.target.x - actor.x; + deltay = actor.target.y - actor.y; + + if (deltax > 10 * FRACUNIT) { + dirtype.d1 = DI_EAST; + } else if (deltax < -10 * FRACUNIT) { + dirtype.d1 = DI_WEST; + } else { + dirtype.d1 = DI_NODIR; + } + + if (deltay < -10 * FRACUNIT) { + dirtype.d2 = DI_SOUTH; + } else if (deltay > 10 * FRACUNIT) { + dirtype.d2 = DI_NORTH; + } else { + dirtype.d2 = DI_NODIR; + } + + // try direct route + if (dirtype.d1 != DI_NODIR && dirtype.d2 != DI_NODIR) { + actor.movedir = diags[(eval(deltay < 0) << 1) + eval(deltax > 0)]; + if (actor.movedir != turnaround && this.TryWalk(actor)) { + return; + } + } + + // try other directions + if (P_Random() > 200 || Math.abs(deltay) > Math.abs(deltax)) { + tdir = dirtype.d1; + dirtype.d1 = dirtype.d2; + dirtype.d2 = tdir; + } + + if (dirtype.d1 == turnaround) { + dirtype.d1 = DI_NODIR; + } + + if (dirtype.d2 == turnaround) { + dirtype.d2 = DI_NODIR; + } + + if (dirtype.d1 != DI_NODIR) { + actor.movedir = dirtype.d1; + if (this.TryWalk(actor)) { + // either moved forward or attacked + return; + } + } + + if (dirtype.d2 != DI_NODIR) { + actor.movedir = dirtype.d2; + + if (this.TryWalk(actor)) { + return; + } + } + + // there is no direct path to the player, + // so pick another direction. + if (olddir != DI_NODIR) { + actor.movedir = olddir; + + if (this.TryWalk(actor)) { + return; + } + } + + // randomly determine direction of search + if (eval(P_Random() & 1)) { + for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++) { + if (tdir != turnaround) { + actor.movedir = tdir; + + if (TryWalk(actor)) { + return; + } + } + } + } else { + for (tdir = DI_SOUTHEAST; tdir != (DI_EAST - 1); tdir--) { + if (tdir != turnaround) { + actor.movedir = tdir; + + if (TryWalk(actor)) { + return; + } + } + } + } + + if (turnaround != DI_NODIR) { + actor.movedir = turnaround; + if (TryWalk(actor)) { + return; + } + } + + actor.movedir = DI_NODIR; // can not move + } + + /** + * TryWalk Attempts to move actor on in its current (ob.moveangle) direction. If blocked by either a wall or an + * actor returns FALSE If move is either clear or blocked only by a door, returns TRUE and sets... If a door is in + * the way, an OpenDoor call is made to start it opening. + */ + default boolean TryWalk(mobj_t actor) { + if (!Move(actor)) { + return false; + } + + actor.movecount = P_Random() & 15; + return true; + } + + // + // P_HitSlideLine + // Adjusts the xmove / ymove + // so that the next move will slide along the wall. + // + default void HitSlideLine(line_t ld) { + final SceneRenderer sr = sceneRenderer(); + final SlideMove slideMove = contextRequire(KEY_SLIDEMOVE); + boolean side; + + // all angles + long lineangle, moveangle, deltaangle; + + @fixed_t + int movelen, newlen; + + if (ld.slopetype == slopetype_t.ST_HORIZONTAL) { + slideMove.tmymove = 0; + return; + } + + if (ld.slopetype == slopetype_t.ST_VERTICAL) { + slideMove.tmxmove = 0; + return; + } + + side = ld.PointOnLineSide(slideMove.slidemo.x, slideMove.slidemo.y); + + lineangle = sr.PointToAngle2(0, 0, ld.dx, ld.dy); + + if (side == true) { + lineangle += ANG180; + } + + moveangle = sr.PointToAngle2(0, 0, slideMove.tmxmove, slideMove.tmymove); + deltaangle = (moveangle - lineangle) & BITS32; + + if (deltaangle > ANG180) { + deltaangle += ANG180; + } + // system.Error ("SlideLine: ang>ANG180"); + + //lineangle >>>= ANGLETOFINESHIFT; + //deltaangle >>>= ANGLETOFINESHIFT; + movelen = AproxDistance(slideMove.tmxmove, slideMove.tmymove); + newlen = FixedMul(movelen, finecosine(deltaangle)); + + slideMove.tmxmove = FixedMul(newlen, finecosine(lineangle)); + slideMove.tmymove = FixedMul(newlen, finesine(lineangle)); + } + + ///(FRACUNIT/MAPFRACUNIT); + // + // P_SlideMove + // The momx / momy move is bad, so try to slide + // along a wall. + // Find the first line hit, move flush to it, + // and slide along it + // + // This is a kludgy mess. + // + default void SlideMove(mobj_t mo) { + final SlideMove slideMove = contextRequire(KEY_SLIDEMOVE); + @fixed_t + int leadx, leady, trailx, traily, newx, newy; + int hitcount; + + slideMove.slidemo = mo; + hitcount = 0; + + do { + if (++hitcount == 3) { + // goto stairstep + this.stairstep(mo); + return; + } // don't loop forever + + // trace along the three leading corners + if (mo.momx > 0) { + leadx = mo.x + mo.radius; + trailx = mo.x - mo.radius; + } else { + leadx = mo.x - mo.radius; + trailx = mo.x + mo.radius; + } + + if (mo.momy > 0) { + leady = mo.y + mo.radius; + traily = mo.y - mo.radius; + } else { + leady = mo.y - mo.radius; + traily = mo.y + mo.radius; + } + + slideMove.bestslidefrac = FRACUNIT + 1; + + PathTraverse(leadx, leady, leadx + mo.momx, leady + mo.momy, PT_ADDLINES, this::SlideTraverse); + PathTraverse(trailx, leady, trailx + mo.momx, leady + mo.momy, PT_ADDLINES, this::SlideTraverse); + PathTraverse(leadx, traily, leadx + mo.momx, traily + mo.momy, PT_ADDLINES, this::SlideTraverse); + + // move up to the wall + if (slideMove.bestslidefrac == FRACUNIT + 1) { + // the move most have hit the middle, so stairstep + this.stairstep(mo); + return; + } // don't loop forever + + // fudge a bit to make sure it doesn't hit + slideMove.bestslidefrac -= FUDGE; + if (slideMove.bestslidefrac > 0) { + newx = FixedMul(mo.momx, slideMove.bestslidefrac); + newy = FixedMul(mo.momy, slideMove.bestslidefrac); + + if (!this.TryMove(mo, mo.x + newx, mo.y + newy)) { + // goto stairstep + this.stairstep(mo); + return; + } // don't loop forever + } + + // Now continue along the wall. + // First calculate remainder. + slideMove.bestslidefrac = FRACUNIT - (slideMove.bestslidefrac + FUDGE); + + if (slideMove.bestslidefrac > FRACUNIT) { + slideMove.bestslidefrac = FRACUNIT; + } + + if (slideMove.bestslidefrac <= 0) { + return; + } + + slideMove.tmxmove = FixedMul(mo.momx, slideMove.bestslidefrac); + slideMove.tmymove = FixedMul(mo.momy, slideMove.bestslidefrac); + + HitSlideLine(slideMove.bestslideline); // clip the moves + + mo.momx = slideMove.tmxmove; + mo.momy = slideMove.tmymove; + + } // goto retry + while (!TryMove(mo, mo.x + slideMove.tmxmove, mo.y + slideMove.tmymove)); + } + + /** + * Fugly "goto stairstep" simulation + * + * @param mo + */ + default void stairstep(mobj_t mo) { + if (!TryMove(mo, mo.x, mo.y + mo.momy)) { + TryMove(mo, mo.x + mo.momx, mo.y); + } + } + + // + // P_XYMovement + // + default void XYMovement(mobj_t mo) { + final Movement mv = contextRequire(KEY_MOVEMENT); + + @fixed_t + int ptryx, ptryy; // pointers to fixed_t ??? + @fixed_t + int xmove, ymove; + player_t player; + + if ((mo.momx == 0) && (mo.momy == 0)) { + if ((mo.flags & MF_SKULLFLY) != 0) { + // the skull slammed into something + mo.flags &= ~MF_SKULLFLY; + mo.momx = mo.momy = mo.momz = 0; + + mo.SetMobjState(mo.info.spawnstate); + } + return; + } + + player = mo.player; + + if (mo.momx > MAXMOVE) { + mo.momx = MAXMOVE; + } else if (mo.momx < -MAXMOVE) { + mo.momx = -MAXMOVE; + } + + if (mo.momy > MAXMOVE) { + mo.momy = MAXMOVE; + } else if (mo.momy < -MAXMOVE) { + mo.momy = -MAXMOVE; + } + + xmove = mo.momx; + ymove = mo.momy; + + do { + if (xmove > MAXMOVE / 2 || ymove > MAXMOVE / 2) { + ptryx = mo.x + xmove / 2; + ptryy = mo.y + ymove / 2; + xmove >>= 1; + ymove >>= 1; + } else { + ptryx = mo.x + xmove; + ptryy = mo.y + ymove; + xmove = ymove = 0; + } + + if (!TryMove(mo, ptryx, ptryy)) { + // blocked move + if (mo.player != null) { // try to slide along it + SlideMove(mo); + } else if (eval(mo.flags & MF_MISSILE)) { + // explode a missile + if (mv.ceilingline != null && mv.ceilingline.backsector != null + && mv.ceilingline.backsector.ceilingpic == DOOM().textureManager.getSkyFlatNum()) { + // Hack to prevent missiles exploding + // against the sky. + // Does not handle sky floors. + RemoveMobj(mo); + return; + } + ExplodeMissile(mo); + } else { + mo.momx = mo.momy = 0; + } + } + } while ((xmove | ymove) != 0); + + // slow down + if (player != null && eval(player.cheats & player_t.CF_NOMOMENTUM)) { + // debug option for no sliding at all + mo.momx = mo.momy = 0; + return; + } + + if (eval(mo.flags & (MF_MISSILE | MF_SKULLFLY))) { + return; // no friction for missiles ever + } + if (mo.z > mo.floorz) { + return; // no friction when airborne + } + if (eval(mo.flags & MF_CORPSE)) { + // do not stop sliding + // if halfway off a step with some momentum + if (mo.momx > FRACUNIT / 4 + || mo.momx < -FRACUNIT / 4 + || mo.momy > FRACUNIT / 4 + || mo.momy < -FRACUNIT / 4) { + if (mo.floorz != mo.subsector.sector.floorheight) { + return; + } + } + } + + if (mo.momx > -STOPSPEED && mo.momx < STOPSPEED && mo.momy > -STOPSPEED && mo.momy < STOPSPEED + && (player == null || (player.cmd.forwardmove == 0 && player.cmd.sidemove == 0))) { + // if in a walking frame, stop moving + // TODO: we need a way to get state indexed inside of states[], to sim pointer arithmetic. + // FIX: added an "id" field. + if (player != null && player.mo.mobj_state.id - statenum_t.S_PLAY_RUN1.ordinal() < 4) { + player.mo.SetMobjState(statenum_t.S_PLAY); + } + + mo.momx = 0; + mo.momy = 0; + } else { + mo.momx = FixedMul(mo.momx, FRICTION); + mo.momy = FixedMul(mo.momy, FRICTION); + } + } + + // + // SLIDE MOVE + // Allows the player to slide along any angled walls. + // + // fixed + // + // PTR_SlideTraverse + // + @SourceCode.P_Map.C(PTR_SlideTraverse) + default boolean SlideTraverse(intercept_t in) { + final SlideMove slideMove = contextRequire(KEY_SLIDEMOVE); + final Movement ma = contextRequire(KEY_MOVEMENT); + line_t li; + + if (!in.isaline) { + doomSystem().Error("PTR_SlideTraverse: not a line?"); + } + + li = (line_t) in.d(); + + if (!eval(li.flags & ML_TWOSIDED)) { + if (li.PointOnLineSide(slideMove.slidemo.x, slideMove.slidemo.y)) { + // don't hit the back side + return true; + } + return this.isblocking(in, li); + } + + // set openrange, opentop, openbottom + LineOpening(li); + + if ((ma.openrange < slideMove.slidemo.height) + || // doesn't fit + (ma.opentop - slideMove.slidemo.z < slideMove.slidemo.height) + || // mobj is too high + (ma.openbottom - slideMove.slidemo.z > 24 * FRACUNIT)) // too big a step up + { + if (in.frac < slideMove.bestslidefrac) { + slideMove.secondslidefrac = slideMove.bestslidefrac; + slideMove.secondslideline = slideMove.bestslideline; + slideMove.bestslidefrac = in.frac; + slideMove.bestslideline = li; + } + + return false; // stop + } else { // this line doesn't block movement + return true; + } + } +; +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsPathTraverse.java b/doom/src/p/Actions/ActionsPathTraverse.java new file mode 100644 index 0000000..456d10f --- /dev/null +++ b/doom/src/p/Actions/ActionsPathTraverse.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.MAPBLOCKSHIFT; +import static data.Defines.MAPBLOCKSIZE; +import static data.Defines.MAPBTOFRAC; +import static data.Defines.PT_ADDLINES; +import static data.Defines.PT_ADDTHINGS; +import static data.Defines.PT_EARLYOUT; +import static data.Limits.MAXINT; +import static data.Limits.MAXINTERCEPTS; +import doom.SourceCode.P_MapUtl; +import static doom.SourceCode.P_MapUtl.P_PathTraverse; +import doom.SourceCode.fixed_t; +import java.util.function.Predicate; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import p.AbstractLevelLoader; +import static p.MapUtils.InterceptVector; +import p.divline_t; +import p.intercept_t; +import p.mobj_t; +import rr.line_t; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import static utils.GenericCopy.malloc; +import utils.TraitFactory.ContextKey; + +public interface ActionsPathTraverse extends ActionsSectors { + + ContextKey KEY_TRAVERSE = ACTION_KEY_CHAIN.newKey(ActionsPathTraverse.class, Traverse::new); + + final class Traverse { + //////////////// PIT FUNCTION OBJECTS /////////////////// + + // + // PIT_AddLineIntercepts. + // Looks for lines in the given block + // that intercept the given trace + // to add to the intercepts list. + // + // A line is crossed if its endpoints + // are on opposite sides of the trace. + // Returns true if earlyout and a solid line hit. + // + divline_t addLineDivLine = new divline_t(); + + // + // PIT_AddThingIntercepts + // + // maybe make this a shared instance variable? + divline_t thingInterceptDivLine = new divline_t(); + + boolean earlyout; + + int intercept_p; + + // + // INTERCEPT ROUTINES + // + intercept_t[] intercepts = malloc(intercept_t::new, intercept_t[]::new, MAXINTERCEPTS); + + void ResizeIntercepts() { + intercepts = C2JUtils.resize(intercepts[0], intercepts, intercepts.length * 2); + } + } + + /** + * P_PathTraverse Traces a line from x1,y1 to x2,y2, calling the traverser function for each. Returns true if the + * traverser function returns true for all lines. + */ + @Override + @P_MapUtl.C(P_PathTraverse) + default boolean PathTraverse(int x1, int y1, int x2, int y2, int flags, Predicate trav) { + final AbstractLevelLoader ll = levelLoader(); + final Spawn sp = contextRequire(KEY_SPAWN); + final Traverse tr = contextRequire(KEY_TRAVERSE); + + // System.out.println("Pathtraverse "+x1+" , " +y1+" to "+x2 +" , " + // +y2); + final int xt1, yt1; + final int xt2, yt2; + final long _x1, _x2, _y1, _y2; + final int mapx1, mapy1; + final int xstep, ystep; + + int partial; + + int xintercept, yintercept; + + int mapx; + int mapy; + + int mapxstep; + int mapystep; + + int count; + + tr.earlyout = eval(flags & PT_EARLYOUT); + + sceneRenderer().increaseValidCount(1); + tr.intercept_p = 0; + + if (((x1 - ll.bmaporgx) & (MAPBLOCKSIZE - 1)) == 0) { + x1 += FRACUNIT; // don't side exactly on a line + } + if (((y1 - ll.bmaporgy) & (MAPBLOCKSIZE - 1)) == 0) { + y1 += FRACUNIT; // don't side exactly on a line + } + sp.trace.x = x1; + sp.trace.y = y1; + sp.trace.dx = x2 - x1; + sp.trace.dy = y2 - y1; + + // Code developed in common with entryway + // for prBoom+ + _x1 = (long) x1 - ll.bmaporgx; + _y1 = (long) y1 - ll.bmaporgy; + xt1 = (int) (_x1 >> MAPBLOCKSHIFT); + yt1 = (int) (_y1 >> MAPBLOCKSHIFT); + + mapx1 = (int) (_x1 >> MAPBTOFRAC); + mapy1 = (int) (_y1 >> MAPBTOFRAC); + + _x2 = (long) x2 - ll.bmaporgx; + _y2 = (long) y2 - ll.bmaporgy; + xt2 = (int) (_x2 >> MAPBLOCKSHIFT); + yt2 = (int) (_y2 >> MAPBLOCKSHIFT); + + x1 -= ll.bmaporgx; + y1 -= ll.bmaporgy; + x2 -= ll.bmaporgx; + y2 -= ll.bmaporgy; + + if (xt2 > xt1) { + mapxstep = 1; + partial = FRACUNIT - (mapx1 & (FRACUNIT - 1)); + ystep = FixedDiv(y2 - y1, Math.abs(x2 - x1)); + } else if (xt2 < xt1) { + mapxstep = -1; + partial = mapx1 & (FRACUNIT - 1); + ystep = FixedDiv(y2 - y1, Math.abs(x2 - x1)); + } else { + mapxstep = 0; + partial = FRACUNIT; + ystep = 256 * FRACUNIT; + } + + yintercept = mapy1 + FixedMul(partial, ystep); + + if (yt2 > yt1) { + mapystep = 1; + partial = FRACUNIT - (mapy1 & (FRACUNIT - 1)); + xstep = FixedDiv(x2 - x1, Math.abs(y2 - y1)); + } else if (yt2 < yt1) { + mapystep = -1; + partial = mapy1 & (FRACUNIT - 1); + xstep = FixedDiv(x2 - x1, Math.abs(y2 - y1)); + } else { + mapystep = 0; + partial = FRACUNIT; + xstep = 256 * FRACUNIT; + } + xintercept = mapx1 + FixedMul(partial, xstep); + + // Step through map blocks. + // Count is present to prevent a round off error + // from skipping the break. + mapx = xt1; + mapy = yt1; + + for (count = 0; count < 64; count++) { + if (eval(flags & PT_ADDLINES)) { + if (!this.BlockLinesIterator(mapx, mapy, this::AddLineIntercepts)) { + return false; // early out + } + } + + if (eval(flags & PT_ADDTHINGS)) { + if (!this.BlockThingsIterator(mapx, mapy, this::AddThingIntercepts)) { + return false; // early out + } + } + + if (mapx == xt2 + && mapy == yt2) { + break; + } + + boolean changeX = (yintercept >> FRACBITS) == mapy; + boolean changeY = (xintercept >> FRACBITS) == mapx; + if (changeX) { + yintercept += ystep; + mapx += mapxstep; + } else //[MAES]: this fixed sync issues. Lookup linuxdoom + if (changeY) { + xintercept += xstep; + mapy += mapystep; + } + + } + // go through the sorted list + //System.out.println("Some intercepts found"); + return TraverseIntercept(trav, FRACUNIT); + } // end method + + default boolean AddLineIntercepts(line_t ld) { + final Spawn sp = contextRequire(KEY_SPAWN); + final Traverse tr = contextRequire(KEY_TRAVERSE); + + boolean s1; + boolean s2; + @fixed_t + int frac; + + // avoid precision problems with two routines + if (sp.trace.dx > FRACUNIT * 16 || sp.trace.dy > FRACUNIT * 16 + || sp.trace.dx < -FRACUNIT * 16 || sp.trace.dy < -FRACUNIT * 16) { + s1 = sp.trace.PointOnDivlineSide(ld.v1x, ld.v1y); + s2 = sp.trace.PointOnDivlineSide(ld.v2x, ld.v2y); + //s1 = obs.trace.DivlineSide(ld.v1x, ld.v1.y); + //s2 = obs.trace.DivlineSide(ld.v2x, ld.v2y); + } else { + s1 = ld.PointOnLineSide(sp.trace.x, sp.trace.y); + s2 = ld.PointOnLineSide(sp.trace.x + sp.trace.dx, sp.trace.y + sp.trace.dy); + //s1 = new divline_t(ld).DivlineSide(obs.trace.x, obs.trace.y); + //s2 = new divline_t(ld).DivlineSide(obs.trace.x + obs.trace.dx, obs.trace.y + obs.trace.dy); + } + + if (s1 == s2) { + return true; // line isn't crossed + } + // hit the line + tr.addLineDivLine.MakeDivline(ld); + frac = InterceptVector(sp.trace, tr.addLineDivLine); + + if (frac < 0) { + return true; // behind source + } + // try to early out the check + if (tr.earlyout && frac < FRACUNIT && ld.backsector == null) { + return false; // stop checking + } + + // "create" a new intercept in the static intercept pool. + if (tr.intercept_p >= tr.intercepts.length) { + tr.ResizeIntercepts(); + } + + tr.intercepts[tr.intercept_p].frac = frac; + tr.intercepts[tr.intercept_p].isaline = true; + tr.intercepts[tr.intercept_p].line = ld; + tr.intercept_p++; + + return true; // continue + } + + ; + + default boolean AddThingIntercepts(mobj_t thing) { + final Spawn sp = contextRequire(KEY_SPAWN); + final Traverse tr = contextRequire(KEY_TRAVERSE); + + @fixed_t + int x1, y1, x2, y2; + boolean s1, s2; + boolean tracepositive; + @fixed_t + int frac; + + tracepositive = (sp.trace.dx ^ sp.trace.dy) > 0; + + // check a corner to corner crossection for hit + if (tracepositive) { + x1 = thing.x - thing.radius; + y1 = thing.y + thing.radius; + + x2 = thing.x + thing.radius; + y2 = thing.y - thing.radius; + } else { + x1 = thing.x - thing.radius; + y1 = thing.y - thing.radius; + + x2 = thing.x + thing.radius; + y2 = thing.y + thing.radius; + } + + s1 = sp.trace.PointOnDivlineSide(x1, y1); + s2 = sp.trace.PointOnDivlineSide(x2, y2); + + if (s1 == s2) { + return true; // line isn't crossed + } + + tr.thingInterceptDivLine.x = x1; + tr.thingInterceptDivLine.y = y1; + tr.thingInterceptDivLine.dx = x2 - x1; + tr.thingInterceptDivLine.dy = y2 - y1; + + frac = InterceptVector(sp.trace, tr.thingInterceptDivLine); + + if (frac < 0) { + return true; // behind source + } + + // "create" a new intercept in the static intercept pool. + if (tr.intercept_p >= tr.intercepts.length) { + tr.ResizeIntercepts(); + } + + tr.intercepts[tr.intercept_p].frac = frac; + tr.intercepts[tr.intercept_p].isaline = false; + tr.intercepts[tr.intercept_p].thing = thing; + tr.intercept_p++; + + return true; // keep going + } + + ; + + // + //P_TraverseIntercepts + //Returns true if the traverser function returns true + //for all lines. + // + default boolean TraverseIntercept(Predicate func, int maxfrac) { + final Traverse tr = contextRequire(KEY_TRAVERSE); + + int count; + @fixed_t + int dist; + intercept_t in = null; // shut up compiler warning + + count = tr.intercept_p; + + while (count-- > 0) { + dist = MAXINT; + for (int scan = 0; scan < tr.intercept_p; scan++) { + if (tr.intercepts[scan].frac < dist) { + dist = tr.intercepts[scan].frac; + in = tr.intercepts[scan]; + } + } + + if (dist > maxfrac) { + return true; // checked everything in range + } + /* // UNUSED + { + // don't check these yet, there may be others inserted + in = scan = intercepts; + for ( scan = intercepts ; scan maxfrac) + *in++ = *scan; + intercept_p = in; + return false; + } + */ + + if (!func.test(in)) { + return false; // don't bother going farther + } + in.frac = MAXINT; + } + + return true; // everything was traversed + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsPlats.java b/doom/src/p/Actions/ActionsPlats.java new file mode 100644 index 0000000..e479166 --- /dev/null +++ b/doom/src/p/Actions/ActionsPlats.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Limits.MAXPLATS; +import static data.Limits.PLATSPEED; +import static data.Limits.PLATWAIT; +import data.sounds; +import doom.thinker_t; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.Settings; +import static m.fixed_t.FRACUNIT; +import mochadoom.Engine; +import mochadoom.Loggers; +import p.AbstractLevelLoader; +import static p.ActiveStates.NOP; +import static p.ActiveStates.T_PlatRaise; +import p.plat_e; +import p.plat_t; +import p.plattype_e; +import rr.line_t; +import rr.sector_t; +import utils.C2JUtils; +import utils.TraitFactory.ContextKey; + +public interface ActionsPlats extends ActionsMoveEvents, ActionsUseEvents { + + ContextKey KEY_PLATS = ACTION_KEY_CHAIN.newKey(ActionsPlats.class, Plats::new); + + int FindSectorFromLineTag(line_t line, int secnum); + + void RemoveThinker(thinker_t activeplat); + + final class Plats { + + static final Logger LOGGER = Loggers.getLogger(ActionsPlats.class.getName()); + + // activeplats is just a placeholder. Plat objects aren't + // actually reused, so we don't need an initialized array. + // Same rule when resizing. + plat_t[] activeplats = new plat_t[MAXPLATS]; + } + + // + // Do Platforms + // "amount" is only used for SOME platforms. + // + @Override + default boolean DoPlat(line_t line, plattype_e type, int amount) { + final AbstractLevelLoader ll = levelLoader(); + + plat_t plat; + int secnum = -1; + boolean rtn = false; + sector_t sec; + + // Activate all plats that are in_stasis + switch (type) { + case perpetualRaise: + ActivateInStasis(line.tag); + break; + + default: + break; + } + + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + sec = ll.sectors[secnum]; + + if (sec.specialdata != null) { + continue; + } + + // Find lowest & highest floors around sector + rtn = true; + plat = new plat_t(); + + plat.type = type; + plat.sector = sec; + plat.sector.specialdata = plat; + plat.thinkerFunction = T_PlatRaise; + AddThinker(plat); + plat.crush = false; + plat.tag = line.tag; + + switch (type) { + case raiseToNearestAndChange: + plat.speed = PLATSPEED / 2; + sec.floorpic = ll.sides[line.sidenum[0]].sector.floorpic; + plat.high = sec.FindNextHighestFloor(sec.floorheight); + plat.wait = 0; + plat.status = plat_e.up; + // NO MORE DAMAGE, IF APPLICABLE + sec.special = 0; + + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_stnmov); + break; + + case raiseAndChange: + plat.speed = PLATSPEED / 2; + sec.floorpic = ll.sides[line.sidenum[0]].sector.floorpic; + plat.high = sec.floorheight + amount * FRACUNIT; + plat.wait = 0; + plat.status = plat_e.up; + + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_stnmov); + break; + + case downWaitUpStay: + plat.speed = PLATSPEED * 4; + plat.low = sec.FindLowestFloorSurrounding(); + + if (plat.low > sec.floorheight) { + plat.low = sec.floorheight; + } + + plat.high = sec.floorheight; + plat.wait = 35 * PLATWAIT; + plat.status = plat_e.down; + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_pstart); + break; + + case blazeDWUS: + plat.speed = PLATSPEED * 8; + plat.low = sec.FindLowestFloorSurrounding(); + + if (plat.low > sec.floorheight) { + plat.low = sec.floorheight; + } + + plat.high = sec.floorheight; + plat.wait = 35 * PLATWAIT; + plat.status = plat_e.down; + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_pstart); + break; + + case perpetualRaise: + plat.speed = PLATSPEED; + plat.low = sec.FindLowestFloorSurrounding(); + + if (plat.low > sec.floorheight) { + plat.low = sec.floorheight; + } + + plat.high = sec.FindHighestFloorSurrounding(); + + if (plat.high < sec.floorheight) { + plat.high = sec.floorheight; + } + + plat.wait = 35 * PLATWAIT; + // Guaranteed to be 0 or 1. + plat.status = plat_e.values()[P_Random() & 1]; + + StartSound(sec.soundorg, sounds.sfxenum_t.sfx_pstart); + break; + } + AddActivePlat(plat); + } + return rtn; + } + + default void ActivateInStasis(int tag) { + final Plats plats = contextRequire(KEY_PLATS); + + for (final plat_t activeplat : plats.activeplats) { + if (activeplat != null && activeplat.tag == tag && activeplat.status == plat_e.in_stasis) { + activeplat.status = activeplat.oldstatus; + activeplat.thinkerFunction = T_PlatRaise; + } + } + } + + @Override + default void StopPlat(line_t line) { + final Plats plats = contextRequire(KEY_PLATS); + + for (final plat_t activeplat : plats.activeplats) { + if (activeplat != null && activeplat.status != plat_e.in_stasis && activeplat.tag == line.tag) { + activeplat.oldstatus = (activeplat).status; + activeplat.status = plat_e.in_stasis; + activeplat.thinkerFunction = NOP; + } + } + } + + default void AddActivePlat(plat_t plat) { + final Plats plats = contextRequire(KEY_PLATS); + + for (int i = 0; i < plats.activeplats.length; i++) { + if (plats.activeplats[i] == null) { + plats.activeplats[i] = plat; + return; + } + } + + /** + * Added option to turn off the resize + * - Good Sign 2017/04/26 + */ + // Uhh... lemme guess. Needs to resize? + // Resize but leave extra items empty. + if (Engine.getConfig().equals(Settings.extend_plats_limit, Boolean.TRUE)) { + plats.activeplats = C2JUtils.resizeNoAutoInit(plats.activeplats, 2 * plats.activeplats.length); + AddActivePlat(plat); + } else { + Plats.LOGGER.log(Level.SEVERE, "P_AddActivePlat: no more plats!"); + System.exit(1); + } + } + + default void RemoveActivePlat(plat_t plat) { + final Plats plats = contextRequire(KEY_PLATS); + + for (int i = 0; i < plats.activeplats.length; i++) { + if (plat == plats.activeplats[i]) { + (plats.activeplats[i]).sector.specialdata = null; + RemoveThinker(plats.activeplats[i]); + plats.activeplats[i] = null; + + return; + } + } + + Plats.LOGGER.log(Level.SEVERE, "P_RemoveActivePlat: can't find plat!"); + System.exit(1); + } + + default void ClearPlatsBeforeLoading() { + final Plats plats = contextRequire(KEY_PLATS); + + for (int i = 0; i < plats.activeplats.length; i++) { + plats.activeplats[i] = null; + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsSectors.java b/doom/src/p/Actions/ActionsSectors.java new file mode 100644 index 0000000..0302533 --- /dev/null +++ b/doom/src/p/Actions/ActionsSectors.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.ITEMQUESIZE; +import static data.Defines.MELEERANGE; +import data.mapthing_t; +import data.mobjtype_t; +import defines.statenum_t; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PIT_ChangeSector; +import doom.SourceCode.fixed_t; +import java.util.logging.Logger; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import mochadoom.Loggers; +import p.AbstractLevelLoader; +import p.ActiveStates; +import p.divline_t; +import p.floor_e; +import p.floormove_t; +import p.mobj_t; +import static p.mobj_t.MF_DROPPED; +import static p.mobj_t.MF_SHOOTABLE; +import static p.mobj_t.MF_SOLID; +import p.result_e; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import rr.sector_t; +import rr.side_t; +import static utils.C2JUtils.eval; +import utils.TraitFactory.ContextKey; + +public interface ActionsSectors extends ActionsLights, ActionsFloors, ActionsDoors, ActionsCeilings, ActionsSlideDoors { + + ContextKey KEY_RESP_QUEUE = ACTION_KEY_CHAIN.newKey(ActionsSectors.class, RespawnQueue::new); + ContextKey KEY_SPAWN = ACTION_KEY_CHAIN.newKey(ActionsSectors.class, Spawn::new); + ContextKey KEY_CRUSHES = ACTION_KEY_CHAIN.newKey(ActionsSectors.class, Crushes::new); + + void RemoveMobj(mobj_t thing); + + void DamageMobj(mobj_t thing, mobj_t tmthing, mobj_t tmthing0, int damage); + + mobj_t SpawnMobj(@fixed_t int x, @fixed_t int y, @fixed_t int z, mobjtype_t type); + + final class Crushes { + + boolean crushchange; + boolean nofit; + } + + final class RespawnQueue { + + // + // P_RemoveMobj + // + mapthing_t[] itemrespawnque = new mapthing_t[ITEMQUESIZE]; + int[] itemrespawntime = new int[ITEMQUESIZE]; + int iquehead; + int iquetail; + } + + final class Spawn { + + final static Logger LOGGER = Loggers.getLogger(ActionsSectors.class.getName()); + + /** + * who got hit (or NULL) + */ + public mobj_t linetarget; + + @fixed_t + public int attackrange; + + public mobj_t shootthing; + // Height if not aiming up or down + // ???: use slope for monsters? + @fixed_t + public int shootz; + + public int la_damage; + + @fixed_t + public int aimslope; + + public divline_t trace = new divline_t(); + + public int topslope, bottomslope; // slopes to top and bottom of target + + // + // P_BulletSlope + // Sets a slope so a near miss is at aproximately + // the height of the intended target + // + public int bulletslope; + + boolean isMeleeRange() { + return attackrange == MELEERANGE; + } + } + + // + // P_ChangeSector + // + // + // SECTOR HEIGHT CHANGING + // After modifying a sectors floor or ceiling height, + // call this routine to adjust the positions + // of all things that touch the sector. + // + // If anything doesn't fit anymore, true will be returned. + // If crunch is true, they will take damage + // as they are being crushed. + // If Crunch is false, you should set the sector height back + // the way it was and call P_ChangeSector again + // to undo the changes. + // + default boolean ChangeSector(sector_t sector, boolean crunch) { + final Crushes cr = contextRequire(KEY_CRUSHES); + int x; + int y; + + cr.nofit = false; + cr.crushchange = crunch; + + // re-check heights for all things near the moving sector + for (x = sector.blockbox[BOXLEFT]; x <= sector.blockbox[BOXRIGHT]; x++) { + for (y = sector.blockbox[BOXBOTTOM]; y <= sector.blockbox[BOXTOP]; y++) { + this.BlockThingsIterator(x, y, this::ChangeSector); + } + } + + return cr.nofit; + } + + /** + * PIT_ChangeSector + */ + @P_Map.C(PIT_ChangeSector) + default boolean ChangeSector(mobj_t thing) { + final Crushes cr = contextRequire(KEY_CRUSHES); + mobj_t mo; + + if (ThingHeightClip(thing)) { + // keep checking + return true; + } + + // crunch bodies to giblets + if (thing.health <= 0) { + thing.SetMobjState(statenum_t.S_GIBS); + + thing.flags &= ~MF_SOLID; + thing.height = 0; + thing.radius = 0; + + // keep checking + return true; + } + + // crunch dropped items + if (eval(thing.flags & MF_DROPPED)) { + RemoveMobj(thing); + + // keep checking + return true; + } + + if (!eval(thing.flags & MF_SHOOTABLE)) { + // assume it is bloody gibs or something + return true; + } + + cr.nofit = true; + + if (cr.crushchange && !eval(LevelTime() & 3)) { + DamageMobj(thing, null, null, 10); + + // spray blood in a random direction + mo = SpawnMobj(thing.x, thing.y, thing.z + thing.height / 2, mobjtype_t.MT_BLOOD); + + mo.momx = (P_Random() - P_Random()) << 12; + mo.momy = (P_Random() - P_Random()) << 12; + } + + // keep checking (crush other things) + return true; + } + + ; + + /** + * Move a plane (floor or ceiling) and check for crushing + * + * @param sector + * @param speed fixed + * @param dest fixed + * @param crush + * @param floorOrCeiling + * @param direction + */ + @Override + default result_e MovePlane(sector_t sector, int speed, int dest, boolean crush, int floorOrCeiling, int direction) { + boolean flag; + @fixed_t + int lastpos; + + switch (floorOrCeiling) { + case 0: + // FLOOR + switch (direction) { + case -1: + // DOWN + if (sector.floorheight - speed < dest) { + lastpos = sector.floorheight; + sector.floorheight = dest; + flag = ChangeSector(sector, crush); + if (flag == true) { + sector.floorheight = lastpos; + ChangeSector(sector, crush); + //return crushed; + } + return result_e.pastdest; + } else { + lastpos = sector.floorheight; + sector.floorheight -= speed; + flag = ChangeSector(sector, crush); + if (flag == true) { + sector.floorheight = lastpos; + ChangeSector(sector, crush); + return result_e.crushed; + } + } + break; + + case 1: + // UP + if (sector.floorheight + speed > dest) { + lastpos = sector.floorheight; + sector.floorheight = dest; + flag = ChangeSector(sector, crush); + if (flag == true) { + sector.floorheight = lastpos; + ChangeSector(sector, crush); + //return crushed; + } + return result_e.pastdest; + } else { + // COULD GET CRUSHED + lastpos = sector.floorheight; + sector.floorheight += speed; + flag = ChangeSector(sector, crush); + if (flag == true) { + if (crush == true) { + return result_e.crushed; + } + sector.floorheight = lastpos; + ChangeSector(sector, crush); + return result_e.crushed; + } + } + break; + } + break; + + case 1: + // CEILING + switch (direction) { + case -1: + // DOWN + if (sector.ceilingheight - speed < dest) { + lastpos = sector.ceilingheight; + sector.ceilingheight = dest; + flag = ChangeSector(sector, crush); + + if (flag == true) { + sector.ceilingheight = lastpos; + ChangeSector(sector, crush); + //return crushed; + } + return result_e.pastdest; + } else { + // COULD GET CRUSHED + lastpos = sector.ceilingheight; + sector.ceilingheight -= speed; + flag = ChangeSector(sector, crush); + + if (flag == true) { + if (crush == true) { + return result_e.crushed; + } + sector.ceilingheight = lastpos; + ChangeSector(sector, crush); + return result_e.crushed; + } + } + break; + + case 1: + // UP + if (sector.ceilingheight + speed > dest) { + lastpos = sector.ceilingheight; + sector.ceilingheight = dest; + flag = ChangeSector(sector, crush); + if (flag == true) { + sector.ceilingheight = lastpos; + ChangeSector(sector, crush); + //return crushed; + } + return result_e.pastdest; + } else { + lastpos = sector.ceilingheight; + sector.ceilingheight += speed; + flag = ChangeSector(sector, crush); + // UNUSED + /* + if (flag == true) + { + sector.ceilingheight = lastpos; + P_ChangeSector(sector,crush); + return crushed; + } + */ + } + break; + } + break; + + } + return result_e.ok; + } + + /** + * Special Stuff that can not be categorized + * + * (I'm sure it has something to do with John Romero's obsession with fucking stuff and making them his bitches). + * + * @param line + * + */ + @Override + default boolean DoDonut(line_t line) { + sector_t s1; + sector_t s2; + sector_t s3; + int secnum; + boolean rtn; + int i; + floormove_t floor; + + secnum = -1; + rtn = false; + while ((secnum = FindSectorFromLineTag(line, secnum)) >= 0) { + s1 = levelLoader().sectors[secnum]; + + // ALREADY MOVING? IF SO, KEEP GOING... + if (s1.specialdata != null) { + continue; + } + + rtn = true; + s2 = s1.lines[0].getNextSector(s1); + for (i = 0; i < s2.linecount; i++) { + if ((!eval(s2.lines[i].flags & ML_TWOSIDED)) || (s2.lines[i].backsector == s1)) { + continue; + } + s3 = s2.lines[i].backsector; + + // Spawn rising slime + floor = new floormove_t(); + s2.specialdata = floor; + floor.thinkerFunction = ActiveStates.T_MoveFloor; + AddThinker(floor); + floor.type = floor_e.donutRaise; + floor.crush = false; + floor.direction = 1; + floor.sector = s2; + floor.speed = FLOORSPEED / 2; + floor.texture = s3.floorpic; + floor.newspecial = 0; + floor.floordestheight = s3.floorheight; + + // Spawn lowering donut-hole + floor = new floormove_t(); + s1.specialdata = floor; + floor.thinkerFunction = ActiveStates.T_MoveFloor; + AddThinker(floor); + floor.type = floor_e.lowerFloor; + floor.crush = false; + floor.direction = -1; + floor.sector = s1; + floor.speed = FLOORSPEED / 2; + floor.floordestheight = s3.floorheight; + break; + } + } + return rtn; + } + + /** + * RETURN NEXT SECTOR # THAT LINE TAG REFERS TO + */ + @Override + default int FindSectorFromLineTag(line_t line, int start) { + final AbstractLevelLoader ll = levelLoader(); + + for (int i = start + 1; i < ll.numsectors; i++) { + if (ll.sectors[i].tag == line.tag) { + return i; + } + } + + return -1; + } + + // + // UTILITIES + // + // + // getSide() + // Will return a side_t* + // given the number of the current sector, + // the line number, and the side (0/1) that you want. + // + @Override + default side_t getSide(int currentSector, int line, int side) { + final AbstractLevelLoader ll = levelLoader(); + return ll.sides[(ll.sectors[currentSector].lines[line]).sidenum[side]]; + } + + /** + * getSector() + * Will return a sector_t + * given the number of the current sector, + * the line number and the side (0/1) that you want. + */ + @Override + default sector_t getSector(int currentSector, int line, int side) { + final AbstractLevelLoader ll = levelLoader(); + return ll.sides[(ll.sectors[currentSector].lines[line]).sidenum[side]].sector; + } + + /** + * twoSided() + * Given the sector number and the line number, + * it will tell you whether the line is two-sided or not. + */ + @Override + default boolean twoSided(int sector, int line) { + return eval((levelLoader().sectors[sector].lines[line]).flags & ML_TWOSIDED); + } + + default void ClearRespawnQueue() { + // clear special respawning que + final RespawnQueue rq = contextRequire(KEY_RESP_QUEUE); + rq.iquehead = rq.iquetail = 0; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsShootEvents.java b/doom/src/p/Actions/ActionsShootEvents.java new file mode 100644 index 0000000..f485dd7 --- /dev/null +++ b/doom/src/p/Actions/ActionsShootEvents.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import p.UnifiedGameMap.Switches; +import p.floor_e; +import p.intercept_t; +import p.mobj_t; +import p.plattype_e; +import p.vldoor_e; +import rr.line_t; + +public interface ActionsShootEvents extends ActionsSpawns { + + /** + * P_ShootSpecialLine - IMPACT SPECIALS Called when a thing shoots a special line. + */ + default void ShootSpecialLine(mobj_t thing, line_t line) { + final Switches sw = getSwitches(); + boolean ok; + + // Impacts that other things can activate. + if (thing.player == null) { + ok = false; + switch (line.special) { + case 46: + // OPEN DOOR IMPACT + ok = true; + break; + } + if (!ok) { + return; + } + } + + switch (line.special) { + case 24: + // RAISE FLOOR + DoFloor(line, floor_e.raiseFloor); + sw.ChangeSwitchTexture(line, false); + break; + + case 46: + // OPEN DOOR + DoDoor(line, vldoor_e.open); + sw.ChangeSwitchTexture(line, true); + break; + + case 47: + // RAISE FLOOR NEAR AND CHANGE + DoPlat(line, plattype_e.raiseToNearestAndChange, 0); + sw.ChangeSwitchTexture(line, false); + break; + } + } + + //_D_: NOTE: this function was added, because replacing a goto by a boolean flag caused a bug if shooting a single sided line + default boolean gotoHitLine(intercept_t in, line_t li) { + final Spawn targ = contextRequire(KEY_SPAWN); + int x, y, z, frac; + + // position a bit closer + frac = in.frac - FixedDiv(4 * FRACUNIT, targ.attackrange); + x = targ.trace.x + FixedMul(targ.trace.dx, frac); + y = targ.trace.y + FixedMul(targ.trace.dy, frac); + z = targ.shootz + FixedMul(targ.aimslope, FixedMul(frac, targ.attackrange)); + + if (li.frontsector.ceilingpic == DOOM().textureManager.getSkyFlatNum()) { + // don't shoot the sky! + if (z > li.frontsector.ceilingheight) { + return false; + } + + // it's a sky hack wall + if (li.backsector != null && li.backsector.ceilingpic == DOOM().textureManager.getSkyFlatNum()) { + return false; + } + } + + // Spawn bullet puffs. + this.SpawnPuff(x, y, z); + + // don't go any farther + return false; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsSight.java b/doom/src/p/Actions/ActionsSight.java new file mode 100644 index 0000000..59a0218 --- /dev/null +++ b/doom/src/p/Actions/ActionsSight.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.NF_SUBSECTOR; +import static data.Defines.RANGECHECK; +import doom.SourceCode.fixed_t; +import static m.fixed_t.FixedDiv; +import p.AbstractLevelLoader; +import p.MapUtils; +import p.divline_t; +import p.mobj_t; +import rr.SceneRenderer; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import rr.node_t; +import rr.sector_t; +import rr.subsector_t; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.flags; +import utils.TraitFactory.ContextKey; + +public interface ActionsSight extends ActionsSectors { + + ContextKey KEY_SIGHT = ACTION_KEY_CHAIN.newKey(ActionsSight.class, Sight::new); + + class Sight { + + int sightzstart; // eye z of looker + divline_t strace = new divline_t(); + ; // from t1 to t2 + int t2x, t2y; + int[] sightcounts = new int[2]; + } + + /** + * P_CheckSight Returns true if a straight line between t1 and t2 is + * unobstructed. Uses REJECT. + */ + default boolean CheckSight(mobj_t t1, mobj_t t2) { + final AbstractLevelLoader ll = levelLoader(); + final Sight sight = contextRequire(KEY_SIGHT); + final Spawn spawn = contextRequire(KEY_SPAWN); + + int s1; + int s2; + int pnum; + int bytenum; + int bitnum; + + // First check for trivial rejection. + // Determine subsector entries in REJECT table. + s1 = t1.subsector.sector.id; // (t1.subsector.sector - sectors); + s2 = t2.subsector.sector.id;// - sectors); + pnum = s1 * ll.numsectors + s2; + bytenum = pnum >> 3; + bitnum = 1 << (pnum & 7); + + // Check in REJECT table. + if (eval(ll.rejectmatrix[bytenum] & bitnum)) { + sight.sightcounts[0]++; + + // can't possibly be connected + return false; + } + + // An unobstructed LOS is possible. + // Now look from eyes of t1 to any part of t2. + sight.sightcounts[1]++; + + sceneRenderer().increaseValidCount(1); + + sight.sightzstart = t1.z + t1.height - (t1.height >> 2); + spawn.topslope = (t2.z + t2.height) - sight.sightzstart; + spawn.bottomslope = (t2.z) - sight.sightzstart; + + sight.strace.x = t1.x; + sight.strace.y = t1.y; + sight.t2x = t2.x; + sight.t2y = t2.y; + sight.strace.dx = t2.x - t1.x; + sight.strace.dy = t2.y - t1.y; + + // the head node is the last node output + return CrossBSPNode(ll.numnodes - 1); + } + + /** + * P_CrossSubsector Returns true if strace crosses the given subsector + * successfully. + */ + default boolean CrossSubsector(int num) { + final SceneRenderer sr = sceneRenderer(); + final AbstractLevelLoader ll = levelLoader(); + final Spawn spawn = contextRequire(KEY_SPAWN); + final Sight sight = contextRequire(KEY_SIGHT); + + int seg; // pointer inside segs + line_t line; + int s1; + int s2; + int count; + subsector_t sub; + sector_t front; + sector_t back; + @fixed_t + int opentop; + int openbottom; + divline_t divl = new divline_t(); + //vertex_t v1; + //vertex_t v2; + @fixed_t + int frac; + int slope; + + if (RANGECHECK) { + if (num >= ll.numsubsectors) { + doomSystem().Error("P_CrossSubsector: ss %d with numss = %d", num, ll.numsubsectors); + } + } + + sub = ll.subsectors[num]; + + // check lines + count = sub.numlines; + seg = sub.firstline;// LL.segs[sub.firstline]; + + for (; count > 0; seg++, count--) { + line = ll.segs[seg].linedef; + + // allready checked other side? + if (line.validcount == sr.getValidCount()) { + continue; + } + + line.validcount = sr.getValidCount(); + + //v1 = line.v1; + //v2 = line.v2; + s1 = sight.strace.DivlineSide(line.v1x, line.v1y); + s2 = sight.strace.DivlineSide(line.v2x, line.v2y); + + // line isn't crossed? + if (s1 == s2) { + continue; + } + + divl.x = line.v1x; + divl.y = line.v1y; + divl.dx = line.v2x - line.v1x; + divl.dy = line.v2y - line.v1y; + s1 = divl.DivlineSide(sight.strace.x, sight.strace.y); + s2 = divl.DivlineSide(sight.t2x, sight.t2y); + + // line isn't crossed? + if (s1 == s2) { + continue; + } + + // stop because it is not two sided anyway + // might do this after updating validcount? + if (!flags(line.flags, ML_TWOSIDED)) { + return false; + } + + // crosses a two sided line + front = ll.segs[seg].frontsector; + back = ll.segs[seg].backsector; + + // no wall to block sight with? + if (front.floorheight == back.floorheight + && front.ceilingheight == back.ceilingheight) { + continue; + } + + // possible occluder + // because of ceiling height differences + if (front.ceilingheight < back.ceilingheight) { + opentop = front.ceilingheight; + } else { + opentop = back.ceilingheight; + } + + // because of ceiling height differences + if (front.floorheight > back.floorheight) { + openbottom = front.floorheight; + } else { + openbottom = back.floorheight; + } + + // quick test for totally closed doors + if (openbottom >= opentop) { + return false; // stop + } + + frac = MapUtils.P_InterceptVector(sight.strace, divl); + + if (front.floorheight != back.floorheight) { + slope = FixedDiv(openbottom - sight.sightzstart, frac); + if (slope > spawn.bottomslope) { + spawn.bottomslope = slope; + } + } + + if (front.ceilingheight != back.ceilingheight) { + slope = FixedDiv(opentop - sight.sightzstart, frac); + if (slope < spawn.topslope) { + spawn.topslope = slope; + } + } + + if (spawn.topslope <= spawn.bottomslope) { + return false; // stop + } + } + // passed the subsector ok + return true; + } + + /** + * P_CrossBSPNode Returns true if strace crosses the given node + * successfully. + */ + default boolean CrossBSPNode(int bspnum) { + final AbstractLevelLoader ll = levelLoader(); + final Sight sight = contextRequire(KEY_SIGHT); + + node_t bsp; + int side; + + if (eval(bspnum & NF_SUBSECTOR)) { + if (bspnum == -1) { + return CrossSubsector(0); + } else { + return CrossSubsector(bspnum & (~NF_SUBSECTOR)); + } + } + + bsp = ll.nodes[bspnum]; + + // decide which side the start point is on + side = bsp.DivlineSide(sight.strace.x, sight.strace.y); + if (side == 2) { + side = 0; // an "on" should cross both sides + } + + // cross the starting side + if (!CrossBSPNode(bsp.children[side])) { + return false; + } + + // the partition plane is crossed here + if (side == bsp.DivlineSide(sight.t2x, sight.t2y)) { + // the line doesn't touch the other side + return true; + } + + // cross the ending side + return CrossBSPNode(bsp.children[side ^ 1]); + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsSlideDoors.java b/doom/src/p/Actions/ActionsSlideDoors.java new file mode 100644 index 0000000..0e3665c --- /dev/null +++ b/doom/src/p/Actions/ActionsSlideDoors.java @@ -0,0 +1,234 @@ +package p.Actions; + +import doom.thinker_t; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.AbstractLevelLoader; +import static p.ActiveStates.T_SlidingDoor; +import p.mobj_t; +import p.sd_e; +import p.sdt_e; +import p.slidedoor_t; +import p.slideframe_t; +import p.slidename_t; +import rr.TextureManager; +import rr.line_t; +import static rr.line_t.ML_BLOCKING; +import rr.sector_t; +import static utils.GenericCopy.malloc; +import utils.TraitFactory.ContextKey; + +public interface ActionsSlideDoors extends ActionTrait { + + static final Logger LOGGER = Loggers.getLogger(ActionsSlideDoors.class.getName()); + + ContextKey KEY_SLIDEDOORS = ACTION_KEY_CHAIN.newKey(ActionsSlideDoors.class, SlideDoors::new); + + void RemoveThinker(thinker_t t); + + // UNUSED + // Separate into p_slidoor.c? + // ABANDONED TO THE MISTS OF TIME!!! + // + // EV_SlidingDoor : slide a door horizontally + // (animate midtexture, then set noblocking line) + // + int MAXSLIDEDOORS = 5; + // how many frames of animation + int SNUMFRAMES = 4; + + int SDOORWAIT = 35 * 3; + int SWAITTICS = 4; + + slidename_t[] slideFrameNames = { + new slidename_t( + "GDOORF1", "GDOORF2", "GDOORF3", "GDOORF4", // front + "GDOORB1", "GDOORB2", "GDOORB3", "GDOORB4" // back + ), + new slidename_t(), new slidename_t(), new slidename_t(), new slidename_t() + }; + + final class SlideDoors { + + slideframe_t[] slideFrames = malloc(slideframe_t::new, slideframe_t[]::new, MAXSLIDEDOORS); + } + + default void SlidingDoor(slidedoor_t door) { + final AbstractLevelLoader ll = levelLoader(); + final SlideDoors sd = contextRequire(KEY_SLIDEDOORS); + switch (door.status) { + case sd_opening: + if (door.timer-- == 0) { + if (++door.frame == ActionsSlideDoors.SNUMFRAMES) { + // IF DOOR IS DONE OPENING... + ll.sides[door.line.sidenum[0]].midtexture = 0; + ll.sides[door.line.sidenum[1]].midtexture = 0; + door.line.flags &= ML_BLOCKING ^ 0xff; + + if (door.type == sdt_e.sdt_openOnly) { + door.frontsector.specialdata = null; + RemoveThinker(door); + break; + } + + door.timer = ActionsSlideDoors.SDOORWAIT; + door.status = sd_e.sd_waiting; + } else { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door.timer = ActionsSlideDoors.SWAITTICS; + + ll.sides[door.line.sidenum[0]].midtexture = (short) sd.slideFrames[door.whichDoorIndex].frontFrames[door.frame]; + ll.sides[door.line.sidenum[1]].midtexture = (short) sd.slideFrames[door.whichDoorIndex].backFrames[door.frame]; + } + } + break; + + case sd_waiting: + // IF DOOR IS DONE WAITING... + if (door.timer-- == 0) { + // CAN DOOR CLOSE? + if (door.frontsector.thinglist != null + || door.backsector.thinglist != null) { + door.timer = ActionsSlideDoors.SDOORWAIT; + break; + } + + // door.frame = SNUMFRAMES-1; + door.status = sd_e.sd_closing; + door.timer = ActionsSlideDoors.SWAITTICS; + } + break; + + case sd_closing: + if (door.timer-- == 0) { + if (--door.frame < 0) { + // IF DOOR IS DONE CLOSING... + door.line.flags |= ML_BLOCKING; + door.frontsector.specialdata = null; + RemoveThinker(door); + break; + } else { + // IF DOOR NEEDS TO ANIMATE TO NEXT FRAME... + door.timer = ActionsSlideDoors.SWAITTICS; + + ll.sides[door.line.sidenum[0]].midtexture = (short) sd.slideFrames[door.whichDoorIndex].frontFrames[door.frame]; + ll.sides[door.line.sidenum[1]].midtexture = (short) sd.slideFrames[door.whichDoorIndex].backFrames[door.frame]; + } + } + break; + } + } + + default void P_InitSlidingDoorFrames() { + final TextureManager tm = DOOM().textureManager; + final SlideDoors sd = contextRequire(KEY_SLIDEDOORS); + + int i; + int f1; + int f2; + int f3; + int f4; + + // DOOM II ONLY... + if (!DOOM().isCommercial()) { + return; + } + + for (i = 0; i < MAXSLIDEDOORS; i++) { + if (slideFrameNames[i].frontFrame1 == null) { + break; + } + + f1 = tm.TextureNumForName(slideFrameNames[i].frontFrame1); + f2 = tm.TextureNumForName(slideFrameNames[i].frontFrame2); + f3 = tm.TextureNumForName(slideFrameNames[i].frontFrame3); + f4 = tm.TextureNumForName(slideFrameNames[i].frontFrame4); + + sd.slideFrames[i].frontFrames[0] = f1; + sd.slideFrames[i].frontFrames[1] = f2; + sd.slideFrames[i].frontFrames[2] = f3; + sd.slideFrames[i].frontFrames[3] = f4; + + f1 = tm.TextureNumForName(slideFrameNames[i].backFrame1); + f2 = tm.TextureNumForName(slideFrameNames[i].backFrame2); + f3 = tm.TextureNumForName(slideFrameNames[i].backFrame3); + f4 = tm.TextureNumForName(slideFrameNames[i].backFrame4); + + sd.slideFrames[i].backFrames[0] = f1; + sd.slideFrames[i].backFrames[1] = f2; + sd.slideFrames[i].backFrames[2] = f3; + sd.slideFrames[i].backFrames[3] = f4; + } + } + + // + // Return index into "slideFrames" array + // for which door type to use + // + default int P_FindSlidingDoorType(line_t line) { + final AbstractLevelLoader ll = levelLoader(); + final SlideDoors sd = contextRequire(KEY_SLIDEDOORS); + + for (int i = 0; i < MAXSLIDEDOORS; i++) { + int val = ll.sides[line.sidenum[0]].midtexture; + if (val == sd.slideFrames[i].frontFrames[0]) { + return i; + } + } + + return -1; + } + + default void EV_SlidingDoor(line_t line, mobj_t thing) { + sector_t sec; + slidedoor_t door; + + // DOOM II ONLY... + if (!DOOM().isCommercial()) { + return; + } + + LOGGER.log(Level.WARNING, "EV_SlidingDoor"); + + // Make sure door isn't already being animated + sec = line.frontsector; + door = null; + if (sec.specialdata != null) { + if (thing.player == null) { + return; + } + + door = (slidedoor_t) sec.specialdata; + if (door.type == sdt_e.sdt_openAndClose) { + if (door.status == sd_e.sd_waiting) { + door.status = sd_e.sd_closing; + } + } else { + return; + } + } + + // Init sliding door vars + if (door == null) { + door = new slidedoor_t(); + AddThinker(door); + sec.specialdata = door; + + door.type = sdt_e.sdt_openAndClose; + door.status = sd_e.sd_opening; + door.whichDoorIndex = P_FindSlidingDoorType(line); + + if (door.whichDoorIndex < 0) { + doomSystem().Error("EV_SlidingDoor: Can't use texture for sliding door!"); + } + + door.frontsector = sec; + door.backsector = line.backsector; + door.thinkerFunction = T_SlidingDoor; + door.timer = SWAITTICS; + door.frame = 0; + door.line = line; + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsSpawns.java b/doom/src/p/Actions/ActionsSpawns.java new file mode 100644 index 0000000..46a5a6d --- /dev/null +++ b/doom/src/p/Actions/ActionsSpawns.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.MTF_AMBUSH; +import static data.Defines.NUMCARDS; +import static data.Defines.ONCEILINGZ; +import static data.Defines.ONFLOORZ; +import static data.Defines.PST_LIVE; +import static data.Defines.PST_REBORN; +import static data.Defines.VIEWHEIGHT; +import static data.Limits.MAXPLAYERS; +import static data.Limits.NUMMOBJTYPES; +import static data.Tables.ANG45; +import static data.info.mobjinfo; +import static data.info.states; +import data.mapthing_t; +import data.mobjinfo_t; +import data.mobjtype_t; +import data.sounds; +import data.state_t; +import defines.skill_t; +import defines.statenum_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.P_Mobj; +import static doom.SourceCode.P_Mobj.P_SpawnMobj; +import static doom.SourceCode.P_Mobj.P_SpawnPlayer; +import doom.SourceCode.fixed_t; +import doom.player_t; +import java.util.logging.Level; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import p.ActiveStates; +import p.mobj_t; +import static p.mobj_t.MF_AMBUSH; +import static p.mobj_t.MF_COUNTITEM; +import static p.mobj_t.MF_COUNTKILL; +import static p.mobj_t.MF_NOTDMATCH; +import static p.mobj_t.MF_SPAWNCEILING; +import static p.mobj_t.MF_TRANSSHIFT; +import rr.subsector_t; +import static utils.C2JUtils.eval; +import v.graphics.Palettes; + +public interface ActionsSpawns extends ActionsSectors { + + /** + * P_NightmareRespawn + */ + default void NightmareRespawn(mobj_t mobj) { + int x, y, z; // fixed + subsector_t ss; + mobj_t mo; + mapthing_t mthing; + + x = mobj.spawnpoint.x << FRACBITS; + y = mobj.spawnpoint.y << FRACBITS; + + // somthing is occupying it's position? + if (!CheckPosition(mobj, x, y)) { + return; // no respwan + } + // spawn a teleport fog at old spot + // because of removal of the body? + mo = SpawnMobj(mobj.x, mobj.y, mobj.subsector.sector.floorheight, mobjtype_t.MT_TFOG); + + // initiate teleport sound + StartSound(mo, sounds.sfxenum_t.sfx_telept); + + // spawn a teleport fog at the new spot + ss = levelLoader().PointInSubsector(x, y); + + mo = SpawnMobj(x, y, ss.sector.floorheight, mobjtype_t.MT_TFOG); + + StartSound(mo, sounds.sfxenum_t.sfx_telept); + + // spawn the new monster + mthing = mobj.spawnpoint; + + // spawn it + if (eval(mobj.info.flags & MF_SPAWNCEILING)) { + z = ONCEILINGZ; + } else { + z = ONFLOORZ; + } + + // inherit attributes from deceased one + mo = SpawnMobj(x, y, z, mobj.type); + mo.spawnpoint = mobj.spawnpoint; + mo.angle = ANG45 * (mthing.angle / 45); + + if (eval(mthing.options & MTF_AMBUSH)) { + mo.flags |= MF_AMBUSH; + } + + mo.reactiontime = 18; + + // remove the old monster, + RemoveMobj(mobj); + } + + /** + * P_SpawnMobj + * + * @param x fixed + * @param y fixed + * @param z fixed + * @param type + * @return + */ + @Override + @SourceCode.Exact + @P_Mobj.C(P_SpawnMobj) + default mobj_t SpawnMobj(@fixed_t int x, @fixed_t int y, @fixed_t int z, mobjtype_t type) { + mobj_t mobj; + state_t st; + mobjinfo_t info; + + Z_Malloc: + { + mobj = createMobj(); + } + info = mobjinfo[type.ordinal()]; + + mobj.type = type; + mobj.info = info; + mobj.x = x; + mobj.y = y; + mobj.radius = info.radius; + mobj.height = info.height; + mobj.flags = info.flags; + mobj.health = info.spawnhealth; + + if (getGameSkill() != skill_t.sk_nightmare) { + mobj.reactiontime = info.reactiontime; + } + + P_Random: + { + mobj.lastlook = P_Random() % MAXPLAYERS; + } + // do not set the state with P_SetMobjState, + // because action routines can not be called yet + st = states[info.spawnstate.ordinal()]; + + mobj.mobj_state = st; + mobj.mobj_tics = st.tics; + mobj.mobj_sprite = st.sprite; + mobj.mobj_frame = st.frame; + + // set subsector and/or block links + P_SetThingPosition: + { + SetThingPosition(mobj); + } + + mobj.floorz = mobj.subsector.sector.floorheight; + mobj.ceilingz = mobj.subsector.sector.ceilingheight; + + if (z == ONFLOORZ) { + mobj.z = mobj.floorz; + } else if (z == ONCEILINGZ) { + mobj.z = mobj.ceilingz - mobj.info.height; + } else { + mobj.z = z; + } + + mobj.thinkerFunction = ActiveStates.P_MobjThinker; + P_AddThinker: + { + AddThinker(mobj); + } + + return mobj; + } + + /** + * P_SpawnPlayer + * Called when a player is spawned on the level. + * Most of the player structure stays unchanged + * between levels. + */ + @SourceCode.Exact + @P_Mobj.C(P_SpawnPlayer) + default void SpawnPlayer(mapthing_t mthing) { + player_t p; + @fixed_t + int x, y, z; + mobj_t mobj; + + // not playing? + if (!PlayerInGame(mthing.type - 1)) { + return; + } + + p = getPlayer(mthing.type - 1); + + if (p.playerstate == PST_REBORN) { + G_PlayerReborn: + { + p.PlayerReborn(); + } + } + //DM.PlayerReborn (mthing.type-1); + + x = mthing.x << FRACBITS; + y = mthing.y << FRACBITS; + z = ONFLOORZ; + P_SpawnMobj: + { + mobj = this.SpawnMobj(x, y, z, mobjtype_t.MT_PLAYER); + } + + // set color translations for player sprites + if (mthing.type > 1) { + mobj.flags |= (mthing.type - 1) << MF_TRANSSHIFT; + } + + mobj.angle = ANG45 * (mthing.angle / 45); + mobj.player = p; + mobj.health = p.health[0]; + + p.mo = mobj; + p.playerstate = PST_LIVE; + p.refire = 0; + p.message = null; + p.damagecount = 0; + p.bonuscount = 0; + p.extralight = 0; + p.fixedcolormap = Palettes.COLORMAP_FIXED; + p.viewheight = VIEWHEIGHT; + + // setup gun psprite + P_SetupPsprites: + { + p.SetupPsprites(); + } + + // give all cards in death match mode + if (IsDeathMatch()) { + for (int i = 0; i < NUMCARDS; i++) { + p.cards[i] = true; + } + } + + if (mthing.type - 1 == ConsolePlayerNumber()) { + // wake up the status bar + ST_Start: + { + statusBar().Start(); + } + // wake up the heads up text + HU_Start: + { + headsUp().Start(); + } + } + } + + /** + * P_SpawnMapThing The fields of the mapthing should already be in host byte order. + */ + default mobj_t SpawnMapThing(mapthing_t mthing) { + final DoomMain D = DOOM(); + int i; + int bit; + mobj_t mobj; + int x; + int y; + int z; + + // count deathmatch start positions + if (mthing.type == 11) { + if (D.deathmatch_p < 10/*DM.deathmatchstarts[10]*/) { + // memcpy (deathmatch_p, mthing, sizeof(*mthing)); + D.deathmatchstarts[D.deathmatch_p] = new mapthing_t(mthing); + D.deathmatch_p++; + } + return null; + } + + if (mthing.type <= 0) { + // Ripped from Chocolate Doom :-p + // Thing type 0 is actually "player -1 start". + // For some reason, Vanilla Doom accepts/ignores this. + // MAES: no kidding. + + return null; + } + + // check for players specially + if (mthing.type <= 4 && mthing.type > 0) // killough 2/26/98 -- fix crashes + { + // save spots for respawning in network games + D.playerstarts[mthing.type - 1] = new mapthing_t(mthing); + if (!IsDeathMatch()) { + this.SpawnPlayer(mthing); + } + + return null; + } + + // check for apropriate skill level + if (!IsNetGame() && eval(mthing.options & 16)) { + return null; + } + + switch (getGameSkill()) { + case sk_baby: + bit = 1; + break; + case sk_nightmare: + bit = 4; + break; + default: + bit = 1 << (getGameSkill().ordinal() - 1); + break; + } + + if (!eval(mthing.options & bit)) { + return null; + } + + // find which type to spawn + for (i = 0; i < NUMMOBJTYPES; i++) { + if (mthing.type == mobjinfo[i].doomednum) { + break; + } + } + + // phares 5/16/98: + // Do not abort because of an unknown thing. Ignore it, but post a + // warning message for the player. + if (i == NUMMOBJTYPES) { + Spawn.LOGGER.log(Level.WARNING, + String.format("P_SpawnMapThing: Unknown type %d at (%d, %d)", mthing.type, mthing.x, mthing.y)); + return null; + } + + // don't spawn keycards and players in deathmatch + if (IsDeathMatch() && eval(mobjinfo[i].flags & MF_NOTDMATCH)) { + return null; + } + + // don't spawn any monsters if -nomonsters + if (D.nomonsters && (i == mobjtype_t.MT_SKULL.ordinal() || eval(mobjinfo[i].flags & MF_COUNTKILL))) { + return null; + } + + // spawn it + x = mthing.x << FRACBITS; + y = mthing.y << FRACBITS; + + if (eval(mobjinfo[i].flags & MF_SPAWNCEILING)) { + z = ONCEILINGZ; + } else { + z = ONFLOORZ; + } + + mobj = this.SpawnMobj(x, y, z, mobjtype_t.values()[i]); + mobj.spawnpoint.copyFrom(mthing); + + if (mobj.mobj_tics > 0) { + mobj.mobj_tics = 1 + (P_Random() % mobj.mobj_tics); + } + if (eval(mobj.flags & MF_COUNTKILL)) { + D.totalkills++; + } + if (eval(mobj.flags & MF_COUNTITEM)) { + D.totalitems++; + } + + mobj.angle = ANG45 * (mthing.angle / 45); + if (eval(mthing.options & MTF_AMBUSH)) { + mobj.flags |= MF_AMBUSH; + } + + return mobj; + + } + + /** + * P_SpawnBlood + * + * @param x fixed + * @param y fixed + * @param z fixed + * @param damage + */ + default void SpawnBlood(int x, int y, int z, int damage) { + mobj_t th; + + z += ((P_Random() - P_Random()) << 10); + th = this.SpawnMobj(x, y, z, mobjtype_t.MT_BLOOD); + th.momz = FRACUNIT * 2; + th.mobj_tics -= P_Random() & 3; + + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + + if (damage <= 12 && damage >= 9) { + th.SetMobjState(statenum_t.S_BLOOD2); + } else if (damage < 9) { + th.SetMobjState(statenum_t.S_BLOOD3); + } + } + + /** + * P_SpawnPuff + * + * @param x fixed + * @param y fixed + * @param z fixed + * + */ + default void SpawnPuff(int x, int y, int z) { + mobj_t th; + + z += ((P_Random() - P_Random()) << 10); + + th = this.SpawnMobj(x, y, z, mobjtype_t.MT_PUFF); + th.momz = FRACUNIT; + th.mobj_tics -= P_Random() & 3; + + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + + // don't make punches spark on the wall + if (contextTest(KEY_SPAWN, Spawn::isMeleeRange)) { + th.SetMobjState(statenum_t.S_PUFF3); + } + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsTeleportation.java b/doom/src/p/Actions/ActionsTeleportation.java new file mode 100644 index 0000000..562b1ca --- /dev/null +++ b/doom/src/p/Actions/ActionsTeleportation.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Limits.MAXRADIUS; +import data.Tables; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.mobjtype_t; +import data.sounds; +import doom.SourceCode.fixed_t; +import doom.thinker_t; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import p.AbstractLevelLoader; +import p.ActiveStates; +import p.mobj_t; +import static p.mobj_t.MF_MISSILE; +import rr.line_t; +import rr.sector_t; +import rr.subsector_t; + +public interface ActionsTeleportation extends ActionsSectors { + + void UnsetThingPosition(mobj_t mobj); + + // + // TELEPORTATION + // + @Override + default int Teleport(line_t line, int side, mobj_t thing) { + int i; + int tag; + mobj_t m; + mobj_t fog; + int an; + thinker_t thinker; + sector_t sector; + @fixed_t + int oldx, oldy, oldz; + + // don't teleport missiles + if ((thing.flags & MF_MISSILE) != 0) { + return 0; + } + + // Don't teleport if hit back of line, + // so you can get out of teleporter. + if (side == 1) { + return 0; + } + + tag = line.tag; + for (i = 0; i < levelLoader().numsectors; i++) { + if (levelLoader().sectors[i].tag == tag) { + //thinker = thinkercap.next; + for (thinker = getThinkerCap().next; thinker != getThinkerCap(); thinker = thinker.next) { + // not a mobj + if (thinker.thinkerFunction != ActiveStates.P_MobjThinker) { + continue; + } + + m = (mobj_t) thinker; + + // not a teleportman + if (m.type != mobjtype_t.MT_TELEPORTMAN) { + continue; + } + + sector = m.subsector.sector; + // wrong sector + if (sector.id != i) { + continue; + } + + oldx = thing.x; + oldy = thing.y; + oldz = thing.z; + + if (!TeleportMove(thing, m.x, m.y)) { + return 0; + } + + thing.z = thing.floorz; //fixme: not needed? + if (thing.player != null) { + thing.player.viewz = thing.z + thing.player.viewheight; + thing.player.lookdir = 0; // Reset lookdir + } + + // spawn teleport fog at source and destination + fog = SpawnMobj(oldx, oldy, oldz, mobjtype_t.MT_TFOG); + StartSound(fog, sounds.sfxenum_t.sfx_telept); + an = Tables.toBAMIndex(m.angle); + fog = SpawnMobj(m.x + 20 * finecosine[an], m.y + 20 * finesine[an], thing.z, mobjtype_t.MT_TFOG); + + // emit sound, where? + StartSound(fog, sounds.sfxenum_t.sfx_telept); + + // don't move for a bit + if (thing.player != null) { + thing.reactiontime = 18; + } + + thing.angle = m.angle; + thing.momx = thing.momy = thing.momz = 0; + return 1; + } + } + } + return 0; + } + + // + // TELEPORT MOVE + // + // + // P_TeleportMove + // + default boolean TeleportMove(mobj_t thing, int x, /*fixed*/ int y) { + final Spechits spechits = contextRequire(KEY_SPECHITS); + final AbstractLevelLoader ll = levelLoader(); + final Movement ma = contextRequire(KEY_MOVEMENT); + int xl; + int xh; + int yl; + int yh; + int bx; + int by; + + subsector_t newsubsec; + + // kill anything occupying the position + ma.tmthing = thing; + ma.tmflags = thing.flags; + + ma.tmx = x; + ma.tmy = y; + + ma.tmbbox[BOXTOP] = y + ma.tmthing.radius; + ma.tmbbox[BOXBOTTOM] = y - ma.tmthing.radius; + ma.tmbbox[BOXRIGHT] = x + ma.tmthing.radius; + ma.tmbbox[BOXLEFT] = x - ma.tmthing.radius; + + newsubsec = ll.PointInSubsector(x, y); + ma.ceilingline = null; + + // The base floor/ceiling is from the subsector + // that contains the point. + // Any contacted lines the step closer together + // will adjust them. + ma.tmfloorz = ma.tmdropoffz = newsubsec.sector.floorheight; + ma.tmceilingz = newsubsec.sector.ceilingheight; + + sceneRenderer().increaseValidCount(1); // This is r_main's ? + spechits.numspechit = 0; + + // stomp on any things contacted + xl = ll.getSafeBlockX(ma.tmbbox[BOXLEFT] - ll.bmaporgx - MAXRADIUS); + xh = ll.getSafeBlockX(ma.tmbbox[BOXRIGHT] - ll.bmaporgx + MAXRADIUS); + yl = ll.getSafeBlockY(ma.tmbbox[BOXBOTTOM] - ll.bmaporgy - MAXRADIUS); + yh = ll.getSafeBlockY(ma.tmbbox[BOXTOP] - ll.bmaporgy + MAXRADIUS); + + for (bx = xl; bx <= xh; bx++) { + for (by = yl; by <= yh; by++) { + if (!BlockThingsIterator(bx, by, this::StompThing)) { + return false; + } + } + } + + // the move is ok, + // so link the thing into its new position + UnsetThingPosition(thing); + + thing.floorz = ma.tmfloorz; + thing.ceilingz = ma.tmceilingz; + thing.x = x; + thing.y = y; + + ll.SetThingPosition(thing); + + return true; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsThings.java b/doom/src/p/Actions/ActionsThings.java new file mode 100644 index 0000000..3acaaf1 --- /dev/null +++ b/doom/src/p/Actions/ActionsThings.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.NUMAMMO; +import static data.Defines.pw_allmap; +import static data.Defines.pw_infrared; +import static data.Defines.pw_invisibility; +import static data.Defines.pw_invulnerability; +import static data.Defines.pw_ironfeet; +import static data.Defines.pw_strength; +import data.mobjtype_t; +import data.sounds.sfxenum_t; +import defines.ammotype_t; +import defines.card_t; +import doom.DoomMain; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PIT_CheckThing; +import static doom.SourceCode.P_Map.PIT_StompThing; +import doom.SourceCode.fixed_t; +import static doom.englsh.*; +import doom.player_t; +import doom.weapontype_t; +import m.Settings; +import static m.fixed_t.FRACUNIT; +import p.mobj_t; +import static p.mobj_t.MF_COUNTITEM; +import static p.mobj_t.MF_DROPPED; +import static p.mobj_t.MF_MISSILE; +import static p.mobj_t.MF_PICKUP; +import static p.mobj_t.MF_SHOOTABLE; +import static p.mobj_t.MF_SKULLFLY; +import static p.mobj_t.MF_SOLID; +import static p.mobj_t.MF_SPECIAL; +import static utils.C2JUtils.eval; + +public interface ActionsThings extends ActionTrait { + + void DamageMobj(mobj_t thing, mobj_t tmthing, mobj_t tmthing0, int damage); + + void RemoveMobj(mobj_t special); + + /** + * PIT_CheckThing + */ + @Override + @P_Map.C(PIT_CheckThing) + default boolean CheckThing(mobj_t thing) { + final Movement movm = contextRequire(KEY_MOVEMENT); + @fixed_t + int blockdist; + boolean solid; + int damage; + + if ((thing.flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)) == 0) { + return true; + } + + blockdist = thing.radius + movm.tmthing.radius; + + if (Math.abs(thing.x - movm.tmx) >= blockdist + || Math.abs(thing.y - movm.tmy) >= blockdist) { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == movm.tmthing) { + return true; + } + + // check for skulls slamming into things + if ((movm.tmthing.flags & MF_SKULLFLY) != 0) { + damage = ((P_Random() % 8) + 1) * movm.tmthing.info.damage; + + DamageMobj(thing, movm.tmthing, movm.tmthing, damage); + + movm.tmthing.flags &= ~MF_SKULLFLY; + movm.tmthing.momx = movm.tmthing.momy = movm.tmthing.momz = 0; + + movm.tmthing.SetMobjState(movm.tmthing.info.spawnstate); + + return false; // stop moving + } + + // missiles can hit other things + if (eval(movm.tmthing.flags & MF_MISSILE)) { + // see if it went over / under + if (movm.tmthing.z > thing.z + thing.height) { + return true; // overhead + } + if (movm.tmthing.z + movm.tmthing.height < thing.z) { + return true; // underneath + } + if (movm.tmthing.target != null && (movm.tmthing.target.type == thing.type + || (movm.tmthing.target.type == mobjtype_t.MT_KNIGHT && thing.type == mobjtype_t.MT_BRUISER) + || (movm.tmthing.target.type == mobjtype_t.MT_BRUISER && thing.type == mobjtype_t.MT_KNIGHT))) { + // Don't hit same species as originator. + if (thing == movm.tmthing.target) { + return true; + } + + if (thing.type != mobjtype_t.MT_PLAYER) { + // Explode, but do no damage. + // Let players missile other players. + return false; + } + } + + if (!eval(thing.flags & MF_SHOOTABLE)) { + // didn't do any damage + return !eval(thing.flags & MF_SOLID); + } + + // damage / explode + damage = ((P_Random() % 8) + 1) * movm.tmthing.info.damage; + DamageMobj(thing, movm.tmthing, movm.tmthing.target, damage); + + // don't traverse any more + return false; + } + + // check for special pickup + if (eval(thing.flags & MF_SPECIAL)) { + solid = eval(thing.flags & MF_SOLID); + if (eval(movm.tmflags & MF_PICKUP)) { + // can remove thing + TouchSpecialThing(thing, movm.tmthing); + } + return !solid; + } + + return !eval(thing.flags & MF_SOLID); + } + + ; + + /** + * P_TouchSpecialThing LIKE ROMERO's ASS!!! + */ + default void TouchSpecialThing(mobj_t special, mobj_t toucher) { + final DoomMain DOOM = DOOM(); + player_t player; + int i; + @fixed_t + int delta; + sfxenum_t sound; + + delta = special.z - toucher.z; + + if (delta > toucher.height || delta < -8 * FRACUNIT) { + // out of reach + return; + } + + sound = sfxenum_t.sfx_itemup; + player = toucher.player; + + // Dead thing touching. + // Can happen with a sliding player corpse. + if (toucher.health <= 0) { + return; + } + + // Identify by sprite. + switch (special.mobj_sprite) { + // armor + case SPR_ARM1: + if (!player.GiveArmor(1)) { + return; + } + player.message = GOTARMOR; + break; + + case SPR_ARM2: + if (!player.GiveArmor(2)) { + return; + } + player.message = GOTMEGA; + break; + + // bonus items + case SPR_BON1: + player.health[0]++; // can go over 100% + if (player.health[0] > 200) { + player.health[0] = 200; + } + player.mo.health = player.health[0]; + player.message = GOTHTHBONUS; + break; + + case SPR_BON2: + player.armorpoints[0]++; // can go over 100% + if (player.armorpoints[0] > 200) { + player.armorpoints[0] = 200; + } + if (player.armortype == 0) { + player.armortype = 1; + } + player.message = GOTARMBONUS; + break; + + case SPR_SOUL: + player.health[0] += 100; + if (player.health[0] > 200) { + player.health[0] = 200; + } + player.mo.health = player.health[0]; + player.message = GOTSUPER; + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_MEGA: + if (!DOOM.isCommercial()) { + return; + } + player.health[0] = 200; + player.mo.health = player.health[0]; + player.GiveArmor(2); + player.message = GOTMSPHERE; + sound = sfxenum_t.sfx_getpow; + break; + + // cards + // leave cards for everyone + case SPR_BKEY: + if (!player.cards[card_t.it_bluecard.ordinal()]) { + player.message = GOTBLUECARD; + } + player.GiveCard(card_t.it_bluecard); + if (!DOOM.netgame) { + break; + } + return; + + case SPR_YKEY: + if (!player.cards[card_t.it_yellowcard.ordinal()]) { + player.message = GOTYELWCARD; + } + player.GiveCard(card_t.it_yellowcard); + if (!DOOM.netgame) { + break; + } + return; + + case SPR_RKEY: + if (!player.cards[card_t.it_redcard.ordinal()]) { + player.message = GOTREDCARD; + } + player.GiveCard(card_t.it_redcard); + if (!DOOM.netgame) { + break; + } + return; + + case SPR_BSKU: + if (!player.cards[card_t.it_blueskull.ordinal()]) { + player.message = GOTBLUESKUL; + } + player.GiveCard(card_t.it_blueskull); + if (!DOOM.netgame) { + break; + } + return; + + case SPR_YSKU: + if (!player.cards[card_t.it_yellowskull.ordinal()]) { + player.message = GOTYELWSKUL; + } + player.GiveCard(card_t.it_yellowskull); + if (!DOOM.netgame) { + break; + } + return; + + case SPR_RSKU: + if (!player.cards[card_t.it_redskull.ordinal()]) { + player.message = GOTREDSKULL; + } + player.GiveCard(card_t.it_redskull); + if (!DOOM.netgame) { + break; + } + return; + + // medikits, heals + case SPR_STIM: + if (!player.GiveBody(10)) { + return; + } + player.message = GOTSTIM; + break; + + case SPR_MEDI: + /** + * Another fix with switchable option to enable + * - Good Sign 2017/04/03 + */ + boolean need = player.health[0] < 25; + + if (!player.GiveBody(25)) { + return; + } + + if (DOOM.CM.equals(Settings.fix_medi_need, Boolean.FALSE)) // default behavior - with bug + { + player.message = player.health[0] < 25 ? GOTMEDINEED : GOTMEDIKIT; + } else //proper behavior + { + player.message = need ? GOTMEDINEED : GOTMEDIKIT; + } + + break; + + // power ups + case SPR_PINV: + if (!player.GivePower(pw_invulnerability)) { + return; + } + player.message = GOTINVUL; + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_PSTR: + if (!player.GivePower(pw_strength)) { + return; + } + player.message = GOTBERSERK; + if (player.readyweapon != weapontype_t.wp_fist) { + player.pendingweapon = weapontype_t.wp_fist; + } + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_PINS: + if (!player.GivePower(pw_invisibility)) { + return; + } + player.message = GOTINVIS; + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_SUIT: + if (!player.GivePower(pw_ironfeet)) { + return; + } + player.message = GOTSUIT; + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_PMAP: + if (!player.GivePower(pw_allmap)) { + return; + } + player.message = GOTMAP; + sound = sfxenum_t.sfx_getpow; + break; + + case SPR_PVIS: + if (!player.GivePower(pw_infrared)) { + return; + } + player.message = GOTVISOR; + sound = sfxenum_t.sfx_getpow; + break; + + // ammo + case SPR_CLIP: + if ((special.flags & MF_DROPPED) != 0) { + if (!player.GiveAmmo(ammotype_t.am_clip, 0)) { + return; + } + } else { + if (!player.GiveAmmo(ammotype_t.am_clip, 1)) { + return; + } + } + player.message = GOTCLIP; + break; + + case SPR_AMMO: + if (!player.GiveAmmo(ammotype_t.am_clip, 5)) { + return; + } + player.message = GOTCLIPBOX; + break; + + case SPR_ROCK: + if (!player.GiveAmmo(ammotype_t.am_misl, 1)) { + return; + } + player.message = GOTROCKET; + break; + + case SPR_BROK: + if (!player.GiveAmmo(ammotype_t.am_misl, 5)) { + return; + } + player.message = GOTROCKBOX; + break; + + case SPR_CELL: + if (!player.GiveAmmo(ammotype_t.am_cell, 1)) { + return; + } + player.message = GOTCELL; + break; + + case SPR_CELP: + if (!player.GiveAmmo(ammotype_t.am_cell, 5)) { + return; + } + player.message = GOTCELLBOX; + break; + + case SPR_SHEL: + if (!player.GiveAmmo(ammotype_t.am_shell, 1)) { + return; + } + player.message = GOTSHELLS; + break; + + case SPR_SBOX: + if (!player.GiveAmmo(ammotype_t.am_shell, 5)) { + return; + } + player.message = GOTSHELLBOX; + break; + + case SPR_BPAK: + if (!player.backpack) { + for (i = 0; i < NUMAMMO; i++) { + player.maxammo[i] *= 2; + } + player.backpack = true; + } + for (i = 0; i < NUMAMMO; i++) { + player.GiveAmmo(ammotype_t.values()[i], 1); + } + player.message = GOTBACKPACK; + break; + + // weapons + case SPR_BFUG: + if (!player.GiveWeapon(weapontype_t.wp_bfg, false)) { + return; + } + player.message = GOTBFG9000; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_MGUN: + if (!player.GiveWeapon(weapontype_t.wp_chaingun, + (special.flags & MF_DROPPED) != 0)) { + return; + } + player.message = GOTCHAINGUN; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_CSAW: + if (!player.GiveWeapon(weapontype_t.wp_chainsaw, false)) { + return; + } + player.message = GOTCHAINSAW; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_LAUN: + if (!player.GiveWeapon(weapontype_t.wp_missile, false)) { + return; + } + player.message = GOTLAUNCHER; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_PLAS: + if (!player.GiveWeapon(weapontype_t.wp_plasma, false)) { + return; + } + player.message = GOTPLASMA; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_SHOT: + if (!player.GiveWeapon(weapontype_t.wp_shotgun, + (special.flags & MF_DROPPED) != 0)) { + return; + } + player.message = GOTSHOTGUN; + sound = sfxenum_t.sfx_wpnup; + break; + + case SPR_SGN2: + if (!player.GiveWeapon(weapontype_t.wp_supershotgun, + (special.flags & MF_DROPPED) != 0)) { + return; + } + player.message = GOTSHOTGUN2; + sound = sfxenum_t.sfx_wpnup; + break; + + default: + DOOM.doomSystem.Error("P_SpecialThing: Unknown gettable thing"); + } + + if ((special.flags & MF_COUNTITEM) != 0) { + player.itemcount++; + } + RemoveMobj(special); + player.bonuscount += player_t.BONUSADD; + if (player == DOOM.players[DOOM.consoleplayer]) { + DOOM.doomSound.StartSound(null, sound); + } + } + + /** + * PIT_StompThing + */ + @Override + @P_Map.C(PIT_StompThing) + default boolean StompThing(mobj_t thing) { + final Movement mov = contextRequire(KEY_MOVEMENT); + @fixed_t + int blockdist; + + if ((thing.flags & MF_SHOOTABLE) == 0) { + return true; + } + + blockdist = thing.radius + mov.tmthing.radius; + + if (Math.abs(thing.x - mov.tmx) >= blockdist || Math.abs(thing.y - mov.tmy) >= blockdist) { + // didn't hit it + return true; + } + + // don't clip against self + if (thing == mov.tmthing) { + return true; + } + + // monsters don't stomp things except on boss level + if ((mov.tmthing.player == null) && (MapNumber() != 30)) { + return false; + } + + DamageMobj(thing, mov.tmthing, mov.tmthing, 10000); // in interaction + return true; + } +; +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsThinkers.java b/doom/src/p/Actions/ActionsThinkers.java new file mode 100644 index 0000000..01c41b7 --- /dev/null +++ b/doom/src/p/Actions/ActionsThinkers.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.ITEMQUESIZE; +import static data.Defines.ONCEILINGZ; +import static data.Defines.ONFLOORZ; +import static data.Limits.MAXPLAYERS; +import static data.Tables.ANG45; +import static data.info.mobjinfo; +import data.mapthing_t; +import data.mobjtype_t; +import data.sounds; +import doom.CommandVariable; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.P_Spec; +import static doom.SourceCode.P_Spec.P_SpawnSpecials; +import static doom.SourceCode.P_Tick.P_RemoveThinker; +import doom.thinker_t; +import static m.fixed_t.FRACBITS; +import p.AbstractLevelLoader; +import p.ActiveStates; +import p.ActiveStates.MobjConsumer; +import p.ActiveStates.ThinkerConsumer; +import static p.DoorDefines.FASTDARK; +import static p.DoorDefines.SLOWDARK; +import p.RemoveState; +import p.ThinkerList; +import p.UnifiedGameMap; +import p.mobj_t; +import static p.mobj_t.MF_SPAWNCEILING; +import rr.sector_t; +import rr.subsector_t; +import static utils.C2JUtils.eval; + +public interface ActionsThinkers extends ActionsSectors, ThinkerList { + + // + // P_RemoveThinker + // Deallocation is lazy -- it will not actually be freed + // until its thinking turn comes up. + // + // + // killough 4/25/98: + // + // Instead of marking the function with -1 value cast to a function pointer, + // set the function to P_RemoveThinkerDelayed(), so that later, it will be + // removed automatically as part of the thinker process. + // + @Override + @SourceCode.Compatible("thinker->function.acv = (actionf_v)(-1)") + @SourceCode.P_Tick.C(P_RemoveThinker) + default void RemoveThinker(thinker_t thinker) { + thinker.thinkerFunction = RemoveState.REMOVE; + } + + /** + * P_SpawnSpecials After the map has been loaded, scan for specials that spawn thinkers + */ + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @P_Spec.C(P_SpawnSpecials) + default void SpawnSpecials() { + final DoomMain D = DOOM(); + final AbstractLevelLoader ll = levelLoader(); + final UnifiedGameMap.Specials sp = getSpecials(); + sector_t sector; + + /*int episode; + + episode = 1; + if (W.CheckNumForName("texture2") >= 0) + episode = 2; + */ + // See if -TIMER needs to be used. + sp.levelTimer = false; + + if (D.cVarManager.bool(CommandVariable.AVG) && IsDeathMatch()) { + sp.levelTimer = true; + sp.levelTimeCount = 20 * 60 * 35; + } + + if (IsDeathMatch()) { + D.cVarManager.with(CommandVariable.TIMER, 0, (Integer i) -> { + sp.levelTimer = true; + sp.levelTimeCount = i * 60 * 35; + }); + } + + // Init special SECTORs. + //sector = LL.sectors; + for (int i = 0; i < ll.numsectors; i++) { + sector = ll.sectors[i]; + if (!eval(sector.special)) { + continue; + } + + switch (sector.special) { + case 1: + // FLICKERING LIGHTS + P_SpawnLightFlash: + { + SpawnLightFlash(sector); + } + break; + + case 2: + // STROBE FAST + P_SpawnStrobeFlash: + { + SpawnStrobeFlash(sector, FASTDARK, 0); + } + break; + + case 3: + // STROBE SLOW + P_SpawnStrobeFlash: + { + SpawnStrobeFlash(sector, SLOWDARK, 0); + } + break; + + case 4: + // STROBE FAST/DEATH SLIME + P_SpawnStrobeFlash: + { + SpawnStrobeFlash(sector, FASTDARK, 0); + } + sector.special = 4; + break; + + case 8: + // GLOWING LIGHT + P_SpawnGlowingLight: + { + SpawnGlowingLight(sector); + } + break; + case 9: + // SECRET SECTOR + D.totalsecret++; + break; + + case 10: + // DOOR CLOSE IN 30 SECONDS + SpawnDoorCloseIn30: + { + SpawnDoorCloseIn30(sector); + } + break; + + case 12: + // SYNC STROBE SLOW + P_SpawnStrobeFlash: + { + SpawnStrobeFlash(sector, SLOWDARK, 1); + } + break; + + case 13: + // SYNC STROBE FAST + P_SpawnStrobeFlash: + { + SpawnStrobeFlash(sector, FASTDARK, 1); + } + break; + + case 14: + // DOOR RAISE IN 5 MINUTES + P_SpawnDoorRaiseIn5Mins: + { + SpawnDoorRaiseIn5Mins(sector, i); + } + break; + + case 17: + P_SpawnFireFlicker: + { + SpawnFireFlicker(sector); + } + break; + } + } + + // Init line EFFECTs + sp.numlinespecials = 0; + for (int i = 0; i < ll.numlines; i++) { + switch (ll.lines[i].special) { + case 48: + // EFFECT FIRSTCOL SCROLL+ + // Maes 6/4/2012: removed length limit. + if (sp.numlinespecials == sp.linespeciallist.length) { + sp.resizeLinesSpecialList(); + } + sp.linespeciallist[sp.numlinespecials] = ll.lines[i]; + sp.numlinespecials++; + break; + } + } + + // Init other misc stuff + for (int i = 0; i < this.getMaxCeilings(); i++) { + this.getActiveCeilings()[i] = null; + } + + getSwitches().initButtonList(); + + // UNUSED: no horizonal sliders. + // if (SL!=null) { + // SL.updateStatus(DM); + // SL.P_InitSlidingDoorFrames(); + //} + } + + /** + * P_RespawnSpecials + */ + default void RespawnSpecials() { + final RespawnQueue resp = contextRequire(KEY_RESP_QUEUE); + int x, y, z; // fixed + + subsector_t ss; + mobj_t mo; + mapthing_t mthing; + + int i; + + // only respawn items in deathmatch (deathmatch!=2) + if (!DOOM().altdeath) { + return; // + } + // nothing left to respawn? + if (resp.iquehead == resp.iquetail) { + return; + } + + // wait at least 30 seconds + if (LevelTime() - resp.itemrespawntime[resp.iquetail] < 30 * 35) { + return; + } + + mthing = resp.itemrespawnque[resp.iquetail]; + + x = mthing.x << FRACBITS; + y = mthing.y << FRACBITS; + + // spawn a teleport fog at the new spot + ss = levelLoader().PointInSubsector(x, y); + mo = SpawnMobj(x, y, ss.sector.floorheight, mobjtype_t.MT_IFOG); + StartSound(mo, sounds.sfxenum_t.sfx_itmbk); + + // find which type to spawn + for (i = 0; i < mobjtype_t.NUMMOBJTYPES.ordinal(); i++) { + if (mthing.type == mobjinfo[i].doomednum) { + break; + } + } + + // spawn it + if (eval(mobjinfo[i].flags & MF_SPAWNCEILING)) { + z = ONCEILINGZ; + } else { + z = ONFLOORZ; + } + + mo = SpawnMobj(x, y, z, mobjtype_t.values()[i]); + mo.spawnpoint = mthing; + mo.angle = ANG45 * (mthing.angle / 45); + + // pull it from the que + resp.iquetail = (resp.iquetail + 1) & (ITEMQUESIZE - 1); + } + + // + // P_AllocateThinker + // Allocates memory and adds a new thinker at the end of the list. + // + //public void AllocateThinker(thinker_t thinker) {; + // UNUSED + //} + // + // P_RunThinkers + // + default void RunThinkers() { + thinker_t thinker = getThinkerCap().next; + while (thinker != getThinkerCap()) { + if (thinker.thinkerFunction == RemoveState.REMOVE) { + // time to remove it + thinker.next.prev = thinker.prev; + thinker.prev.next = thinker.next; + // Z_Free (currentthinker); + } else { + ActiveStates thinkerFunction = (ActiveStates) thinker.thinkerFunction; + if (thinkerFunction.isParamType(MobjConsumer.class)) { + thinkerFunction.fun(MobjConsumer.class).accept(DOOM().actions, (mobj_t) thinker); + } else if (thinkerFunction.isParamType(ThinkerConsumer.class)) { + thinkerFunction.fun(ThinkerConsumer.class).accept(DOOM().actions, thinker); + } + } + thinker = thinker.next; + } + } + + // + //P_Ticker + // + default void Ticker() { + // run the tic + if (IsPaused()) { + return; + } + + // pause if in menu and at least one tic has been run + if (!IsNetGame() && IsMenuActive() && !IsDemoPlayback() && getPlayer(ConsolePlayerNumber()).viewz != 1) { + return; + } + + for (int i = 0; i < MAXPLAYERS; i++) { + if (PlayerInGame(i)) { + getPlayer(i).PlayerThink(); + } + } + + RunThinkers(); + getSpecials().UpdateSpecials(); // In specials. Merge? + RespawnSpecials(); + + // for par times + DOOM().leveltime++; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActionsUseEvents.java b/doom/src/p/Actions/ActionsUseEvents.java new file mode 100644 index 0000000..3a4566b --- /dev/null +++ b/doom/src/p/Actions/ActionsUseEvents.java @@ -0,0 +1,539 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions; + +import static data.Defines.PT_ADDLINES; +import static data.Defines.USERANGE; +import data.Tables; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.sounds; +import doom.SourceCode.P_Map; +import static doom.SourceCode.P_Map.PTR_UseTraverse; +import doom.player_t; +import java.util.function.Predicate; +import static m.fixed_t.FRACBITS; +import p.ceiling_e; +import p.floor_e; +import p.intercept_t; +import p.mobj_t; +import p.plattype_e; +import p.stair_e; +import p.vldoor_e; +import rr.line_t; +import static rr.line_t.ML_SECRET; +import static utils.C2JUtils.eval; + +public interface ActionsUseEvents extends ActionTrait { + + void VerticalDoor(line_t line, mobj_t thing); + + void LightTurnOn(line_t line, int i); + + boolean BuildStairs(line_t line, stair_e stair_e); + + boolean DoDonut(line_t line); + + boolean DoFloor(line_t line, floor_e floor_e); + + boolean DoDoor(line_t line, vldoor_e vldoor_e); + + boolean DoPlat(line_t line, plattype_e plattype_e, int i); + + boolean DoCeiling(line_t line, ceiling_e ceiling_e); + + boolean DoLockedDoor(line_t line, vldoor_e vldoor_e, mobj_t thing); + + boolean PathTraverse(int x1, int y1, int x2, int y2, int flags, Predicate trav); + + /** + * P_UseSpecialLine Called when a thing uses a special line. Only the front sides of lines are usable. + */ + default boolean UseSpecialLine(mobj_t thing, line_t line, boolean side) { + // Err... + // Use the back sides of VERY SPECIAL lines... + if (side) { + switch (line.special) { + case 124: + // Sliding door open&close + // SL.EV_SlidingDoor(line, thing); + break; + + default: + return false; + //break; + } + } + + // Switches that other things can activate. + //_D_: little bug fixed here, see linuxdoom source + if (thing.player ==/*!=*/ null) { + // never open secret doors + if (eval(line.flags & ML_SECRET)) { + return false; + } + + switch (line.special) { + case 1: // MANUAL DOOR RAISE + case 32: // MANUAL BLUE + case 33: // MANUAL RED + case 34: // MANUAL YELLOW + break; + + default: + return false; + //break; + } + } + + // do something + switch (line.special) { + // MANUALS + case 1: // Vertical Door + case 26: // Blue Door/Locked + case 27: // Yellow Door /Locked + case 28: // Red Door /Locked + + case 31: // Manual door open + case 32: // Blue locked door open + case 33: // Red locked door open + case 34: // Yellow locked door open + + case 117: // Blazing door raise + case 118: // Blazing door open + VerticalDoor(line, thing); + break; + + //UNUSED - Door Slide Open&Close + case 124: + // NOTE: clashes with secret level exit. + //SL.EV_SlidingDoor (line, thing); + break; + + // SWITCHES + case 7: + // Build Stairs + if (BuildStairs(line, stair_e.build8)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 9: + // Change Donut + if (DoDonut(line)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 11: + // Exit level + getSwitches().ChangeSwitchTexture(line, false); + DOOM().ExitLevel(); + break; + + case 14: + // Raise Floor 32 and change texture + if (DoPlat(line, plattype_e.raiseAndChange, 32)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 15: + // Raise Floor 24 and change texture + if (DoPlat(line, plattype_e.raiseAndChange, 24)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 18: + // Raise Floor to next highest floor + if (DoFloor(line, floor_e.raiseFloorToNearest)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 20: + // Raise Plat next highest floor and change texture + if (DoPlat(line, plattype_e.raiseToNearestAndChange, 0)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 21: + // PlatDownWaitUpStay + if (DoPlat(line, plattype_e.downWaitUpStay, 0)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 23: + // Lower Floor to Lowest + if (DoFloor(line, floor_e.lowerFloorToLowest)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 29: + // Raise Door + if (DoDoor(line, vldoor_e.normal)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 41: + // Lower Ceiling to Floor + if (DoCeiling(line, ceiling_e.lowerToFloor)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 71: + // Turbo Lower Floor + if (DoFloor(line, floor_e.turboLower)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 49: + // Ceiling Crush And Raise + if (DoCeiling(line, ceiling_e.crushAndRaise)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 50: + // Close Door + if (DoDoor(line, vldoor_e.close)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 51: + // Secret EXIT + getSwitches().ChangeSwitchTexture(line, false); + DOOM().SecretExitLevel(); + break; + + case 55: + // Raise Floor Crush + if (DoFloor(line, floor_e.raiseFloorCrush)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 101: + // Raise Floor + if (DoFloor(line, floor_e.raiseFloor)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 102: + // Lower Floor to Surrounding floor height + if (DoFloor(line, floor_e.lowerFloor)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 103: + // Open Door + if (DoDoor(line, vldoor_e.open)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 111: + // Blazing Door Raise (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeRaise)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 112: + // Blazing Door Open (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeOpen)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 113: + // Blazing Door Close (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeClose)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 122: + // Blazing PlatDownWaitUpStay + if (DoPlat(line, plattype_e.blazeDWUS, 0)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 127: + // Build Stairs Turbo 16 + if (this.BuildStairs(line, stair_e.turbo16)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 131: + // Raise Floor Turbo + if (DoFloor(line, floor_e.raiseFloorTurbo)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 133: + // BlzOpenDoor BLUE + case 135: + // BlzOpenDoor RED + case 137: + // BlzOpenDoor YELLOW + if (DoLockedDoor(line, vldoor_e.blazeOpen, thing)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + case 140: + // Raise Floor 512 + if (DoFloor(line, floor_e.raiseFloor512)) { + getSwitches().ChangeSwitchTexture(line, false); + } + break; + + // BUTTONS + case 42: + // Close Door + if (DoDoor(line, vldoor_e.close)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 43: + // Lower Ceiling to Floor + if (this.DoCeiling(line, ceiling_e.lowerToFloor)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 45: + // Lower Floor to Surrounding floor height + if (DoFloor(line, floor_e.lowerFloor)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 60: + // Lower Floor to Lowest + if (DoFloor(line, floor_e.lowerFloorToLowest)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 61: + // Open Door + if (DoDoor(line, vldoor_e.open)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 62: + // PlatDownWaitUpStay + if (DoPlat(line, plattype_e.downWaitUpStay, 1)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 63: + // Raise Door + if (DoDoor(line, vldoor_e.normal)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 64: + // Raise Floor to ceiling + if (DoFloor(line, floor_e.raiseFloor)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 66: + // Raise Floor 24 and change texture + if (DoPlat(line, plattype_e.raiseAndChange, 24)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 67: + // Raise Floor 32 and change texture + if (DoPlat(line, plattype_e.raiseAndChange, 32)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 65: + // Raise Floor Crush + if (DoFloor(line, floor_e.raiseFloorCrush)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 68: + // Raise Plat to next highest floor and change texture + if (DoPlat(line, plattype_e.raiseToNearestAndChange, 0)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 69: + // Raise Floor to next highest floor + if (DoFloor(line, floor_e.raiseFloorToNearest)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 70: + // Turbo Lower Floor + if (DoFloor(line, floor_e.turboLower)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 114: + // Blazing Door Raise (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeRaise)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 115: + // Blazing Door Open (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeOpen)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 116: + // Blazing Door Close (faster than TURBO!) + if (DoDoor(line, vldoor_e.blazeClose)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 123: + // Blazing PlatDownWaitUpStay + if (DoPlat(line, plattype_e.blazeDWUS, 0)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 132: + // Raise Floor Turbo + if (DoFloor(line, floor_e.raiseFloorTurbo)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 99: + // BlzOpenDoor BLUE + case 134: + // BlzOpenDoor RED + case 136: + // BlzOpenDoor YELLOW + if (this.DoLockedDoor(line, vldoor_e.blazeOpen, thing)) { + getSwitches().ChangeSwitchTexture(line, true); + } + break; + + case 138: + // Light Turn On + LightTurnOn(line, 255); + getSwitches().ChangeSwitchTexture(line, true); + break; + + case 139: + // Light Turn Off + LightTurnOn(line, 35); + getSwitches().ChangeSwitchTexture(line, true); + break; + + } + + return true; + } + + /** + * P_UseLines Looks for special lines in front of the player to activate. + */ + default void UseLines(player_t player) { + final Spechits sp = contextRequire(KEY_SPECHITS); + int angle; + int x1, y1, x2, y2; + //System.out.println("Uselines"); + sp.usething = player.mo; + + // Normally this shouldn't cause problems? + angle = Tables.toBAMIndex(player.mo.angle); + + x1 = player.mo.x; + y1 = player.mo.y; + x2 = x1 + (USERANGE >> FRACBITS) * finecosine[angle]; + y2 = y1 + (USERANGE >> FRACBITS) * finesine[angle]; + + PathTraverse(x1, y1, x2, y2, PT_ADDLINES, this::UseTraverse); + } + + // + // USE LINES + // + @P_Map.C(PTR_UseTraverse) + default boolean UseTraverse(intercept_t in) { + final Movement mov = contextRequire(KEY_MOVEMENT); + final Spechits sp = contextRequire(KEY_SPECHITS); + + boolean side; + // FIXME: some sanity check here? + line_t line = (line_t) in.d(); + + if (line.special == 0) { + LineOpening(line); + if (mov.openrange <= 0) { + StartSound(sp.usething, sounds.sfxenum_t.sfx_noway); + + // can't use through a wall + return false; + } + // not a special line, but keep checking + return true; + } + + side = false; + if (line.PointOnLineSide(sp.usething.x, sp.usething.y)) { + side = true; + } + + // return false; // don't use back side + UseSpecialLine(sp.usething, line, side); + + // can't use for than one special line in a row + return false; + } +; +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Ai.java b/doom/src/p/Actions/ActiveStates/Ai.java new file mode 100644 index 0000000..eaec2ea --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Ai.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import static data.Tables.ANG45; +import static data.Tables.BITS32; +import data.mobjtype_t; +import data.sounds; +import defines.skill_t; +import defines.statenum_t; +import p.mobj_t; +import static p.mobj_t.MF_AMBUSH; +import static p.mobj_t.MF_COUNTKILL; +import static p.mobj_t.MF_JUSTATTACKED; +import static p.mobj_t.MF_SHADOW; +import static p.mobj_t.MF_SHOOTABLE; +import static p.mobj_t.MF_SKULLFLY; +import static p.mobj_t.MF_SOLID; +import static utils.C2JUtils.eval; + +public interface Ai extends Monsters, Sounds { + + // + // A_Look + // Stay in state until a player is sighted. + // + default void A_Look(mobj_t actor) { + mobj_t targ; + boolean seeyou = false; // to avoid the fugly goto + + actor.threshold = 0; // any shot will wake up + targ = actor.subsector.sector.soundtarget; + + if (targ != null + && eval(targ.flags & MF_SHOOTABLE)) { + actor.target = targ; + + if (eval(actor.flags & MF_AMBUSH)) { + seeyou = getEnemies().CheckSight(actor, actor.target); + } else { + seeyou = true; + } + } + if (!seeyou) { + if (!getEnemies().LookForPlayers(actor, false)) { + return; + } + } + + // go into chase state + seeyou: + if (actor.info.seesound != null && actor.info.seesound != sounds.sfxenum_t.sfx_None) { + int sound; + + switch (actor.info.seesound) { + case sfx_posit1: + case sfx_posit2: + case sfx_posit3: + sound = sounds.sfxenum_t.sfx_posit1.ordinal() + P_Random() % 3; + break; + + case sfx_bgsit1: + case sfx_bgsit2: + sound = sounds.sfxenum_t.sfx_bgsit1.ordinal() + P_Random() % 2; + break; + + default: + sound = actor.info.seesound.ordinal(); + break; + } + + if (actor.type == mobjtype_t.MT_SPIDER || actor.type == mobjtype_t.MT_CYBORG) { + // full volume + StartSound(null, sound); + } else { + StartSound(actor, sound); + } + } + + actor.SetMobjState(actor.info.seestate); + } + + /** + * A_Chase + * Actor has a melee attack, + * so it tries to close as fast as possible + */ + @Override + default void A_Chase(mobj_t actor) { + int delta; + boolean nomissile = false; // for the fugly goto + + if (actor.reactiontime != 0) { + actor.reactiontime--; + } + + // modify target threshold + if (actor.threshold != 0) { + if (actor.target == null || actor.target.health <= 0) { + actor.threshold = 0; + } else { + actor.threshold--; + } + } + + // turn towards movement direction if not there yet + if (actor.movedir < 8) { + actor.angle &= (7 << 29); + actor.angle &= BITS32; + // Nice problem, here! + delta = (int) (actor.angle - (actor.movedir << 29)); + + if (delta > 0) { + actor.angle -= ANG45; + } else if (delta < 0) { + actor.angle += ANG45; + } + + actor.angle &= BITS32; + } + + if (actor.target == null || !eval(actor.target.flags & MF_SHOOTABLE)) { + // look for a new target + if (getEnemies().LookForPlayers(actor, true)) { + return; // got a new target + } + actor.SetMobjState(actor.info.spawnstate); + return; + } + + // do not attack twice in a row + if (eval(actor.flags & MF_JUSTATTACKED)) { + actor.flags &= ~MF_JUSTATTACKED; + if (getGameSkill() != skill_t.sk_nightmare && !IsFastParm()) { + getAttacks().NewChaseDir(actor); + } + return; + } + + // check for melee attack + if (actor.info.meleestate != statenum_t.S_NULL && getEnemies().CheckMeleeRange(actor)) { + if (actor.info.attacksound != null) { + StartSound(actor, actor.info.attacksound); + } + actor.SetMobjState(actor.info.meleestate); + return; + } + + // check for missile attack + if (actor.info.missilestate != statenum_t.S_NULL) { //_D_: this caused a bug where Demon for example were disappearing + // Assume that a missile attack is possible + if (getGameSkill().ordinal() < skill_t.sk_nightmare.ordinal() && !IsFastParm() && actor.movecount != 0) { + // Uhm....no. + nomissile = true; + } else if (!getEnemies().CheckMissileRange(actor)) { + nomissile = true; // Out of range + } + if (!nomissile) { + // Perform the attack + actor.SetMobjState(actor.info.missilestate); + actor.flags |= MF_JUSTATTACKED; + return; + } + } + + // This should be executed always, if not averted by returns. + // possibly choose another target + if (IsNetGame() && actor.threshold == 0 && !getEnemies().CheckSight(actor, actor.target)) { + if (getEnemies().LookForPlayers(actor, true)) { + return; // got a new target + } + } + + // chase towards player + if (--actor.movecount < 0 || !getAttacks().Move(actor)) { + getAttacks().NewChaseDir(actor); + } + + // make active sound + if (actor.info.activesound != null && P_Random() < 3) { + StartSound(actor, actor.info.activesound); + } + } + + @Override + default void A_Fall(mobj_t actor) { + // actor is on ground, it can be walked over + actor.flags &= ~MF_SOLID; + + // So change this if corpse objects + // are meant to be obstacles. + } + + /** + * Causes object to move and perform obs. + * Can only be called through the Actions dispatcher. + * + * @param mobj + */ + // + //P_MobjThinker + // + default void P_MobjThinker(mobj_t mobj) { + // momentum movement + if (mobj.momx != 0 || mobj.momy != 0 || (eval(mobj.flags & MF_SKULLFLY))) { + getAttacks().XYMovement(mobj); + + if (mobj.thinkerFunction.ordinal() == 0) { + return; // mobj was removed or nop + } + } + if ((mobj.z != mobj.floorz) || mobj.momz != 0) { + mobj.ZMovement(); + + if (mobj.thinkerFunction.ordinal() == 0) { + return; // mobj was removed or nop + } + } + + // cycle through states, + // calling action functions at transitions + if (mobj.mobj_tics != -1) { + mobj.mobj_tics--; + + // you can cycle through multiple states in a tic + if (!eval(mobj.mobj_tics)) { + if (!mobj.SetMobjState(mobj.mobj_state.nextstate)) { + // freed itself + } + } + } else { + // check for nightmare respawn + if (!eval(mobj.flags & MF_COUNTKILL)) { + return; + } + + if (!DOOM().respawnmonsters) { + return; + } + + mobj.movecount++; + + if (mobj.movecount < 12 * 35) { + return; + } + + if (eval(LevelTime() & 31)) { + return; + } + + if (P_Random() > 4) { + return; + } + + getEnemies().NightmareRespawn(mobj); + } + } + + // + // A_FaceTarget + // + @Override + default void A_FaceTarget(mobj_t actor) { + if (actor.target == null) { + return; + } + + actor.flags &= ~MF_AMBUSH; + + actor.angle = sceneRenderer().PointToAngle2(actor.x, + actor.y, + actor.target.x, + actor.target.y) & BITS32; + + if (eval(actor.target.flags & MF_SHADOW)) { + actor.angle += (P_Random() - P_Random()) << 21; + } + actor.angle &= BITS32; + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Attacks.java b/doom/src/p/Actions/ActiveStates/Attacks.java new file mode 100644 index 0000000..ac1694e --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Attacks.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import static data.Defines.MELEERANGE; +import static data.Defines.MISSILERANGE; +import static data.Defines.pw_strength; +import static data.Tables.ANG180; +import static data.Tables.ANG90; +import static data.Tables.BITS32; +import data.mobjtype_t; +import data.sounds; +import defines.statenum_t; +import doom.SourceCode.angle_t; +import static doom.items.weaponinfo; +import doom.player_t; +import static doom.player_t.ps_flash; +import static m.fixed_t.FRACUNIT; +import static p.Actions.ActionsSectors.KEY_SPAWN; +import p.Actions.ActionsSectors.Spawn; +import p.mobj_t; +import static p.mobj_t.MF_JUSTATTACKED; +import p.pspdef_t; +import static utils.C2JUtils.eval; + +public interface Attacks extends Monsters { + + // plasma cells for a bfg attack + // IDEA: make action functions partially parametrizable? + int BFGCELLS = 40; + + // + // A_FirePistol + // + default void A_FirePistol(player_t player, pspdef_t psp) { + StartSound(player.mo, sounds.sfxenum_t.sfx_pistol); + + player.mo.SetMobjState(statenum_t.S_PLAY_ATK2); + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()]--; + + player.SetPsprite( + ps_flash, + weaponinfo[player.readyweapon.ordinal()].flashstate); + + getAttacks().P_BulletSlope(player.mo); + getAttacks().P_GunShot(player.mo, !eval(player.refire)); + } + + // + // A_FireShotgun + // + default void A_FireShotgun(player_t player, pspdef_t psp) { + int i; + + StartSound(player.mo, sounds.sfxenum_t.sfx_shotgn); + player.mo.SetMobjState(statenum_t.S_PLAY_ATK2); + + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()]--; + + player.SetPsprite( + ps_flash, + weaponinfo[player.readyweapon.ordinal()].flashstate); + + getAttacks().P_BulletSlope(player.mo); + + for (i = 0; i < 7; i++) { + getAttacks().P_GunShot(player.mo, false); + } + } + + /** + * A_FireShotgun2 + */ + default void A_FireShotgun2(player_t player, pspdef_t psp) { + final Spawn sp = getEnemies().contextRequire(KEY_SPAWN); + long angle; + int damage; + + StartSound(player.mo, sounds.sfxenum_t.sfx_dshtgn); + player.mo.SetMobjState(statenum_t.S_PLAY_ATK2); + + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()] -= 2; + + player.SetPsprite( + ps_flash, + weaponinfo[player.readyweapon.ordinal()].flashstate); + + getAttacks().P_BulletSlope(player.mo); + + for (int i = 0; i < 20; i++) { + damage = 5 * (P_Random() % 3 + 1); + angle = player.mo.angle; + angle += (P_Random() - P_Random()) << 19; + getAttacks().LineAttack(player.mo, angle, MISSILERANGE, sp.bulletslope + ((P_Random() - P_Random()) << 5), damage); + } + } + + // + // A_Punch + // + default void A_Punch(player_t player, pspdef_t psp) { + final Spawn sp = contextRequire(KEY_SPAWN); + @angle_t + long angle; + int damage; + int slope; + + damage = (P_Random() % 10 + 1) << 1; + + if (eval(player.powers[pw_strength])) { + damage *= 10; + } + + angle = player.mo.angle; + //angle = (angle+(RND.P_Random()-RND.P_Random())<<18)/*&BITS32*/; + // _D_: for some reason, punch didnt work until I change this + // I think it's because of "+" VS "<<" prioritys... + angle += (P_Random() - P_Random()) << 18; + slope = getAttacks().AimLineAttack(player.mo, angle, MELEERANGE); + getAttacks().LineAttack(player.mo, angle, MELEERANGE, slope, damage); + + // turn to face target + if (eval(sp.linetarget)) { + StartSound(player.mo, sounds.sfxenum_t.sfx_punch); + player.mo.angle = sceneRenderer().PointToAngle2( + player.mo.x, + player.mo.y, + sp.linetarget.x, + sp.linetarget.y + ) & BITS32; + } + } + + // + // A_Saw + // + default void A_Saw(player_t player, pspdef_t psp) { + final Spawn sp = contextRequire(KEY_SPAWN); + @angle_t + long angle; + int damage; + int slope; + + damage = 2 * (P_Random() % 10 + 1); + angle = player.mo.angle; + angle += (P_Random() - P_Random()) << 18; + angle &= BITS32; + + // use meleerange + 1 se the puff doesn't skip the flash + slope = getAttacks().AimLineAttack(player.mo, angle, MELEERANGE + 1); + getAttacks().LineAttack(player.mo, angle, MELEERANGE + 1, slope, damage); + + if (!eval(sp.linetarget)) { + StartSound(player.mo, sounds.sfxenum_t.sfx_sawful); + return; + } + StartSound(player.mo, sounds.sfxenum_t.sfx_sawhit); + + // turn to face target + angle = sceneRenderer().PointToAngle2(player.mo.x, player.mo.y, + sp.linetarget.x, sp.linetarget.y) & BITS32; + /* FIXME: this comparison is going to fail.... or not? + If e.g. angle = 359 degrees (which will be mapped to a small negative number), + and player.mo.angle = 160 degrees (a large, positive value), the result will be a + large negative value, which will still be "greater" than ANG180. + + It seems that *differences* between angles will always compare correctly, but + not direct inequalities. + + */ + + // Yet another screwy place where unsigned BAM angles are used as SIGNED comparisons. + long dangle = (angle - player.mo.angle); + dangle &= BITS32; + if (dangle > ANG180) { + if ((int) dangle < -ANG90 / 20) { + player.mo.angle = angle + ANG90 / 21; + } else { + player.mo.angle -= ANG90 / 20; + } + } else { + if (dangle > ANG90 / 20) { + player.mo.angle = angle - ANG90 / 21; + } else { + player.mo.angle += ANG90 / 20; + } + } + player.mo.angle &= BITS32; + player.mo.flags |= MF_JUSTATTACKED; + } + + // + // A_FireMissile + // + default void A_FireMissile(player_t player, pspdef_t psp) { + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()]--; + getAttacks().SpawnPlayerMissile(player.mo, mobjtype_t.MT_ROCKET); + } + + // + // A_FireBFG + // + default void A_FireBFG(player_t player, pspdef_t psp) { + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()] -= BFGCELLS; + getAttacks().SpawnPlayerMissile(player.mo, mobjtype_t.MT_BFG); + } + + // + // A_FireCGun + // + default void A_FireCGun(player_t player, pspdef_t psp) { + // For convenience. + int readyweap = player.readyweapon.ordinal(); + int flashstate = weaponinfo[readyweap].flashstate.ordinal(); + int current_state = psp.state.id; + + StartSound(player.mo, sounds.sfxenum_t.sfx_pistol); + if (!eval(player.ammo[weaponinfo[readyweap].ammo.ordinal()])) { + return; + } + + player.mo.SetMobjState(statenum_t.S_PLAY_ATK2); + player.ammo[weaponinfo[readyweap].ammo.ordinal()]--; + + // MAES: Code to alternate between two different gun flashes + // needed a clear rewrite, as it was way too messy. + // We know that the flash states are a certain amount away from + // the firing states. This amount is two frames. + player.SetPsprite(ps_flash, statenum_t.values()[flashstate + current_state - statenum_t.S_CHAIN1.ordinal()] + ); + + getAttacks().P_BulletSlope(player.mo); + getAttacks().P_GunShot(player.mo, !eval(player.refire)); + } + + // + // A_FirePlasma + // + default void A_FirePlasma(player_t player, pspdef_t psp) { + player.ammo[weaponinfo[player.readyweapon.ordinal()].ammo.ordinal()]--; + + player.SetPsprite( + ps_flash, + weaponinfo[player.readyweapon.ordinal()].flashstate); + + getAttacks().SpawnPlayerMissile(player.mo, mobjtype_t.MT_PLASMA); + } + + default void A_XScream(mobj_t actor) { + StartSound(actor, sounds.sfxenum_t.sfx_slop); + } + + default void A_Pain(mobj_t actor) { + if (actor.info.painsound != null) { + StartSound(actor, actor.info.painsound); + } + } + + // + // A_Explode + // + default void A_Explode(mobj_t thingy) { + getAttacks().RadiusAttack(thingy, thingy.target, 128); + } + + // + // A_BFGSpray + // Spawn a BFG explosion on every monster in view + // + default void A_BFGSpray(mobj_t mo) { + final Spawn sp = contextRequire(KEY_SPAWN); + + int damage; + long an; // angle_t + + // offset angles from its attack angle + for (int i = 0; i < 40; i++) { + an = (mo.angle - ANG90 / 2 + ANG90 / 40 * i) & BITS32; + + // mo.target is the originator (player) + // of the missile + getAttacks().AimLineAttack(mo.target, an, 16 * 64 * FRACUNIT); + + if (!eval(sp.linetarget)) { + continue; + } + + getEnemies().SpawnMobj(sp.linetarget.x, sp.linetarget.y, sp.linetarget.z + (sp.linetarget.height >> 2), mobjtype_t.MT_EXTRABFG); + + damage = 0; + for (int j = 0; j < 15; j++) { + damage += (P_Random() & 7) + 1; + } + + getEnemies().DamageMobj(sp.linetarget, mo.target, mo.target, damage); + } + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Bosses.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Bosses.java new file mode 100644 index 0000000..c236c5f --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Bosses.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import static data.Limits.MAXPLAYERS; +import data.mobjtype_t; +import doom.DoomMain; +import doom.thinker_t; +import p.Actions.ActionTrait; +import p.ActiveStates; +import p.floor_e; +import p.mobj_t; +import p.vldoor_e; +import rr.line_t; + +public interface Bosses extends ActionTrait { + + void A_Fall(mobj_t mo); + + /** + * A_BossDeath + * Possibly trigger special effects + * if on first boss level + * + * TODO: find out how Plutonia/TNT does cope with this. + * Special clauses? + * + */ + default void A_BossDeath(mobj_t mo) { + final DoomMain D = DOOM(); + thinker_t th; + mobj_t mo2; + line_t junk = new line_t(); + int i; + + if (D.isCommercial()) { + if (D.gamemap != 7) { + return; + } + + if ((mo.type != mobjtype_t.MT_FATSO) + && (mo.type != mobjtype_t.MT_BABY)) { + return; + } + } else { + switch (D.gameepisode) { + case 1: + if (D.gamemap != 8) { + return; + } + + if (mo.type != mobjtype_t.MT_BRUISER) { + return; + } + break; + + case 2: + if (D.gamemap != 8) { + return; + } + + if (mo.type != mobjtype_t.MT_CYBORG) { + return; + } + break; + + case 3: + if (D.gamemap != 8) { + return; + } + + if (mo.type != mobjtype_t.MT_SPIDER) { + return; + } + + break; + + case 4: + switch (D.gamemap) { + case 6: + if (mo.type != mobjtype_t.MT_CYBORG) { + return; + } + break; + + case 8: + if (mo.type != mobjtype_t.MT_SPIDER) { + return; + } + break; + + default: + return; + } + break; + + default: + if (D.gamemap != 8) { + return; + } + break; + } + + } + + // make sure there is a player alive for victory + for (i = 0; i < MAXPLAYERS; i++) { + if (D.playeringame[i] && D.players[i].health[0] > 0) { + break; + } + } + + if (i == MAXPLAYERS) { + return; // no one left alive, so do not end game + } + // scan the remaining thinkers to see + // if all bosses are dead + for (th = getThinkerCap().next; th != getThinkerCap(); th = th.next) { + if (th.thinkerFunction != ActiveStates.P_MobjThinker) { + continue; + } + + mo2 = (mobj_t) th; + if (mo2 != mo + && mo2.type == mo.type + && mo2.health > 0) { + // other boss not dead + return; + } + } + + // victory! + if (D.isCommercial()) { + if (D.gamemap == 7) { + if (mo.type == mobjtype_t.MT_FATSO) { + junk.tag = 666; + getThinkers().DoFloor(junk, floor_e.lowerFloorToLowest); + return; + } + + if (mo.type == mobjtype_t.MT_BABY) { + junk.tag = 667; + getThinkers().DoFloor(junk, floor_e.raiseToTexture); + return; + } + } + } else { + switch (D.gameepisode) { + case 1: + junk.tag = 666; + getThinkers().DoFloor(junk, floor_e.lowerFloorToLowest); + return; + + case 4: + switch (D.gamemap) { + case 6: + junk.tag = 666; + getThinkers().DoDoor(junk, vldoor_e.blazeOpen); + return; + + case 8: + junk.tag = 666; + getThinkers().DoFloor(junk, floor_e.lowerFloorToLowest); + return; + } + } + } + + D.ExitLevel(); + } + + default void A_KeenDie(mobj_t mo) { + thinker_t th; + mobj_t mo2; + line_t junk = new line_t(); // MAES: fixed null 21/5/2011 + + A_Fall(mo); + + // scan the remaining thinkers + // to see if all Keens are dead + for (th = getThinkerCap().next; th != getThinkerCap(); th = th.next) { + if (th.thinkerFunction != ActiveStates.P_MobjThinker) { + continue; + } + + mo2 = (mobj_t) th; + if (mo2 != mo + && mo2.type == mo.type + && mo2.health > 0) { + // other Keen not dead + return; + } + } + + junk.tag = 666; + getThinkers().DoDoor(junk, vldoor_e.open); + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Demonspawns.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Demonspawns.java new file mode 100644 index 0000000..04ee5e2 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Demonspawns.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import data.mobjtype_t; +import data.sounds; +import p.Actions.ActionTrait; +import p.mobj_t; + +public interface Demonspawns extends ActionTrait { + + void A_FaceTarget(mobj_t actor); + + // + // A_TroopAttack + // + default void A_TroopAttack(mobj_t actor) { + int damage; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + if (getEnemies().CheckMeleeRange(actor)) { + StartSound(actor, sounds.sfxenum_t.sfx_claw); + damage = (P_Random() % 8 + 1) * 3; + getAttacks().DamageMobj(actor.target, actor, actor, damage); + return; + } + + // launch a missile + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_TROOPSHOT); + } + + default void A_SargAttack(mobj_t actor) { + int damage; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + if (getEnemies().CheckMeleeRange(actor)) { + damage = ((P_Random() % 10) + 1) * 4; + getAttacks().DamageMobj(actor.target, actor, actor, damage); + } + } + + default void A_HeadAttack(mobj_t actor) { + int damage; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + if (getEnemies().CheckMeleeRange(actor)) { + damage = (P_Random() % 6 + 1) * 10; + getAttacks().DamageMobj(actor.target, actor, actor, damage); + return; + } + + // launch a missile + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_HEADSHOT); + } + + default void A_CyberAttack(mobj_t actor) { + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_ROCKET); + } + + default void A_BruisAttack(mobj_t actor) { + int damage; + + if (actor.target == null) { + return; + } + + if (getEnemies().CheckMeleeRange(actor)) { + StartSound(actor, sounds.sfxenum_t.sfx_claw); + damage = (P_Random() % 8 + 1) * 10; + getAttacks().DamageMobj(actor.target, actor, actor, damage); + return; + } + + // launch a missile + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_BRUISERSHOT); + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/HorrendousVisages.java b/doom/src/p/Actions/ActiveStates/MonsterStates/HorrendousVisages.java new file mode 100644 index 0000000..81382e0 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/HorrendousVisages.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import data.Limits; +import data.mobjtype_t; +import data.sounds; +import defines.skill_t; +import defines.statenum_t; +import doom.thinker_t; +import static m.fixed_t.FRACUNIT; +import p.Actions.ActiveStates.Sounds; +import p.ActiveStates; +import p.mobj_t; +import utils.TraitFactory.ContextKey; + +public interface HorrendousVisages extends Sounds { + + ContextKey KEY_BRAIN = ACTION_KEY_CHAIN.newKey(HorrendousVisages.class, Brain::new); + + final class Brain { + + // Brain status + mobj_t[] braintargets = new mobj_t[Limits.NUMBRAINTARGETS]; + int numbraintargets; + int braintargeton; + int easy = 0; + } + + default void A_BrainAwake(mobj_t mo) { + final Brain brain = contextRequire(KEY_BRAIN); + thinker_t thinker; + mobj_t m; + + // find all the target spots + brain.numbraintargets = 0; + brain.braintargeton = 0; + + //thinker = obs.thinkercap.next; + for (thinker = getThinkerCap().next; thinker != getThinkerCap(); thinker = thinker.next) { + if (thinker.thinkerFunction != ActiveStates.P_MobjThinker) { + continue; // not a mobj + } + m = (mobj_t) thinker; + + if (m.type == mobjtype_t.MT_BOSSTARGET) { + brain.braintargets[brain.numbraintargets] = m; + brain.numbraintargets++; + } + } + + StartSound(null, sounds.sfxenum_t.sfx_bossit); + } + + default void A_BrainScream(mobj_t mo) { + int x; + int y; + int z; + mobj_t th; + + for (x = mo.x - 196 * FRACUNIT; x < mo.x + 320 * FRACUNIT; x += FRACUNIT * 8) { + y = mo.y - 320 * FRACUNIT; + z = 128 + P_Random() * 2 * FRACUNIT; + th = getEnemies().SpawnMobj(x, y, z, mobjtype_t.MT_ROCKET); + th.momz = P_Random() * 512; + + th.SetMobjState(statenum_t.S_BRAINEXPLODE1); + + th.mobj_tics -= P_Random() & 7; + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + } + + StartSound(null, sounds.sfxenum_t.sfx_bosdth); + } + + default void A_BrainExplode(mobj_t mo) { + int x; + int y; + int z; + mobj_t th; + + x = mo.x + (P_Random() - P_Random()) * 2048; + y = mo.y; + z = 128 + P_Random() * 2 * FRACUNIT; + th = getEnemies().SpawnMobj(x, y, z, mobjtype_t.MT_ROCKET); + th.momz = P_Random() * 512; + + th.SetMobjState(statenum_t.S_BRAINEXPLODE1); + + th.mobj_tics -= P_Random() & 7; + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + } + + default void A_BrainDie(mobj_t mo) { + DOOM().ExitLevel(); + } + + default void A_BrainSpit(mobj_t mo) { + final Brain brain = contextRequire(KEY_BRAIN); + mobj_t targ; + mobj_t newmobj; + + brain.easy ^= 1; + if (getGameSkill().ordinal() <= skill_t.sk_easy.ordinal() && (brain.easy == 0)) { + return; + } + + // shoot a cube at current target + targ = brain.braintargets[brain.braintargeton]; + + // Load-time fix: awake on zero numbrain targets, if A_BrainSpit is called. + if (brain.numbraintargets == 0) { + A_BrainAwake(mo); + return; + } + brain.braintargeton = (brain.braintargeton + 1) % brain.numbraintargets; + + // spawn brain missile + newmobj = getAttacks().SpawnMissile(mo, targ, mobjtype_t.MT_SPAWNSHOT); + newmobj.target = targ; + newmobj.reactiontime = ((targ.y - mo.y) / newmobj.momy) / newmobj.mobj_state.tics; + + StartSound(null, sounds.sfxenum_t.sfx_bospit); + } + + @Override + default void A_SpawnFly(mobj_t mo) { + mobj_t newmobj; + mobj_t fog; + mobj_t targ; + int r; + mobjtype_t type; + + if (--mo.reactiontime != 0) { + return; // still flying + } + targ = mo.target; + + // First spawn teleport fog. + fog = getEnemies().SpawnMobj(targ.x, targ.y, targ.z, mobjtype_t.MT_SPAWNFIRE); + StartSound(fog, sounds.sfxenum_t.sfx_telept); + + // Randomly select monster to spawn. + r = P_Random(); + + // Probability distribution (kind of :), + // decreasing likelihood. + if (r < 50) { + type = mobjtype_t.MT_TROOP; + } else if (r < 90) { + type = mobjtype_t.MT_SERGEANT; + } else if (r < 120) { + type = mobjtype_t.MT_SHADOWS; + } else if (r < 130) { + type = mobjtype_t.MT_PAIN; + } else if (r < 160) { + type = mobjtype_t.MT_HEAD; + } else if (r < 162) { + type = mobjtype_t.MT_VILE; + } else if (r < 172) { + type = mobjtype_t.MT_UNDEAD; + } else if (r < 192) { + type = mobjtype_t.MT_BABY; + } else if (r < 222) { + type = mobjtype_t.MT_FATSO; + } else if (r < 246) { + type = mobjtype_t.MT_KNIGHT; + } else { + type = mobjtype_t.MT_BRUISER; + } + + newmobj = getEnemies().SpawnMobj(targ.x, targ.y, targ.z, type); + if (getEnemies().LookForPlayers(newmobj, true)) { + newmobj.SetMobjState(newmobj.info.seestate); + } + + // telefrag anything in this spot + getAttacks().TeleportMove(newmobj, newmobj.x, newmobj.y); + + // remove self (i.e., cube). + getEnemies().RemoveMobj(mo); + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Mancubi.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Mancubi.java new file mode 100644 index 0000000..ecaff33 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Mancubi.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import data.Tables; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.mobjtype_t; +import data.sounds; +import static m.fixed_t.FixedMul; +import p.Actions.ActionTrait; +import p.mobj_t; + +public interface Mancubi extends ActionTrait { + + static final long FATSPREAD = Tables.ANG90 / 8; + + void A_FaceTarget(mobj_t actor); + + // + // Mancubus attack, + // firing three missiles (bruisers) + // in three different directions? + // Doesn't look like it. + // + default void A_FatRaise(mobj_t actor) { + A_FaceTarget(actor); + StartSound(actor, sounds.sfxenum_t.sfx_manatk); + } + + default void A_FatAttack1(mobj_t actor) { + mobj_t mo; + int an; + + A_FaceTarget(actor); + // Change direction to ... + actor.angle += FATSPREAD; + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + + mo = getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + mo.angle += FATSPREAD; + an = Tables.toBAMIndex(mo.angle); + mo.momx = FixedMul(mo.info.speed, finecosine[an]); + mo.momy = FixedMul(mo.info.speed, finesine[an]); + } + + default void A_FatAttack2(mobj_t actor) { + mobj_t mo; + int an; + + A_FaceTarget(actor); + // Now here choose opposite deviation. + actor.angle -= FATSPREAD; + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + + mo = getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + mo.angle -= FATSPREAD * 2; + an = Tables.toBAMIndex(mo.angle); + mo.momx = FixedMul(mo.info.speed, finecosine[an]); + mo.momy = FixedMul(mo.info.speed, finesine[an]); + } + + default void A_FatAttack3(mobj_t actor) { + mobj_t mo; + int an; + + A_FaceTarget(actor); + + mo = getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + mo.angle -= FATSPREAD / 2; + an = Tables.toBAMIndex(mo.angle); + mo.momx = FixedMul(mo.info.speed, finecosine[an]); + mo.momy = FixedMul(mo.info.speed, finesine[an]); + + mo = getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_FATSHOT); + mo.angle += FATSPREAD / 2; + an = Tables.toBAMIndex(mo.angle); + mo.momx = FixedMul(mo.info.speed, finecosine[an]); + mo.momy = FixedMul(mo.info.speed, finesine[an]); + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/PainsSouls.java b/doom/src/p/Actions/ActiveStates/MonsterStates/PainsSouls.java new file mode 100644 index 0000000..6be5cb5 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/PainsSouls.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import static data.Limits.MAXSKULLS; +import data.Tables; +import static data.Tables.ANG180; +import static data.Tables.ANG270; +import static data.Tables.ANG90; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.mobjinfo; +import data.mobjtype_t; +import doom.SourceCode.angle_t; +import doom.SourceCode.fixed_t; +import doom.thinker_t; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import p.Actions.ActionTrait; +import p.ActiveStates; +import static p.MapUtils.AproxDistance; +import p.mobj_t; +import static p.mobj_t.MF_SKULLFLY; + +public interface PainsSouls extends ActionTrait { + + static final int SKULLSPEED = 20 * m.fixed_t.MAPFRACUNIT; + + void A_FaceTarget(mobj_t actor); + + void A_Fall(mobj_t actor); + + /** + * SkullAttack + * Fly at the player like a missile. + */ + default void A_SkullAttack(mobj_t actor) { + mobj_t dest; + int an; + int dist; + + if (actor.target == null) { + return; + } + + dest = actor.target; + actor.flags |= MF_SKULLFLY; + + StartSound(actor, actor.info.attacksound); + A_FaceTarget(actor); + an = Tables.toBAMIndex(actor.angle); + actor.momx = FixedMul(SKULLSPEED, finecosine[an]); + actor.momy = FixedMul(SKULLSPEED, finesine[an]); + dist = AproxDistance(dest.x - actor.x, dest.y - actor.y); + dist /= SKULLSPEED; + + if (dist < 1) { + dist = 1; + } + actor.momz = (dest.z + (dest.height >> 1) - actor.z) / dist; + } + + /** + * A_PainShootSkull + * Spawn a lost soul and launch it at the target + * It's not a valid callback like the others, actually. + * No idea if some DEH patch does use it to cause + * mayhem though. + * + */ + default void A_PainShootSkull(mobj_t actor, Long angle) { + @fixed_t + int x, y, z; + + mobj_t newmobj; + @angle_t + int an; + int prestep; + int count; + thinker_t currentthinker; + + // count total number of skull currently on the level + count = 0; + + currentthinker = getThinkerCap().next; + while (currentthinker != getThinkerCap()) { + if ((currentthinker.thinkerFunction == ActiveStates.P_MobjThinker) + && ((mobj_t) currentthinker).type == mobjtype_t.MT_SKULL) { + count++; + } + currentthinker = currentthinker.next; + } + + // if there are allready 20 skulls on the level, + // don't spit another one + if (count > MAXSKULLS) { + return; + } + + // okay, there's playe for another one + an = Tables.toBAMIndex(angle); + + prestep + = 4 * FRACUNIT + + 3 * (actor.info.radius + mobjinfo[mobjtype_t.MT_SKULL.ordinal()].radius) / 2; + + x = actor.x + FixedMul(prestep, finecosine[an]); + y = actor.y + FixedMul(prestep, finesine[an]); + z = actor.z + 8 * FRACUNIT; + + newmobj = getAttacks().SpawnMobj(x, y, z, mobjtype_t.MT_SKULL); + + // Check for movements. + if (!getAttacks().TryMove(newmobj, newmobj.x, newmobj.y)) { + // kill it immediately + getAttacks().DamageMobj(newmobj, actor, actor, 10000); + return; + } + + newmobj.target = actor.target; + A_SkullAttack(newmobj); + } + + // + // A_PainAttack + // Spawn a lost soul and launch it at the target + // + default void A_PainAttack(mobj_t actor) { + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + A_PainShootSkull(actor, actor.angle); + } + + default void A_PainDie(mobj_t actor) { + A_Fall(actor); + A_PainShootSkull(actor, actor.angle + ANG90); + A_PainShootSkull(actor, actor.angle + ANG180); + A_PainShootSkull(actor, actor.angle + ANG270); + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Skels.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Skels.java new file mode 100644 index 0000000..0f9e2f3 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Skels.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import data.Tables; +import static data.Tables.ANG180; +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.mobjtype_t; +import data.sounds; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static m.fixed_t.MAPFRACUNIT; +import p.Actions.ActionTrait; +import static p.MapUtils.AproxDistance; +import p.mobj_t; +import static utils.C2JUtils.eval; + +public interface Skels extends ActionTrait { + + int TRACEANGLE = 0xC_00_00_00; + + // + // A_SkelMissile + // + default void A_SkelMissile(mobj_t actor) { + mobj_t mo; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + actor.z += 16 * FRACUNIT; // so missile spawns higher + mo = getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_TRACER); + actor.z -= 16 * FRACUNIT; // back to normal + + mo.x += mo.momx; + mo.y += mo.momy; + mo.tracer = actor.target; + } + + default void A_SkelWhoosh(mobj_t actor) { + if (actor.target == null) { + return; + } + A_FaceTarget(actor); + StartSound(actor, sounds.sfxenum_t.sfx_skeswg); + } + + default void A_SkelFist(mobj_t actor) { + int damage; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + + if (getEnemies().CheckMeleeRange(actor)) { + damage = ((P_Random() % 10) + 1) * 6; + StartSound(actor, sounds.sfxenum_t.sfx_skepch); + getAttacks().DamageMobj(actor.target, actor, actor, damage); + } + } + + default void A_Tracer(mobj_t actor) { + long exact; //angle_t + int dist, slope; // fixed + mobj_t dest; + mobj_t th; + if (eval(DOOM().gametic & 3)) { + return; + } + // spawn a puff of smoke behind the rocket + getAttacks().SpawnPuff(actor.x, actor.y, actor.z); + th = getEnemies().SpawnMobj(actor.x - actor.momx, actor.y - actor.momy, actor.z, mobjtype_t.MT_SMOKE); + th.momz = MAPFRACUNIT; + th.mobj_tics -= P_Random() & 3; + if (th.mobj_tics < 1) { + th.mobj_tics = 1; + } + + // adjust direction + dest = actor.tracer; + if (dest == null || dest.health <= 0) { + return; + } + + // change angle + exact = sceneRenderer().PointToAngle2(actor.x, actor.y, dest.x, dest.y) & BITS32; + + // MAES: let's analyze the logic here... + // So exact is the angle between the missile and its target. + if (exact != actor.angle) { // missile is already headed there dead-on. + if (exact - actor.angle > ANG180) { + actor.angle -= TRACEANGLE; + actor.angle &= BITS32; + if (((exact - actor.angle) & BITS32) < ANG180) { + actor.angle = exact; + } + } else { + actor.angle += TRACEANGLE; + actor.angle &= BITS32; + if (((exact - actor.angle) & BITS32) > ANG180) { + actor.angle = exact; + } + } + } + // MAES: fixed and sped up. + int exact2 = Tables.toBAMIndex(actor.angle); + actor.momx = FixedMul(actor.info.speed, finecosine[exact2]); + actor.momy = FixedMul(actor.info.speed, finesine[exact2]); + // change slope + dist = AproxDistance(dest.x - actor.x, dest.y - actor.y); + dist /= actor.info.speed; + if (dist < 1) { + dist = 1; + } + slope = (dest.z + 40 * FRACUNIT - actor.z) / dist; + if (slope < actor.momz) { + actor.momz -= FRACUNIT / 8; + } else { + actor.momz += FRACUNIT / 8; + } + } + + public void A_FaceTarget(mobj_t actor); + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Spiders.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Spiders.java new file mode 100644 index 0000000..59a0130 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Spiders.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import data.mobjtype_t; +import p.Actions.ActionTrait; +import p.mobj_t; + +public interface Spiders extends ActionTrait { + + void A_FaceTarget(mobj_t actor); + + default void A_SpidRefire(mobj_t actor) { + // keep firing unless target got out of sight + A_FaceTarget(actor); + + if (P_Random() < 10) { + return; + } + + if (actor.target == null || actor.target.health <= 0 || !getEnemies().CheckSight(actor, actor.target)) { + actor.SetMobjState(actor.info.seestate); + } + } + + default void A_BspiAttack(mobj_t actor) { + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + + // launch a missile + getAttacks().SpawnMissile(actor, actor.target, mobjtype_t.MT_ARACHPLAZ); + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Viles.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Viles.java new file mode 100644 index 0000000..a0ad731 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Viles.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import static data.Limits.MAXRADIUS; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import data.mobjinfo_t; +import data.mobjtype_t; +import data.sounds; +import defines.statenum_t; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static m.fixed_t.MAPFRACUNIT; +import p.AbstractLevelLoader; +import p.Actions.ActionTrait; +import p.Actions.ActionsAttacks; +import p.Actions.ActionsAttacks.Attacks; +import static p.Actions.ActionsAttacks.KEY_ATTACKS; +import static p.ChaseDirections.DI_NODIR; +import static p.ChaseDirections.xspeed; +import static p.ChaseDirections.yspeed; +import p.mobj_t; + +public interface Viles extends ActionTrait { + + void A_FaceTarget(mobj_t actor); + + void A_Chase(mobj_t actor); + + // + // A_VileChase + // Check for ressurecting a body + // + default void A_VileChase(mobj_t actor) { + final AbstractLevelLoader ll = levelLoader(); + final ActionsAttacks actionsAttacks = getAttacks(); + final Attacks att = actionsAttacks.contextRequire(KEY_ATTACKS); + + int xl; + int xh; + int yl; + int yh; + + int bx; + int by; + + mobjinfo_t info; + mobj_t temp; + + if (actor.movedir != DI_NODIR) { + // check for corpses to raise + att.vileTryX = actor.x + actor.info.speed * xspeed[actor.movedir]; + att.vileTryY = actor.y + actor.info.speed * yspeed[actor.movedir]; + + xl = ll.getSafeBlockX(att.vileTryX - ll.bmaporgx - MAXRADIUS * 2); + xh = ll.getSafeBlockX(att.vileTryX - ll.bmaporgx + MAXRADIUS * 2); + yl = ll.getSafeBlockY(att.vileTryY - ll.bmaporgy - MAXRADIUS * 2); + yh = ll.getSafeBlockY(att.vileTryY - ll.bmaporgy + MAXRADIUS * 2); + + att.vileObj = actor; + for (bx = xl; bx <= xh; bx++) { + for (by = yl; by <= yh; by++) { + // Call PIT_VileCheck to check + // whether object is a corpse + // that can be raised. + if (!BlockThingsIterator(bx, by, actionsAttacks::VileCheck)) { + // got one! + temp = actor.target; + actor.target = att.vileCorpseHit; + A_FaceTarget(actor); + actor.target = temp; + + actor.SetMobjState(statenum_t.S_VILE_HEAL1); + StartSound(att.vileCorpseHit, sounds.sfxenum_t.sfx_slop); + info = att.vileCorpseHit.info; + + att.vileCorpseHit.SetMobjState(info.raisestate); + att.vileCorpseHit.height <<= 2; + att.vileCorpseHit.flags = info.flags; + att.vileCorpseHit.health = info.spawnhealth; + att.vileCorpseHit.target = null; + + return; + } + } + } + } + + // Return to normal attack. + A_Chase(actor); + } + + // + // A_VileStart + // + default void A_VileStart(mobj_t actor) { + StartSound(actor, sounds.sfxenum_t.sfx_vilatk); + } + + // + // A_Fire + // Keep fire in front of player unless out of sight + // + default void A_StartFire(mobj_t actor) { + StartSound(actor, sounds.sfxenum_t.sfx_flamst); + A_Fire(actor); + } + + default void A_FireCrackle(mobj_t actor) { + StartSound(actor, sounds.sfxenum_t.sfx_flame); + A_Fire(actor); + } + + default void A_Fire(mobj_t actor) { + mobj_t dest; + //long an; + + dest = actor.tracer; + if (dest == null) { + return; + } + + // don't move it if the vile lost sight + if (!getEnemies().CheckSight(actor.target, dest)) { + return; + } + + // an = dest.angle >>> ANGLETOFINESHIFT; + getAttacks().UnsetThingPosition(actor); + actor.x = dest.x + FixedMul(24 * FRACUNIT, finecosine(dest.angle)); + actor.y = dest.y + FixedMul(24 * FRACUNIT, finesine(dest.angle)); + actor.z = dest.z; + SetThingPosition(actor); + } + + // + // A_VileTarget + // Spawn the hellfire + // + default void A_VileTarget(mobj_t actor) { + mobj_t fog; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + + fog = getEnemies().SpawnMobj(actor.target.x, actor.target.y, actor.target.z, mobjtype_t.MT_FIRE); + + actor.tracer = fog; + fog.target = actor; + fog.tracer = actor.target; + A_Fire(fog); + } + + // + // A_VileAttack + // + default void A_VileAttack(mobj_t actor) { + mobj_t fire; + //int an; + + if (actor.target == null) { + return; + } + + A_FaceTarget(actor); + + if (!getEnemies().CheckSight(actor, actor.target)) { + return; + } + + StartSound(actor, sounds.sfxenum_t.sfx_barexp); + getAttacks().DamageMobj(actor.target, actor, actor, 20); + actor.target.momz = 1000 * MAPFRACUNIT / actor.target.info.mass; + + // an = actor.angle >> ANGLETOFINESHIFT; + fire = actor.tracer; + + if (fire == null) { + return; + } + + // move the fire between the vile and the player + fire.x = actor.target.x - FixedMul(24 * FRACUNIT, finecosine(actor.angle)); + fire.y = actor.target.y - FixedMul(24 * FRACUNIT, finesine(actor.angle)); + getAttacks().RadiusAttack(fire, actor, 70); + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/MonsterStates/Zombies.java b/doom/src/p/Actions/ActiveStates/MonsterStates/Zombies.java new file mode 100644 index 0000000..e7bb6a8 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/MonsterStates/Zombies.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates.MonsterStates; + +import static data.Defines.MISSILERANGE; +import data.sounds; +import p.Actions.ActionTrait; +import p.mobj_t; + +public interface Zombies extends ActionTrait { + + void A_FaceTarget(mobj_t actor); + + // + // A_PosAttack + // + default void A_PosAttack(mobj_t actor) { + int angle; + int damage; + int slope; + + if (actor.target == null) { + return; + } + A_FaceTarget(actor); + angle = (int) actor.angle; + slope = getAttacks().AimLineAttack(actor, angle, MISSILERANGE); + + StartSound(actor, sounds.sfxenum_t.sfx_pistol); + angle += (P_Random() - P_Random()) << 20; + damage = ((P_Random() % 5) + 1) * 3; + getAttacks().LineAttack(actor, angle, MISSILERANGE, slope, damage); + } + + default void A_SPosAttack(mobj_t actor) { + int i; + long angle; + long bangle; + int damage; + int slope; + + if (actor.target == null) { + return; + } + + StartSound(actor, sounds.sfxenum_t.sfx_shotgn); + A_FaceTarget(actor); + bangle = actor.angle; + slope = getAttacks().AimLineAttack(actor, bangle, MISSILERANGE); + + for (i = 0; i < 3; i++) { + angle = bangle + ((P_Random() - P_Random()) << 20); + damage = ((P_Random() % 5) + 1) * 3; + getAttacks().LineAttack(actor, angle, MISSILERANGE, slope, damage); + } + } + + default void A_CPosAttack(mobj_t actor) { + long angle; + long bangle; + int damage; + int slope; + + if (actor.target == null) { + return; + } + + StartSound(actor, sounds.sfxenum_t.sfx_shotgn); + A_FaceTarget(actor); + bangle = actor.angle; + slope = getAttacks().AimLineAttack(actor, bangle, MISSILERANGE); + + angle = bangle + ((P_Random() - P_Random()) << 20); + damage = ((P_Random() % 5) + 1) * 3; + getAttacks().LineAttack(actor, angle, MISSILERANGE, slope, damage); + } + + default void A_CPosRefire(mobj_t actor) { + // keep firing unless target got out of sight + A_FaceTarget(actor); + + if (P_Random() < 40) { + return; + } + + if (actor.target == null || actor.target.health <= 0 || !getEnemies().CheckSight(actor, actor.target)) { + actor.SetMobjState(actor.info.seestate); + } + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Monsters.java b/doom/src/p/Actions/ActiveStates/Monsters.java new file mode 100644 index 0000000..1eafebc --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Monsters.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import p.Actions.ActiveStates.MonsterStates.Bosses; +import p.Actions.ActiveStates.MonsterStates.Demonspawns; +import p.Actions.ActiveStates.MonsterStates.HorrendousVisages; +import p.Actions.ActiveStates.MonsterStates.Mancubi; +import p.Actions.ActiveStates.MonsterStates.PainsSouls; +import p.Actions.ActiveStates.MonsterStates.Skels; +import p.Actions.ActiveStates.MonsterStates.Spiders; +import p.Actions.ActiveStates.MonsterStates.Viles; +import p.Actions.ActiveStates.MonsterStates.Zombies; + +/** + * Include all from Monsters package + * + * @author Good Sign + */ +public interface Monsters extends + Bosses, + Demonspawns, + HorrendousVisages, + Mancubi, + PainsSouls, + Skels, + Spiders, + Viles, + Zombies { + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Sounds.java b/doom/src/p/Actions/ActiveStates/Sounds.java new file mode 100644 index 0000000..b505a06 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Sounds.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import data.mobjtype_t; +import data.sounds; +import doom.player_t; +import p.Actions.ActionTrait; +import p.mobj_t; +import p.pspdef_t; + +public interface Sounds extends ActionTrait { + + void A_Chase(mobj_t mo); + + void A_ReFire(player_t player, pspdef_t psp); + + void A_SpawnFly(mobj_t mo); + + default void A_Scream(mobj_t actor) { + int sound; + + switch (actor.info.deathsound) { + case sfx_None: + return; + + case sfx_podth1: + case sfx_podth2: + case sfx_podth3: + sound = sounds.sfxenum_t.sfx_podth1.ordinal() + P_Random() % 3; + break; + + case sfx_bgdth1: + case sfx_bgdth2: + sound = sounds.sfxenum_t.sfx_bgdth1.ordinal() + P_Random() % 2; + break; + + default: + sound = actor.info.deathsound.ordinal(); + break; + } + + // Check for bosses. + if (actor.type == mobjtype_t.MT_SPIDER + || actor.type == mobjtype_t.MT_CYBORG) { + // full volume + StartSound(null, sound); + } else { + StartSound(actor, sound); + } + } + + default void A_Hoof(mobj_t mo) { + StartSound(mo, sounds.sfxenum_t.sfx_hoof); + A_Chase(mo); + } + + // + // A_BFGsound + // + default void A_BFGsound(player_t player, pspdef_t psp) { + StartSound(player.mo, sounds.sfxenum_t.sfx_bfg); + } + + default void A_OpenShotgun2(player_t player, pspdef_t psp) { + StartSound(player.mo, sounds.sfxenum_t.sfx_dbopn); + } + + default void A_LoadShotgun2(player_t player, pspdef_t psp) { + StartSound(player.mo, sounds.sfxenum_t.sfx_dbload); + } + + default void A_CloseShotgun2(player_t player, pspdef_t psp) { + StartSound(player.mo, sounds.sfxenum_t.sfx_dbcls); + A_ReFire(player, psp); + } + + default void A_BrainPain(mobj_t mo) { + StartSound(null, sounds.sfxenum_t.sfx_bospn); + } + + default void A_Metal(mobj_t mo) { + StartSound(mo, sounds.sfxenum_t.sfx_metal); + A_Chase(mo); + } + + default void A_BabyMetal(mobj_t mo) { + StartSound(mo, sounds.sfxenum_t.sfx_bspwlk); + A_Chase(mo); + } + + // travelling cube sound + default void A_SpawnSound(mobj_t mo) { + StartSound(mo, sounds.sfxenum_t.sfx_boscub); + A_SpawnFly(mo); + } + + default void A_PlayerScream(mobj_t actor) { + // Default death sound. + sounds.sfxenum_t sound = sounds.sfxenum_t.sfx_pldeth; + + if (DOOM().isCommercial() && (actor.health < -50)) { + // IF THE PLAYER DIES + // LESS THAN -50% WITHOUT GIBBING + sound = sounds.sfxenum_t.sfx_pdiehi; + } + + StartSound(actor, sound); + } + +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Thinkers.java b/doom/src/p/Actions/ActiveStates/Thinkers.java new file mode 100644 index 0000000..c0c1520 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Thinkers.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import doom.SourceCode; +import doom.SourceCode.P_Lights; +import static doom.SourceCode.P_Lights.T_FireFlicker; +import static doom.SourceCode.P_Lights.T_Glow; +import static doom.SourceCode.P_Lights.T_LightFlash; +import doom.thinker_t; +import p.Actions.ActionTrait; +import p.Actions.ActionsLights.fireflicker_t; +import p.Actions.ActionsLights.glow_t; +import p.Actions.ActionsLights.lightflash_t; +import static p.DoorDefines.GLOWSPEED; +import p.ceiling_t; +import p.floormove_t; +import p.plat_t; +import p.slidedoor_t; +import p.strobe_t; +import p.vldoor_t; + +public interface Thinkers extends ActionTrait { + + // + // T_FireFlicker + // + @SourceCode.Exact + @P_Lights.C(T_FireFlicker) + default void T_FireFlicker(thinker_t f) { + final fireflicker_t flick = (fireflicker_t) f; + int amount; + + if (--flick.count != 0) { + return; + } + + amount = (P_Random() & 3) * 16; + + if (flick.sector.lightlevel - amount < flick.minlight) { + flick.sector.lightlevel = (short) flick.minlight; + } else { + flick.sector.lightlevel = (short) (flick.maxlight - amount); + } + + flick.count = 4; + } + + /** + * T_LightFlash + * Do flashing lights. + */ + @SourceCode.Exact + @P_Lights.C(T_LightFlash) + default void T_LightFlash(thinker_t l) { + final lightflash_t flash = (lightflash_t) l; + if (--flash.count != 0) { + return; + } + + if (flash.sector.lightlevel == flash.maxlight) { + flash.sector.lightlevel = (short) flash.minlight; + flash.count = (P_Random() & flash.mintime) + 1; + } else { + flash.sector.lightlevel = (short) flash.maxlight; + flash.count = (P_Random() & flash.maxtime) + 1; + } + } + + default void T_StrobeFlash(thinker_t s) { + ((strobe_t) s).StrobeFlash(); + } + + // + // Spawn glowing light + // + @SourceCode.Exact + @P_Lights.C(T_Glow) + default void T_Glow(thinker_t t) { + glow_t g = (glow_t) t; + switch (g.direction) { + case -1: + // DOWN + g.sector.lightlevel -= GLOWSPEED; + if (g.sector.lightlevel <= g.minlight) { + g.sector.lightlevel += GLOWSPEED; + g.direction = 1; + } + break; + + case 1: + // UP + g.sector.lightlevel += GLOWSPEED; + if (g.sector.lightlevel >= g.maxlight) { + g.sector.lightlevel -= GLOWSPEED; + g.direction = -1; + } + break; + + default: + break; + } + } + + default void T_MoveCeiling(thinker_t c) { + getThinkers().MoveCeiling((ceiling_t) c); + } + + default void T_MoveFloor(thinker_t f) { + getThinkers().MoveFloor((floormove_t) f); + } + + default void T_VerticalDoor(thinker_t v) { + getThinkers().VerticalDoor((vldoor_t) v); + } + + default void T_SlidingDoor(thinker_t door) { + getThinkers().SlidingDoor((slidedoor_t) door); + } + + default void T_PlatRaise(thinker_t p) { + getThinkers().PlatRaise((plat_t) p); + } +} \ No newline at end of file diff --git a/doom/src/p/Actions/ActiveStates/Weapons.java b/doom/src/p/Actions/ActiveStates/Weapons.java new file mode 100644 index 0000000..e81db06 --- /dev/null +++ b/doom/src/p/Actions/ActiveStates/Weapons.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 1993-1996 by id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p.Actions.ActiveStates; + +import static data.Defines.BT_ATTACK; +import static data.Defines.PST_DEAD; +import static data.Tables.FINEANGLES; +import static data.Tables.FINEMASK; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.info.states; +import data.sounds; +import defines.statenum_t; +import static doom.items.weaponinfo; +import doom.player_t; +import static doom.player_t.LOWERSPEED; +import static doom.player_t.RAISESPEED; +import static doom.player_t.WEAPONBOTTOM; +import static doom.player_t.WEAPONTOP; +import static doom.player_t.ps_flash; +import static doom.player_t.ps_weapon; +import doom.weapontype_t; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import p.pspdef_t; +import static utils.C2JUtils.eval; + +public interface Weapons extends Sounds { + + /** + * A_WeaponReady + * The player can fire the weapon + * or change to another weapon at this time. + * Follows after getting weapon up, + * or after previous attack/fire sequence. + */ + default void A_WeaponReady(player_t player, pspdef_t psp) { + statenum_t newstate; + int angle; + + // get out of attack state + if (player.mo.mobj_state == states[statenum_t.S_PLAY_ATK1.ordinal()] + || player.mo.mobj_state == states[statenum_t.S_PLAY_ATK2.ordinal()]) { + player.mo.SetMobjState(statenum_t.S_PLAY); + } + + if (player.readyweapon == weapontype_t.wp_chainsaw + && psp.state == states[statenum_t.S_SAW.ordinal()]) { + StartSound(player.mo, sounds.sfxenum_t.sfx_sawidl); + } + + // check for change + // if player is dead, put the weapon away + if (player.pendingweapon != weapontype_t.wp_nochange || !eval(player.health[0])) { + // change weapon + // (pending weapon should allready be validated) + newstate = weaponinfo[player.readyweapon.ordinal()].downstate; + player.SetPsprite(player_t.ps_weapon, newstate); + return; + } + + // check for fire + // the missile launcher and bfg do not auto fire + if (eval(player.cmd.buttons & BT_ATTACK)) { + if (!player.attackdown + || (player.readyweapon != weapontype_t.wp_missile + && player.readyweapon != weapontype_t.wp_bfg)) { + player.attackdown = true; + getEnemies().FireWeapon(player); + return; + } + } else { + player.attackdown = false; + } + + // bob the weapon based on movement speed + angle = (128 * LevelTime()) & FINEMASK; + psp.sx = FRACUNIT + FixedMul(player.bob, finecosine[angle]); + angle &= FINEANGLES / 2 - 1; + psp.sy = player_t.WEAPONTOP + FixedMul(player.bob, finesine[angle]); + } + + // + // A_Raise + // + default void A_Raise(player_t player, pspdef_t psp) { + statenum_t newstate; + + //System.out.println("Trying to raise weapon"); + //System.out.println(player.readyweapon + " height: "+psp.sy); + psp.sy -= RAISESPEED; + + if (psp.sy > WEAPONTOP) { + //System.out.println("Not on top yet, exit and repeat."); + return; + } + + psp.sy = WEAPONTOP; + + // The weapon has been raised all the way, + // so change to the ready state. + newstate = weaponinfo[player.readyweapon.ordinal()].readystate; + //System.out.println("Weapon raised, setting new state."); + + player.SetPsprite(ps_weapon, newstate); + } + + // + // A_ReFire + // The player can re-fire the weapon + // without lowering it entirely. + // + @Override + default void A_ReFire(player_t player, pspdef_t psp) { + // check for fire + // (if a weaponchange is pending, let it go through instead) + if (eval(player.cmd.buttons & BT_ATTACK) + && player.pendingweapon == weapontype_t.wp_nochange + && eval(player.health[0])) { + player.refire++; + getEnemies().FireWeapon(player); + } else { + player.refire = 0; + player.CheckAmmo(); + } + } + + // + // A_GunFlash + // + default void A_GunFlash(player_t player, pspdef_t psp) { + player.mo.SetMobjState(statenum_t.S_PLAY_ATK2); + player.SetPsprite(ps_flash, weaponinfo[player.readyweapon.ordinal()].flashstate); + } + + // + // ? + // + default void A_Light0(player_t player, pspdef_t psp) { + player.extralight = 0; + } + + default void A_Light1(player_t player, pspdef_t psp) { + player.extralight = 1; + } + + default void A_Light2(player_t player, pspdef_t psp) { + player.extralight = 2; + } + + // + // A_Lower + // Lowers current weapon, + // and changes weapon at bottom. + // + default void A_Lower(player_t player, pspdef_t psp) { + psp.sy += LOWERSPEED; + + // Is already down. + if (psp.sy < WEAPONBOTTOM) { + return; + } + + // Player is dead. + if (player.playerstate == PST_DEAD) { + psp.sy = WEAPONBOTTOM; + + // don't bring weapon back up + return; + } + + // The old weapon has been lowered off the screen, + // so change the weapon and start raising it + if (!eval(player.health[0])) { + // Player is dead, so keep the weapon off screen. + player.SetPsprite(ps_weapon, statenum_t.S_NULL); + return; + } + + player.readyweapon = player.pendingweapon; + + player.BringUpWeapon(); + } + + default void A_CheckReload(player_t player, pspdef_t psp) { + player.CheckAmmo(); + /* + if (player.ammo[am_shell]<2) + P_SetPsprite (player, ps_weapon, S_DSNR1); + */ + } + +} \ No newline at end of file diff --git a/doom/src/p/ActiveStates.java b/doom/src/p/ActiveStates.java new file mode 100644 index 0000000..4e3ec8c --- /dev/null +++ b/doom/src/p/ActiveStates.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 1993-1996 Id Software, Inc. + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package p; + +import doom.SourceCode.D_Think; +import doom.SourceCode.D_Think.actionf_t; +import doom.SourceCode.actionf_p1; +import doom.SourceCode.actionf_p2; +import doom.SourceCode.actionf_v; +import doom.player_t; +import doom.thinker_t; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * In vanilla doom there is union called actionf_t that can hold + * one of the three types: actionf_p1, actionf_v and actionf_p2 + * + * typedef union + * { + * actionf_p1 acp1; + * actionf_v acv; + * actionf_p2 acp2; + * + * } actionf_t; + * + * For those unfamiliar with C, the union can have only one value + * assigned with all the values combined solving the behavior of + * logical and of all of them) + * + * actionf_p1, actionf_v and actionf_p2 are defined as these: + * + * typedef void (*actionf_v)(); + * typedef void (*actionf_p1)( void* ); + * typedef void (*actionf_p2)( void*, void* ); + * + * As you can see, they are pointers, so they all occupy the same space + * in the union: the length of the memory pointer. + * + * Effectively, this means that you can write to any of the three fields + * the pointer to the function correspoding to the field, and + * it will completely overwrite any other function assigned in other + * two fields. Even more: the other fields will have the same pointer, + * just with wrong type. + * + * In Mocha Doom, this were addressed differently. A special helper enum + * was created to hold possible names of the functions, and they were checked + * by name, not by equality of the objects (object == object if point the same) + * assigned to one of three fields. But, not understanding the true nature + * of C's unions, in Mocha Doom all three fields were preserved and threated + * like they can hold some different information at the same time. + * + * I present hereby the solution that will both simplify the definition + * and usage of the action functions, and provide a way to achieve the + * exact same behavior as would be in C: if you assign the function, + * you will replace the old one (virtually, "all the three fields") + * and you can call any function with 0 to 2 arguments. + * + * Also to store the functions in the same place where we declare them, + * an Command pattern is implemented, requiring the function caller + * to provide himself or any sufficient class that implements the Client + * contract to provide the information needed for holding the state + * of action functions. + * + * - Good Sign 2017/04/28 + * + * Thinkers can either have one parameter of type (mobj_t), + * Or otherwise be sector specials, flickering lights etc. + * Those are atypical and need special handling. + */ +public enum ActiveStates implements ThinkerStates { + NOP(ActiveStates::nop, ThinkerConsumer.class), + A_Light0(ActionFunctions::A_Light0, PlayerSpriteConsumer.class), + A_WeaponReady(ActionFunctions::A_WeaponReady, PlayerSpriteConsumer.class), + A_Lower(ActionFunctions::A_Lower, PlayerSpriteConsumer.class), + A_Raise(ActionFunctions::A_Raise, PlayerSpriteConsumer.class), + A_Punch(ActionFunctions::A_Punch, PlayerSpriteConsumer.class), + A_ReFire(ActionFunctions::A_ReFire, PlayerSpriteConsumer.class), + A_FirePistol(ActionFunctions::A_FirePistol, PlayerSpriteConsumer.class), + A_Light1(ActionFunctions::A_Light1, PlayerSpriteConsumer.class), + A_FireShotgun(ActionFunctions::A_FireShotgun, PlayerSpriteConsumer.class), + A_Light2(ActionFunctions::A_Light2, PlayerSpriteConsumer.class), + A_FireShotgun2(ActionFunctions::A_FireShotgun2, PlayerSpriteConsumer.class), + A_CheckReload(ActionFunctions::A_CheckReload, PlayerSpriteConsumer.class), + A_OpenShotgun2(ActionFunctions::A_OpenShotgun2, PlayerSpriteConsumer.class), + A_LoadShotgun2(ActionFunctions::A_LoadShotgun2, PlayerSpriteConsumer.class), + A_CloseShotgun2(ActionFunctions::A_CloseShotgun2, PlayerSpriteConsumer.class), + A_FireCGun(ActionFunctions::A_FireCGun, PlayerSpriteConsumer.class), + A_GunFlash(ActionFunctions::A_GunFlash, PlayerSpriteConsumer.class), + A_FireMissile(ActionFunctions::A_FireMissile, PlayerSpriteConsumer.class), + A_Saw(ActionFunctions::A_Saw, PlayerSpriteConsumer.class), + A_FirePlasma(ActionFunctions::A_FirePlasma, PlayerSpriteConsumer.class), + A_BFGsound(ActionFunctions::A_BFGsound, PlayerSpriteConsumer.class), + A_FireBFG(ActionFunctions::A_FireBFG, PlayerSpriteConsumer.class), + A_BFGSpray(ActionFunctions::A_BFGSpray, MobjConsumer.class), + A_Explode(ActionFunctions::A_Explode, MobjConsumer.class), + A_Pain(ActionFunctions::A_Pain, MobjConsumer.class), + A_PlayerScream(ActionFunctions::A_PlayerScream, MobjConsumer.class), + A_Fall(ActionFunctions::A_Fall, MobjConsumer.class), + A_XScream(ActionFunctions::A_XScream, MobjConsumer.class), + A_Look(ActionFunctions::A_Look, MobjConsumer.class), + A_Chase(ActionFunctions::A_Chase, MobjConsumer.class), + A_FaceTarget(ActionFunctions::A_FaceTarget, MobjConsumer.class), + A_PosAttack(ActionFunctions::A_PosAttack, MobjConsumer.class), + A_Scream(ActionFunctions::A_Scream, MobjConsumer.class), + A_SPosAttack(ActionFunctions::A_SPosAttack, MobjConsumer.class), + A_VileChase(ActionFunctions::A_VileChase, MobjConsumer.class), + A_VileStart(ActionFunctions::A_VileStart, MobjConsumer.class), + A_VileTarget(ActionFunctions::A_VileTarget, MobjConsumer.class), + A_VileAttack(ActionFunctions::A_VileAttack, MobjConsumer.class), + A_StartFire(ActionFunctions::A_StartFire, MobjConsumer.class), + A_Fire(ActionFunctions::A_Fire, MobjConsumer.class), + A_FireCrackle(ActionFunctions::A_FireCrackle, MobjConsumer.class), + A_Tracer(ActionFunctions::A_Tracer, MobjConsumer.class), + A_SkelWhoosh(ActionFunctions::A_SkelWhoosh, MobjConsumer.class), + A_SkelFist(ActionFunctions::A_SkelFist, MobjConsumer.class), + A_SkelMissile(ActionFunctions::A_SkelMissile, MobjConsumer.class), + A_FatRaise(ActionFunctions::A_FatRaise, MobjConsumer.class), + A_FatAttack1(ActionFunctions::A_FatAttack1, MobjConsumer.class), + A_FatAttack2(ActionFunctions::A_FatAttack2, MobjConsumer.class), + A_FatAttack3(ActionFunctions::A_FatAttack3, MobjConsumer.class), + A_BossDeath(ActionFunctions::A_BossDeath, MobjConsumer.class), + A_CPosAttack(ActionFunctions::A_CPosAttack, MobjConsumer.class), + A_CPosRefire(ActionFunctions::A_CPosRefire, MobjConsumer.class), + A_TroopAttack(ActionFunctions::A_TroopAttack, MobjConsumer.class), + A_SargAttack(ActionFunctions::A_SargAttack, MobjConsumer.class), + A_HeadAttack(ActionFunctions::A_HeadAttack, MobjConsumer.class), + A_BruisAttack(ActionFunctions::A_BruisAttack, MobjConsumer.class), + A_SkullAttack(ActionFunctions::A_SkullAttack, MobjConsumer.class), + A_Metal(ActionFunctions::A_Metal, MobjConsumer.class), + A_SpidRefire(ActionFunctions::A_SpidRefire, MobjConsumer.class), + A_BabyMetal(ActionFunctions::A_BabyMetal, MobjConsumer.class), + A_BspiAttack(ActionFunctions::A_BspiAttack, MobjConsumer.class), + A_Hoof(ActionFunctions::A_Hoof, MobjConsumer.class), + A_CyberAttack(ActionFunctions::A_CyberAttack, MobjConsumer.class), + A_PainAttack(ActionFunctions::A_PainAttack, MobjConsumer.class), + A_PainDie(ActionFunctions::A_PainDie, MobjConsumer.class), + A_KeenDie(ActionFunctions::A_KeenDie, MobjConsumer.class), + A_BrainPain(ActionFunctions::A_BrainPain, MobjConsumer.class), + A_BrainScream(ActionFunctions::A_BrainScream, MobjConsumer.class), + A_BrainDie(ActionFunctions::A_BrainDie, MobjConsumer.class), + A_BrainAwake(ActionFunctions::A_BrainAwake, MobjConsumer.class), + A_BrainSpit(ActionFunctions::A_BrainSpit, MobjConsumer.class), + A_SpawnSound(ActionFunctions::A_SpawnSound, MobjConsumer.class), + A_SpawnFly(ActionFunctions::A_SpawnFly, MobjConsumer.class), + A_BrainExplode(ActionFunctions::A_BrainExplode, MobjConsumer.class), + P_MobjThinker(ActionFunctions::P_MobjThinker, MobjConsumer.class), + T_FireFlicker(ActionFunctions::T_FireFlicker, ThinkerConsumer.class), + T_LightFlash(ActionFunctions::T_LightFlash, ThinkerConsumer.class), + T_StrobeFlash(ActionFunctions::T_StrobeFlash, ThinkerConsumer.class), + T_Glow(ActionFunctions::T_Glow, ThinkerConsumer.class), + T_MoveCeiling(ActionFunctions::T_MoveCeiling, ThinkerConsumer.class), + T_MoveFloor(ActionFunctions::T_MoveFloor, ThinkerConsumer.class), + T_VerticalDoor(ActionFunctions::T_VerticalDoor, ThinkerConsumer.class), + T_PlatRaise(ActionFunctions::T_PlatRaise, ThinkerConsumer.class), + T_SlidingDoor(ActionFunctions::T_SlidingDoor, ThinkerConsumer.class); + + private final static Logger LOGGER = Loggers.getLogger(ActiveStates.class.getName()); + + private final ParamClass actionFunction; + private final Class> paramType; + + private > ActiveStates(final T actionFunction, final Class paramType) { + this.actionFunction = actionFunction; + this.paramType = paramType; + } + + private static void nop(Object... o) { + } + + @actionf_p1 + @D_Think.C(actionf_t.acp1) + public interface MobjConsumer extends ParamClass { + + void accept(ActionFunctions a, mobj_t m); + } + + @actionf_v + @D_Think.C(actionf_t.acv) + public interface ThinkerConsumer extends ParamClass { + + void accept(ActionFunctions a, thinker_t t); + } + + @actionf_p2 + @D_Think.C(actionf_t.acp2) + public interface PlayerSpriteConsumer extends ParamClass { + + void accept(ActionFunctions a, player_t p, pspdef_t s); + } + + private interface ParamClass> { + } + + public boolean isParamType(final Class paramType) { + return this.paramType == paramType; + } + + @SuppressWarnings("unchecked") + public > T fun(final Class paramType) { + if (this.paramType != paramType) { + LOGGER.log(Level.WARNING, "Wrong paramType for state: {0}", this); + return null; + } + + // don't believe, it's checked + return (T) this.actionFunction; + } +} \ No newline at end of file diff --git a/doom/src/p/BoomLevelLoader.java b/doom/src/p/BoomLevelLoader.java new file mode 100644 index 0000000..92123d5 --- /dev/null +++ b/doom/src/p/BoomLevelLoader.java @@ -0,0 +1,2237 @@ +package p; + +import static boom.Compatibility.prboom_2_compatibility; +import boom.DeepBSPNodesV4; +import static boom.E6Y.NO_INDEX; +import boom.mapglvertex_t; +import boom.mapnode_v4_t; +import boom.mapnode_znod_t; +import boom.mapseg_v4_t; +import boom.mapseg_znod_t; +import boom.mapsubsector_v4_t; +import boom.mapsubsector_znod_t; +import static data.Defines.NF_SUBSECTOR; +import static data.Defines.NF_SUBSECTOR_CLASSIC; +import static data.Defines.PU_LEVEL; +import data.Limits; +import data.maplinedef_t; +import data.mapnode_t; +import data.mapsector_t; +import data.mapseg_t; +import data.mapsidedef_t; +import data.mapsubsector_t; +import data.mapthing_t; +import data.mapvertex_t; +import defines.skill_t; +import defines.slopetype_t; +import doom.CommandVariable; +import doom.DoomMain; +import doom.DoomStatus; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.P_Setup; +import static doom.SourceCode.P_Setup.P_LoadThings; +import static doom.SourceCode.P_Setup.P_SetupLevel; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.BBox; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import m.fixed_t; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import mochadoom.Loggers; +import rr.RendererState; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import rr.node_t; +import rr.sector_t; +import rr.seg_t; +import rr.side_t; +import rr.subsector_t; +import rr.vertex_t; +import rr.z_vertex_t; +import s.degenmobj_t; +import utils.C2JUtils; +import static utils.C2JUtils.flags; +import static utils.C2JUtils.unsigned; +import utils.GenericCopy.ArraySupplier; +import static utils.GenericCopy.malloc; +import w.CacheableDoomObjectContainer; +import w.DoomBuffer; +import w.wadfile_info_t; + +/* + * Emacs style mode select -*- Java -*- + * ----------------------------------------------------------------------------- + * PrBoom: a Doom port merged with LxDoom and LSDLDoom based on BOOM, a modified + * and improved DOOM engine Copyright (C) 1999 by id Software, Chi Hoang, Lee + * Killough, Jim Flynn, Rand Phares, Ty Halderman Copyright (C) 1999-2000 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze Copyright 2005, + * 2006 by Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko This + * program is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. DESCRIPTION: Do all + * the WAD I/O, get map description, set up initial state and misc. LUTs. + * + * MAES 30/9/2011: This is a direct translation of prBoom+'s 2.5.0.8 p_setup.c + * and p_setup.h. + * + * + * + * ----------------------------------------------------------------------------- + */ +public class BoomLevelLoader extends AbstractLevelLoader { + + private static final Logger LOGGER = Loggers.getLogger(BoomLevelLoader.class.getName()); + + public BoomLevelLoader(DoomMain DM) { + super(DM); + // TODO Auto-generated constructor stub + } + + // OpenGL related. + byte[] map_subsectors; + + // ////////////////////////////////////////////////////////////////////////////////////////// + // figgi 08/21/00 -- finalants and globals for glBsp support + public static final int gNd2 = 0x32644E67; // figgi -- suppport for new + // GL_VERT format v2.0 + + public static final int gNd3 = 0x33644E67; + + public static final int gNd4 = 0x34644E67; + + public static final int gNd5 = 0x35644E67; + + public static final int ZNOD = 0x444F4E5A; + + public static final int ZGLN = 0x4E4C475A; + + public static final int GL_VERT_OFFSET = 4; + + int firstglvertex = 0; + + int nodesVersion = 0; + + boolean forceOldBsp = false; + + // figgi 08/21/00 -- glSegs + class glseg_t { + + char v1; // start vertex (16 bit) + + char v2; // end vertex (16 bit) + + char linedef; // linedef, or -1 for minisegs + + short side; // side on linedef: 0 for right, 1 for left + + short partner; // corresponding partner seg, or -1 on one-sided walls + } + + public static final int ML_GL_LABEL = 0; // A separator name, GL_ExMx or + // GL_MAPxx + + public static final int ML_GL_VERTS = 1; // Extra Vertices + + public static final int ML_GL_SEGS = 2; // Segs, from linedefs & minisegs + + public static final int ML_GL_SSECT = 3; // SubSectors, list of segs + + public static final int ML_GL_NODES = 4; // GL BSP nodes + + // ////////////////////////////////////////////////////////////////////////////////////////// + // + // REJECT + // For fast sight rejection. + // Speeds up enemy AI by skipping detailed + // LineOf Sight calculation. + // Without the special effect, this could + // be used as a PVS lookup as well. + // + private int rejectlump = -1;// cph - store reject lump num if cached + + private int current_episode = -1; + + private int current_map = -1; + + private int current_nodesVersion = -1; + + private boolean samelevel = false; + + /** + * e6y: Smart malloc Used by P_SetupLevel() for smart data loading. Do + * nothing if level is the same. Passing a null array forces allocation. + * + * @param p + * generically typed array to consider + * @param numstuff + * elements to realloc + */ + private T[] malloc_IfSameLevel(T[] p, int numstuff, ArraySupplier supplier, IntFunction generator) { + if (!samelevel || (p == null)) { + return malloc(supplier, generator, numstuff); + } + return p; + } + + // e6y: Smart calloc + // Used by P_SetupLevel() for smart data loading + // Clear the memory without allocation if level is the same + private T[] calloc_IfSameLevel(T[] p, int numstuff, ArraySupplier supplier, IntFunction generator) { + if (!samelevel) { + return malloc(supplier, generator, numstuff); + } else { + // TODO: stuff should be resetted! + C2JUtils.resetAll(p); + return p; + } + } + + // + // P_CheckForZDoomNodes + // + private boolean P_CheckForZDoomNodes(int lumpnum, int gl_lumpnum) { + byte[] data; + int check; + + data = DOOM.wadLoader.CacheLumpNumAsRawBytes(lumpnum + ML_NODES, 0); + check = ByteBuffer.wrap(data).getInt(); + + if (check == ZNOD) { + DOOM.doomSystem.Error("P_CheckForZDoomNodes: ZDoom nodes not supported yet"); + } + + data = DOOM.wadLoader.CacheLumpNumAsRawBytes(lumpnum + ML_SSECTORS, 0); + check = ByteBuffer.wrap(data).getInt(); + + if (check == ZGLN) { + DOOM.doomSystem.Error("P_CheckForZDoomNodes: ZDoom GL nodes not supported yet"); + } + + // Unlock them to force different buffering interpretation. + DOOM.wadLoader.UnlockLumpNum(lumpnum + ML_NODES); + DOOM.wadLoader.UnlockLumpNum(lumpnum + ML_SSECTORS); + + return false; + } + + // + // P_CheckForDeePBSPv4Nodes + // http://www.sbsoftware.com/files/DeePBSPV4specs.txt + // + private boolean P_CheckForDeePBSPv4Nodes(int lumpnum, int gl_lumpnum) { + byte[] data; + boolean result = false; + + data = DOOM.wadLoader.CacheLumpNumAsRawBytes(lumpnum + ML_NODES, 0); + byte[] compare = Arrays.copyOfRange(data, 0, 7); + + if (Arrays.equals(compare, DeepBSPNodesV4.DeepBSPHeader)) { + LOGGER.log(Level.INFO, "P_CheckForDeePBSPv4Nodes: DeePBSP v4 Extended nodes are detected"); + result = true; + } + + DOOM.wadLoader.UnlockLumpNum(lumpnum + ML_NODES); + + return result; + } + + // + // P_CheckForZDoomUncompressedNodes + // http://zdoom.org/wiki/ZDBSP#Compressed_Nodes + // + private static final int XNOD = 0x584e4f44; + + private boolean P_CheckForZDoomUncompressedNodes(int lumpnum, int gl_lumpnum) { + byte[] data; + int wrapper; + boolean result = false; + + data = DOOM.wadLoader.CacheLumpNumAsRawBytes(lumpnum + ML_NODES, 0); + wrapper = ByteBuffer.wrap(data).getInt(); + + if (wrapper == XNOD) { + LOGGER.log(Level.INFO, "P_CheckForZDoomUncompressedNodes: ZDoom uncompressed normal nodes are detected"); + result = true; + } + + DOOM.wadLoader.UnlockLumpNum(lumpnum + ML_NODES); + + return result; + } + + // + // P_GetNodesVersion + // + public void P_GetNodesVersion(int lumpnum, int gl_lumpnum) { + int ver = -1; + nodesVersion = 0; + + if ((gl_lumpnum > lumpnum) && (forceOldBsp == false) + && (DoomStatus.compatibility_level >= prboom_2_compatibility)) { + + byte[] data = DOOM.wadLoader.CacheLumpNumAsRawBytes(gl_lumpnum + ML_GL_VERTS, 0); + int wrapper = ByteBuffer.wrap(data).getInt(); + if (wrapper == gNd2) { + data = DOOM.wadLoader.CacheLumpNumAsRawBytes(gl_lumpnum + ML_GL_SEGS, 0); + wrapper = ByteBuffer.wrap(data).getInt(); + if (wrapper == gNd3) { + ver = 3; + } else { + nodesVersion = gNd2; + LOGGER.log(Level.INFO, "P_GetNodesVersion: found version 2 nodes"); + } + } + if (wrapper == gNd4) { + ver = 4; + } + if (wrapper == gNd5) { + ver = 5; + } + // e6y: unknown gl nodes will be ignored + if (nodesVersion == 0 && ver != -1) { + LOGGER.log(Level.INFO, String.format("P_GetNodesVersion: found version %d nodes", ver)); + LOGGER.log(Level.INFO, String.format("P_GetNodesVersion: version %d nodes not supported", ver)); + } + } else { + nodesVersion = 0; + LOGGER.log(Level.INFO, "P_GetNodesVersion: using normal BSP nodes"); + if (P_CheckForZDoomNodes(lumpnum, gl_lumpnum)) { + DOOM.doomSystem.Error("P_GetNodesVersion: ZDoom nodes not supported yet"); + } + } + } + + // + // P_LoadVertexes + // + // killough 5/3/98: reformatted, cleaned up + // + private void P_LoadVertexes(int lump) { + final mapvertex_t[] data; // cph - final + + // Determine number of lumps: + // total lump length / vertex record length. + numvertexes = DOOM.wadLoader.LumpLength(lump) / mapvertex_t.sizeOf(); + + // Allocate zone memory for buffer. + vertexes = calloc_IfSameLevel(vertexes, numvertexes, vertex_t::new, vertex_t[]::new); + + // Load data into cache. + // cph 2006/07/29 - cast to mapvertex_t here, making the loop below much + // neater + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numvertexes, mapvertex_t::new, mapvertex_t[]::new); + + // Copy and convert vertex coordinates, + // internal representation as fixed. + for (int i = 0; i < numvertexes; i++) { + vertexes[i].x = data[i].x << m.fixed_t.FRACBITS; + vertexes[i].y = data[i].y << FRACBITS; + } + + // Free buffer memory. + DOOM.wadLoader.UnlockLumpNum(lump); + } + + /******************************************* + * Name : P_LoadVertexes2 * modified : 09/18/00, adapted for PrBoom * author + * : figgi * what : support for gl nodes + * + * @throws IOException + * * + *******************************************/ + // figgi -- FIXME: Automap showes wrong zoom boundaries when starting game + // when P_LoadVertexes2 is used with classic BSP nodes. + private void P_LoadVertexes2(int lump, int gllump) throws IOException { + final ByteBuffer gldata; + mapvertex_t[] ml; + + // GL vertexes come after regular ones. + firstglvertex = DOOM.wadLoader.LumpLength(lump) / mapvertex_t.sizeOf(); + numvertexes = DOOM.wadLoader.LumpLength(lump) / mapvertex_t.sizeOf(); + + if (gllump >= 0) { // check for glVertices + // Read GL lump into buffer. This allows some flexibility + gldata = DOOM.wadLoader.CacheLumpNumAsDoomBuffer(gllump).getBuffer(); + + if (nodesVersion == gNd2) { // 32 bit GL_VERT format (16.16 fixed) + // These vertexes are double in size than regular Doom vertexes. + // Furthermore, we have to skip the first 4 bytes + // (GL_VERT_OFFSET) + // of the gl lump. + numvertexes += (DOOM.wadLoader.LumpLength(gllump) - GL_VERT_OFFSET) / mapglvertex_t.sizeOf(); + + // Vertexes size accomodates both normal and GL nodes. + vertexes = malloc_IfSameLevel(vertexes, numvertexes, vertex_t::new, vertex_t[]::new); + + final mapglvertex_t mgl[] = malloc(mapglvertex_t::new, mapglvertex_t[]::new, numvertexes - firstglvertex); + + // Get lump and skip first 4 bytes + gldata.rewind(); + gldata.position(GL_VERT_OFFSET); + + CacheableDoomObjectContainer.unpack(gldata, mgl); + + int mgl_count = 0; + + for (int i = firstglvertex; i < numvertexes; i++) { + vertexes[i].x = mgl[mgl_count].x; + vertexes[i].y = mgl[mgl_count].y; + mgl_count++; + } + } else { + // Vertexes size accomodates both normal and GL nodes. + numvertexes += DOOM.wadLoader.LumpLength(gllump) / mapvertex_t.sizeOf(); + vertexes = malloc_IfSameLevel(vertexes, numvertexes, vertex_t::new, vertex_t[]::new); + + ml = malloc(mapvertex_t::new, mapvertex_t[]::new, numvertexes - firstglvertex); + + // We can read this "directly" because no skipping is involved. + gldata.rewind(); + CacheableDoomObjectContainer.unpack(gldata, ml); + // ml = W.CacheLumpNumIntoArray(gllump, + // numvertexes-firstglvertex,mapvertex_t.class); + int ml_count = 0; + + for (int i = firstglvertex; i < numvertexes; i++) { + vertexes[i].x = ml[ml_count].x; + vertexes[i].y = ml[ml_count].y; + ml_count++; + } + } + DOOM.wadLoader.UnlockLumpNum(gllump); + } + + // Loading of regular lumps (sheesh!) + ml = DOOM.wadLoader.CacheLumpNumIntoArray(lump, firstglvertex, mapvertex_t::new, mapvertex_t[]::new); + + for (int i = 0; i < firstglvertex; i++) { + vertexes[i].x = ml[i].x; + vertexes[i].y = ml[i].y; + } + + DOOM.wadLoader.UnlockLumpNum(lump); + + } + + /******************************************* + * created : 08/13/00 * modified : 09/18/00, adapted for PrBoom * author : + * figgi * what : basic functions needed for * computing gl nodes * + *******************************************/ + public int checkGLVertex(int num) { + if ((num & 0x8000) != 0) { + num = (num & 0x7FFF) + firstglvertex; + } + return num; + } + + public static float GetDistance(int dx, int dy) { + float fx = (float) (dx) / FRACUNIT, fy = (float) (dy) / FRACUNIT; + return (float) Math.sqrt(fx * fx + fy * fy); + } + + public static float GetTexelDistance(int dx, int dy) { + // return (float)((int)(GetDistance(dx, dy) + 0.5f)); + float fx = (float) (dx) / FRACUNIT, fy = (float) (dy) / FRACUNIT; + return ((int) (0.5f + (float) Math.sqrt(fx * fx + fy * fy))); + } + + public static int GetOffset(vertex_t v1, vertex_t v2) { + float a, b; + int r; + a = (v1.x - v2.x) / (float) FRACUNIT; + b = (v1.y - v2.y) / (float) FRACUNIT; + r = (int) (Math.sqrt(a * a + b * b) * FRACUNIT); + return r; + } + + // + // P_LoadSegs + // + // killough 5/3/98: reformatted, cleaned up + private void P_LoadSegs(int lump) { + final mapseg_t[] data; // cph - final + + numsegs = DOOM.wadLoader.LumpLength(lump) / mapseg_t.sizeOf(); + segs = calloc_IfSameLevel(segs, numsegs, seg_t::new, seg_t[]::new); + + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsegs, mapseg_t::new, mapseg_t[]::new); // cph - + // wad + // lump + // handling + // updated + + if ((data == null) || (numsegs == 0)) { + DOOM.doomSystem.Error("P_LoadSegs: no segs in level"); + } + + for (int i = 0; i < numsegs; i++) { + seg_t li = segs[i]; + final mapseg_t ml = data[i]; + char v1, v2; + + int side, linedef; + line_t ldef; + + li.iSegID = i; // proff 11/05/2000: needed for OpenGL + + v1 = ml.v1; + v2 = ml.v2; + + // e6y + // moved down for additional checks to avoid overflow + // if wrong vertexe's indexes are in SEGS lump + // see below for more detailed information + // li.v1 = &vertexes[v1]; + // li.v2 = &vertexes[v2]; + li.miniseg = false; // figgi -- there are no minisegs in classic BSP + // nodes + + // e6y: moved down, see below + // li.length = GetDistance(li.v2.x - li.v1.x, li.v2.y - li.v1.y); + li.angle = ml.angle << 16; + li.offset = ml.offset << 16; + linedef = ml.linedef; + + // e6y: check for wrong indexes + if (linedef >= numlines) { + DOOM.doomSystem.Error("P_LoadSegs: seg %d references a non-existent linedef %d", i, linedef); + } + + ldef = lines[linedef]; + li.linedef = ldef; + side = ml.side; + + // e6y: fix wrong side index + if (side != 0 && side != 1) { + LOGGER.log(Level.WARNING, String.format("P_LoadSegs: seg %d contains wrong side index %d. Replaced with 1.", i, side)); + side = 1; + } + + // e6y: check for wrong indexes + if (ldef.sidenum[side] >= (char) numsides) { + DOOM.doomSystem.Error( + "P_LoadSegs: linedef %d for seg %d references a non-existent sidedef %d", + linedef, i, ldef.sidenum[side] + ); + } + + li.sidedef = sides[ldef.sidenum[side]]; + + /* + * cph 2006/09/30 - our frontsector can be the second side of the + * linedef, so must check for NO_INDEX in case we are incorrectly + * referencing the back of a 1S line + */ + if (ldef.sidenum[side] != NO_INDEX) { + li.frontsector = sides[ldef.sidenum[side]].sector; + } else { + li.frontsector = null; + LOGGER.log(Level.INFO, String.format("P_LoadSegs: front of seg %d has no sidedef", i)); + } + + if (flags(ldef.flags, ML_TWOSIDED) && ldef.sidenum[side ^ 1] != NO_INDEX) { + li.backsector = sides[ldef.sidenum[side ^ 1]].sector; + } else { + li.backsector = null; + } + + // e6y + // check and fix wrong references to non-existent vertexes + // see e1m9 @ NIVELES.WAD + // http://www.doomworld.com/idgames/index.php?id=12647 + if (v1 >= numvertexes || v2 >= numvertexes) { + String str = "P_LoadSegs: compatibility loss - seg %d references a non-existent vertex %d\n"; + + if (DOOM.demorecording) { + DOOM.doomSystem.Error( + str + "Demo recording on levels with invalid nodes is not allowed", + i, (v1 >= numvertexes ? v1 : v2) + ); + } + + if (v1 >= numvertexes) { + LOGGER.log(Level.WARNING, String.format(str, i, v1)); + } + if (v2 >= numvertexes) { + LOGGER.log(Level.WARNING, String.format(str, i, v2)); + } + + if (li.sidedef == sides[li.linedef.sidenum[0]]) { + li.v1 = lines[ml.linedef].v1; + li.v2 = lines[ml.linedef].v2; + } else { + li.v1 = lines[ml.linedef].v2; + li.v2 = lines[ml.linedef].v1; + } + } else { + li.v1 = vertexes[v1]; + li.v2 = vertexes[v2]; + } + + li.assignVertexValues(); + + // e6y: now we can calculate it + li.length = GetDistance(li.v2x - li.v1x, li.v2y - li.v1y); + + // Recalculate seg offsets that are sometimes incorrect + // with certain nodebuilders. Fixes among others, line 20365 + // of DV.wad, map 5 + li.offset = GetOffset(li.v1, (ml.side != 0 ? ldef.v2 : ldef.v1)); + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + private void P_LoadSegs_V4(int lump) { + int i; + mapseg_v4_t[] data; + + numsegs = DOOM.wadLoader.LumpLength(lump) / mapseg_v4_t.sizeOf(); + segs = calloc_IfSameLevel(segs, numsegs, seg_t::new, seg_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsegs, mapseg_v4_t::new, mapseg_v4_t[]::new); + + if ((data == null) || (numsegs == 0)) { + DOOM.doomSystem.Error("P_LoadSegs_V4: no segs in level"); + } + + for (i = 0; i < numsegs; i++) { + seg_t li = segs[i]; + final mapseg_v4_t ml = data[i]; + int v1, v2; + + int side, linedef; + line_t ldef; + + li.iSegID = i; // proff 11/05/2000: needed for OpenGL + + v1 = ml.v1; + v2 = ml.v2; + + li.miniseg = false; // figgi -- there are no minisegs in classic BSP + // nodes + + li.angle = ml.angle << 16; + li.offset = ml.offset << 16; + linedef = ml.linedef; + + // e6y: check for wrong indexes + if (unsigned(linedef) >= unsigned(numlines)) { + DOOM.doomSystem.Error( + "P_LoadSegs_V4: seg %d references a non-existent linedef %d", + i, unsigned(linedef)); + } + + ldef = lines[linedef]; + li.linedef = ldef; + side = ml.side; + + // e6y: fix wrong side index + if (side != 0 && side != 1) { + LOGGER.log(Level.WARNING, String.format("P_LoadSegs_V4: seg %d contains wrong side index %d. Replaced with 1.", i, side)); + side = 1; + } + + // e6y: check for wrong indexes + if (unsigned(ldef.sidenum[side]) >= unsigned(numsides)) { + DOOM.doomSystem.Error( + "P_LoadSegs_V4: linedef %d for seg %d references a non-existent sidedef %d", + linedef, i, unsigned(ldef.sidenum[side]) + ); + } + + li.sidedef = sides[ldef.sidenum[side]]; + + /* + * cph 2006/09/30 - our frontsector can be the second side of the + * linedef, so must check for NO_INDEX in case we are incorrectly + * referencing the back of a 1S line + */ + if (ldef.sidenum[side] != NO_INDEX) { + li.frontsector = sides[ldef.sidenum[side]].sector; + } else { + li.frontsector = null; + LOGGER.log(Level.WARNING, String.format("P_LoadSegs_V4: front of seg %d has no sidedef", i)); + } + + if (flags(ldef.flags, ML_TWOSIDED) + && ldef.sidenum[side ^ 1] != NO_INDEX) { + li.backsector = sides[ldef.sidenum[side ^ 1]].sector; + } else { + li.backsector = null; + } + + // e6y + // check and fix wrong references to non-existent vertexes + // see e1m9 @ NIVELES.WAD + // http://www.doomworld.com/idgames/index.php?id=12647 + if (v1 >= numvertexes || v2 >= numvertexes) { + String str = "P_LoadSegs_V4: compatibility loss - seg %d references a non-existent vertex %d\n"; + + if (DOOM.demorecording) { + DOOM.doomSystem.Error( + (str + "Demo recording on levels with invalid nodes is not allowed"), + i, (v1 >= numvertexes ? v1 : v2) + ); + } + + if (v1 >= numvertexes) { + LOGGER.log(Level.WARNING, String.format(str, i, v1)); + } + if (v2 >= numvertexes) { + LOGGER.log(Level.WARNING, String.format(str, i, v2)); + } + + if (li.sidedef == sides[li.linedef.sidenum[0]]) { + li.v1 = lines[ml.linedef].v1; + li.v2 = lines[ml.linedef].v2; + } else { + li.v1 = lines[ml.linedef].v2; + li.v2 = lines[ml.linedef].v1; + } + } else { + li.v1 = vertexes[v1]; + li.v2 = vertexes[v2]; + } + + // e6y: now we can calculate it + li.length = GetDistance(li.v2.x - li.v1.x, li.v2.y - li.v1.y); + + // Recalculate seg offsets that are sometimes incorrect + // with certain nodebuilders. Fixes among others, line 20365 + // of DV.wad, map 5 + li.offset = GetOffset(li.v1, (ml.side != 0 ? ldef.v2 : ldef.v1)); + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + /******************************************* + * Name : P_LoadGLSegs * created : 08/13/00 * modified : 09/18/00, adapted + * for PrBoom * author : figgi * what : support for gl nodes * + *******************************************/ + /* + * private void P_LoadGLSegs(int lump) { int i; final glseg_t ml; line_t + * ldef; numsegs = W.LumpLength(lump) / sizeof(glseg_t); segs = + * malloc_IfSameLevel(segs, numsegs * sizeof(seg_t)); memset(segs, 0, + * numsegs * sizeof(seg_t)); ml = (final glseg_t*)W.CacheLumpNum(lump); if + * ((!ml) || (!numsegs)) I_Error("P_LoadGLSegs: no glsegs in level"); for(i + * = 0; i < numsegs; i++) { // check for gl-vertices segs[i].v1 = + * &vertexes[checkGLVertex(LittleShort(ml.v1))]; segs[i].v2 = + * &vertexes[checkGLVertex(LittleShort(ml.v2))]; segs[i].iSegID = i; + * if(ml.linedef != (unsigned short)-1) // skip minisegs { ldef = + * &lines[ml.linedef]; segs[i].linedef = ldef; segs[i].miniseg = false; + * segs[i].angle = + * R_PointToAngle2(segs[i].v1.x,segs[i].v1.y,segs[i].v2.x,segs[i].v2.y); + * segs[i].sidedef = &sides[ldef.sidenum[ml.side]]; segs[i].length = + * GetDistance(segs[i].v2.x - segs[i].v1.x, segs[i].v2.y - segs[i].v1.y); + * segs[i].frontsector = sides[ldef.sidenum[ml.side]].sector; if (ldef.flags + * & ML_TWOSIDED) segs[i].backsector = + * sides[ldef.sidenum[ml.side^1]].sector; else segs[i].backsector = 0; if + * (ml.side) segs[i].offset = GetOffset(segs[i].v1, ldef.v2); else + * segs[i].offset = GetOffset(segs[i].v1, ldef.v1); } else { segs[i].miniseg + * = true; segs[i].angle = 0; segs[i].offset = 0; segs[i].length = 0; + * segs[i].linedef = NULL; segs[i].sidedef = NULL; segs[i].frontsector = + * NULL; segs[i].backsector = NULL; } ml++; } W.UnlockLumpNum(lump); } + */ + // + // P_LoadSubsectors + // + // killough 5/3/98: reformatted, cleaned up + private void P_LoadSubsectors(int lump) { + /* + * cph 2006/07/29 - make data a final mapsubsector_t *, so the loop + * below is simpler & gives no finalness warnings + */ + final mapsubsector_t[] data; + + numsubsectors = DOOM.wadLoader.LumpLength(lump) / mapsubsector_t.sizeOf(); + subsectors = calloc_IfSameLevel(subsectors, numsubsectors, subsector_t::new, subsector_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsubsectors, mapsubsector_t::new, mapsubsector_t[]::new); + + if ((data == null) || (numsubsectors == 0)) { + DOOM.doomSystem.Error("P_LoadSubsectors: no subsectors in level"); + } + + for (int i = 0; i < numsubsectors; i++) { + // e6y: support for extended nodes + subsectors[i].numlines = data[i].numsegs; + subsectors[i].firstline = data[i].firstseg; + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + private void P_LoadSubsectors_V4(int lump) { + /* + * cph 2006/07/29 - make data a final mapsubsector_t *, so the loop + * below is simpler & gives no finalness warnings + */ + final mapsubsector_v4_t[] data; + + numsubsectors = DOOM.wadLoader.LumpLength(lump) / mapsubsector_v4_t.sizeOf(); + subsectors = calloc_IfSameLevel(subsectors, numsubsectors, subsector_t::new, subsector_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsubsectors, mapsubsector_v4_t::new, mapsubsector_v4_t[]::new); + + if ((data == null) || (numsubsectors == 0)) { + DOOM.doomSystem.Error("P_LoadSubsectors_V4: no subsectors in level"); + } + + for (int i = 0; i < numsubsectors; i++) { + subsectors[i].numlines = data[i].numsegs; + subsectors[i].firstline = data[i].firstseg; + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + // + // P_LoadSectors + // + // killough 5/3/98: reformatted, cleaned up + private void P_LoadSectors(int lump) { + final mapsector_t[] data; // cph - final* + + numsectors = DOOM.wadLoader.LumpLength(lump) / mapsector_t.sizeOf(); + sectors = calloc_IfSameLevel(sectors, numsectors, sector_t::new, sector_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsectors, mapsector_t::new, mapsector_t[]::new); // cph + // - + // wad + // lump + // handling + // updated + + for (int i = 0; i < numsectors; i++) { + sector_t ss = sectors[i]; + final mapsector_t ms = data[i]; + + ss.id = i; // proff 04/05/2000: needed for OpenGL + ss.floorheight = ms.floorheight << FRACBITS; + ss.ceilingheight = ms.ceilingheight << FRACBITS; + ss.floorpic = (short) DOOM.textureManager.FlatNumForName(ms.floorpic); + ss.ceilingpic = (short) DOOM.textureManager.FlatNumForName(ms.ceilingpic); + ss.lightlevel = ms.lightlevel; + ss.special = ms.special; + // ss.oldspecial = ms.special; huh? + ss.tag = ms.tag; + ss.thinglist = null; + // MAES: link to thinker list and RNG + ss.TL = this.DOOM.actions; + ss.RND = this.DOOM.random; + + // ss.touching_thinglist = null; // phares 3/14/98 + // ss.nextsec = -1; //jff 2/26/98 add fields to support locking out + // ss.prevsec = -1; // stair retriggering until build completes + // killough 3/7/98: + // ss.floor_xoffs = 0; + // ss.floor_yoffs = 0; // floor and ceiling flats offsets + // ss.ceiling_xoffs = 0; + // ss.ceiling_yoffs = 0; + // ss.heightsec = -1; // sector used to get floor and ceiling height + // ss.floorlightsec = -1; // sector used to get floor lighting + // killough 3/7/98: end changes + // killough 4/11/98 sector used to get ceiling lighting: + // ss.ceilinglightsec = -1; + // killough 4/4/98: colormaps: + // ss.bottommap = ss.midmap = ss.topmap = 0; + // killough 10/98: sky textures coming from sidedefs: + // ss.sky = 0; + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + // + // P_LoadNodes + // + // killough 5/3/98: reformatted, cleaned up + private void P_LoadNodes(int lump) { + final mapnode_t[] data; // cph - final* + + numnodes = DOOM.wadLoader.LumpLength(lump) / mapnode_t.sizeOf(); + nodes = malloc_IfSameLevel(nodes, numnodes, node_t::new, node_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numnodes, mapnode_t::new, mapnode_t[]::new); // cph + // - + // wad + // lump + // handling + // updated + + if ((data == null) || (numnodes == 0)) { + // allow trivial maps + if (numsubsectors == 1) { + LOGGER.log(Level.INFO, "P_LoadNodes: trivial map (no nodes, one subsector)"); + } else { + DOOM.doomSystem.Error("P_LoadNodes: no nodes in level"); + } + } + + for (int i = 0; i < numnodes; i++) { + node_t no = nodes[i]; + final mapnode_t mn = data[i]; + + no.x = mn.x << FRACBITS; + no.y = mn.y << FRACBITS; + no.dx = mn.dx << FRACBITS; + no.dy = mn.dy << FRACBITS; + + for (int j = 0; j < 2; j++) { + // e6y: support for extended nodes + no.children[j] = mn.children[j]; + + // e6y: support for extended nodes + if (no.children[j] == 0xFFFF) { + no.children[j] = 0xFFFFFFFF; + } else if (flags(no.children[j], NF_SUBSECTOR_CLASSIC)) { + // Convert to extended type + no.children[j] &= ~NF_SUBSECTOR_CLASSIC; + + // haleyjd 11/06/10: check for invalid subsector reference + if (no.children[j] >= numsubsectors) { + LOGGER.log(Level.WARNING, String.format("P_LoadNodes: BSP tree references invalid subsector %d.", no.children[j])); + no.children[j] = 0; + } + + no.children[j] |= NF_SUBSECTOR; + } + + for (int k = 0; k < 4; k++) { + no.bbox[j].set(k, mn.bbox[j][k] << FRACBITS); + } + } + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + private void P_LoadNodes_V4(int lump) { + final DeepBSPNodesV4 data; // cph - final* + + numnodes = (DOOM.wadLoader.LumpLength(lump) - 8) / mapnode_v4_t.sizeOf(); + nodes = malloc_IfSameLevel(nodes, numnodes, node_t::new, node_t[]::new); + data = DOOM.wadLoader.CacheLumpNum(lump, 0, DeepBSPNodesV4.class); // cph + // - + // wad + // lump + // handling + // updated + + if ((data == null) || (numnodes == 0)) { + // allow trivial maps + if (numsubsectors == 1) { + LOGGER.log(Level.INFO, "P_LoadNodes_V4: trivial map (no nodes, one subsector)\n"); + } else { + DOOM.doomSystem.Error("P_LoadNodes_V4: no nodes in level"); + } + } + + for (int i = 0; i < numnodes; i++) { + node_t no = nodes[i]; + final mapnode_v4_t mn = data.getNodes()[i]; + + no.x = mn.x << FRACBITS; + no.y = mn.y << FRACBITS; + no.dx = mn.dx << FRACBITS; + no.dy = mn.dy << FRACBITS; + + for (int j = 0; j < 2; j++) { + no.children[j] = mn.children[j]; + + for (int k = 0; k < 4; k++) { + no.bbox[j].bbox[k] = mn.bbox[j][k] << FRACBITS; + } + } + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + private void P_LoadZSegs(ByteBuffer data) throws IOException { + final mapseg_znod_t nodes[] = malloc(mapseg_znod_t::new, mapseg_znod_t[]::new, numsegs); + CacheableDoomObjectContainer.unpack(data, nodes); + + for (int i = 0; i < numsegs; i++) { + line_t ldef; + int v1, v2; + int linedef; + char side; + seg_t li = segs[i]; + final mapseg_znod_t ml = nodes[i]; + + v1 = ml.v1; + v2 = ml.v2; + + li.iSegID = i; // proff 11/05/2000: needed for OpenGL + li.miniseg = false; + + linedef = ml.linedef; + + // e6y: check for wrong indexes + if (unsigned(linedef) >= unsigned(numlines)) { + DOOM.doomSystem.Error( + "P_LoadZSegs: seg %d references a non-existent linedef %d", + i, unsigned(linedef) + ); + } + + ldef = lines[linedef]; + li.linedef = ldef; + side = (char) ml.side; + + // e6y: fix wrong side index + if (side != 0 && side != 1) { + LOGGER.log(Level.WARNING, String.format("P_LoadZSegs: seg %d contains wrong side index %d. Replaced with 1.", i, (int) side)); + side = 1; + } + + // e6y: check for wrong indexes + if (unsigned(ldef.sidenum[side]) >= unsigned(numsides)) { + DOOM.doomSystem.Error( + "P_LoadZSegs: linedef %d for seg %d references a non-existent sidedef %d", + linedef, i, unsigned(ldef.sidenum[side]) + ); + } + + li.sidedef = sides[ldef.sidenum[side]]; + + /* + * cph 2006/09/30 - our frontsector can be the second side of the + * linedef, so must check for NO_INDEX in case we are incorrectly + * referencing the back of a 1S line + */ + if (ldef.sidenum[side] != NO_INDEX) { + li.frontsector = sides[ldef.sidenum[side]].sector; + } else { + li.frontsector = null; + LOGGER.log(Level.WARNING, String.format("P_LoadZSegs: front of seg %d has no sidedef", i)); + } + + if (flags(ldef.flags, ML_TWOSIDED) && (ldef.sidenum[side ^ 1] != NO_INDEX)) { + li.backsector = sides[ldef.sidenum[side ^ 1]].sector; + } else { + li.backsector = null; + } + + li.v1 = vertexes[v1]; + li.v2 = vertexes[v2]; + + li.length = GetDistance(li.v2.x - li.v1.x, li.v2.y - li.v1.y); + li.offset = GetOffset(li.v1, (side != 0 ? ldef.v2 : ldef.v1)); + li.angle = RendererState.PointToAngle(segs[i].v1.x, segs[i].v1.y, segs[i].v2.x, segs[i].v2.y); + // li.angle = (int)((float)atan2(li.v2.y - li.v1.y,li.v2.x - + // li.v1.x) * (ANG180 / M_PI)); + } + } + + private int CheckZNodesOverflow(int size, int count) { + size -= count; + + if (size < 0) { + DOOM.doomSystem.Error("P_LoadZNodes: incorrect nodes"); + } + + return size; + } + + private void P_LoadZNodes(int lump, int glnodes) throws IOException { + ByteBuffer data; + int len; + int header; // for debugging + + int orgVerts, newVerts; + int numSubs, currSeg; + int numSegs; + int numNodes; + vertex_t[] newvertarray = null; + + data = DOOM.wadLoader.CacheLumpNumAsDoomBuffer(lump).getBuffer(); + data.order(ByteOrder.LITTLE_ENDIAN); + len = DOOM.wadLoader.LumpLength(lump); + + // skip header + len = CheckZNodesOverflow(len, 4); + header = data.getInt(); + + // Read extra vertices added during node building + len = CheckZNodesOverflow(len, 4); + orgVerts = data.getInt(); + + len = CheckZNodesOverflow(len, 4); + newVerts = data.getInt(); + + if (!samelevel) { + if (orgVerts + newVerts == numvertexes) { + newvertarray = vertexes; + } else { + newvertarray = new vertex_t[orgVerts + newVerts]; + // TODO: avoid creating new objects that will be rewritten instantly - Good Sign 2017/05/07 + Arrays.setAll(newvertarray, ii -> new vertex_t()); + System.arraycopy(vertexes, 0, newvertarray, 0, orgVerts); + } + + //(sizeof(newvertarray[0].x) + sizeof(newvertarray[0].y)) + len = CheckZNodesOverflow(len, newVerts * vertex_t.sizeOf()); + z_vertex_t tmp = new z_vertex_t(); + + for (int i = 0; i < newVerts; i++) { + tmp.unpack(data); + newvertarray[i + orgVerts].x = tmp.x; + newvertarray[i + orgVerts].y = tmp.y; + } + + // Extra vertexes read in + if (vertexes != newvertarray) { + for (int i = 0; i < numlines; i++) { + //lines[i].v1 = lines[i].v1 - vertexes + newvertarray; + //lines[i].v2 = lines[i].v2 - vertexes + newvertarray; + // Find indexes of v1 & v2 inside old vertexes array + // (.v1-vertexes) and use that index to re-point inside newvertarray + lines[i].v1 = newvertarray[C2JUtils.indexOf(vertexes, lines[i].v1)]; + lines[i].v2 = newvertarray[C2JUtils.indexOf(vertexes, lines[i].v2)]; + } + // free(vertexes); + vertexes = newvertarray; + numvertexes = orgVerts + newVerts; + } + } else { + // Skip the reading of all these new vertices and the expensive indexOf searches. + int size = newVerts * z_vertex_t.sizeOf(); + len = CheckZNodesOverflow(len, size); + data.position(data.position() + size); + } + + // Read the subsectors + len = CheckZNodesOverflow(len, 4); + numSubs = data.getInt(); + + numsubsectors = numSubs; + if (numsubsectors <= 0) { + DOOM.doomSystem.Error("P_LoadZNodes: no subsectors in level"); + } + subsectors = calloc_IfSameLevel(subsectors, numsubsectors, subsector_t::new, subsector_t[]::new); + + len = CheckZNodesOverflow(len, numSubs * mapsubsector_znod_t.sizeOf()); + final mapsubsector_znod_t mseg = new mapsubsector_znod_t(); + for (int i = currSeg = 0; i < numSubs; i++) { + mseg.unpack(data); + + subsectors[i].firstline = currSeg; + subsectors[i].numlines = (int) mseg.numsegs; + currSeg += mseg.numsegs; + } + + // Read the segs + len = CheckZNodesOverflow(len, 4); + numSegs = data.getInt(); + + // The number of segs stored should match the number of + // segs used by subsectors. + if (numSegs != currSeg) { + DOOM.doomSystem.Error("P_LoadZNodes: Incorrect number of segs in nodes."); + } + + numsegs = numSegs; + segs = calloc_IfSameLevel(segs, numsegs, seg_t::new, seg_t[]::new); + + if (glnodes == 0) { + len = CheckZNodesOverflow(len, numsegs * mapseg_znod_t.sizeOf()); + P_LoadZSegs(data); + } else { + //P_LoadGLZSegs (data, glnodes); + DOOM.doomSystem.Error("P_LoadZNodes: GL segs are not supported."); + } + + // Read nodes + len = CheckZNodesOverflow(len, 4); + numNodes = data.getInt(); + + numnodes = numNodes; + nodes = calloc_IfSameLevel(nodes, numNodes, node_t::new, node_t[]::new); + + len = CheckZNodesOverflow(len, numNodes * mapnode_znod_t.sizeOf()); + + mapnode_znod_t znodes[] = malloc(mapnode_znod_t::new, mapnode_znod_t[]::new, numNodes); + CacheableDoomObjectContainer.unpack(data, znodes); + + for (int i = 0; i < numNodes; i++) { + int j, k; + node_t no = nodes[i]; + final mapnode_znod_t mn = znodes[i]; + + no.x = mn.x << FRACBITS; + no.y = mn.y << FRACBITS; + no.dx = mn.dx << FRACBITS; + no.dy = mn.dy << FRACBITS; + + for (j = 0; j < 2; j++) { + no.children[j] = mn.children[j]; + + for (k = 0; k < 4; k++) { + no.bbox[j].bbox[k] = mn.bbox[j][k] << FRACBITS; + } + } + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + } + + private boolean no_overlapped_sprites; + + private int GETXY(mobj_t mobj) { + return (mobj.x + (mobj.y >> 16)); + } + + private int dicmp_sprite_by_pos(final Object a, final Object b) { + mobj_t m1 = (mobj_t) a; + mobj_t m2 = (mobj_t) b; + + int res = GETXY(m2) - GETXY(m1); + no_overlapped_sprites = no_overlapped_sprites && (res != 0); + return res; + } + + /* + * P_LoadThings killough 5/3/98: reformatted, cleaned up cph 2001/07/07 - + * don't write into the lump cache, especially non-idepotent changes like + * byte order reversals. Take a copy to edit. + */ + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @P_Setup.C(P_LoadThings) + private void P_LoadThings(int lump) { + int numthings = DOOM.wadLoader.LumpLength(lump) / mapthing_t.sizeOf(); + final mapthing_t[] data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numthings, mapthing_t::new, mapthing_t[]::new); + + mobj_t mobj; + int mobjcount = 0; + mobj_t[] mobjlist = new mobj_t[numthings]; + Arrays.setAll(mobjlist, j -> mobj_t.createOn(DOOM)); + + if ((data == null) || (numthings == 0)) { + DOOM.doomSystem.Error("P_LoadThings: no things in level"); + } + + for (int i = 0; i < numthings; i++) { + mapthing_t mt = data[i]; + + /* + * Not needed. Handled during unmarshaling. mt.x = + * LittleShort(mt.x); mt.y = LittleShort(mt.y); mt.angle = + * LittleShort(mt.angle); mt.type = LittleShort(mt.type); mt.options + * = LittleShort(mt.options); + */ + if (!P_IsDoomnumAllowed(mt.type)) { + continue; + } + + // Do spawn all other stuff. + mobj = DOOM.actions.SpawnMapThing(mt/* , i */); + if (mobj != null && mobj.info.speed == 0) { + mobjlist[mobjcount++] = mobj; + } + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the data + /* + * #ifdef GL_DOOM if (V_GetMode() == VID_MODEGL) { no_overlapped_sprites + * = true; qsort(mobjlist, mobjcount, sizeof(mobjlist[0]), + * dicmp_sprite_by_pos); if (!no_overlapped_sprites) { i = 1; while (i < + * mobjcount) { mobj_t *m1 = mobjlist[i - 1]; mobj_t *m2 = mobjlist[i - + * 0]; if (GETXY(m1) == GETXY(m2)) { mobj_t *mo = (m1.index < m2.index ? + * m1 : m2); i++; while (i < mobjcount && GETXY(mobjlist[i]) == + * GETXY(m1)) { if (mobjlist[i].index < mo.index) { mo = mobjlist[i]; } + * i++; } // 'nearest' mo.flags |= MF_FOREGROUND; } i++; } } } #endif + */ + + } + + /* + * P_IsDoomnumAllowed() Based on code taken from P_LoadThings() in + * src/p_setup.c Return TRUE if the thing in question is expected to be + * available in the gamemode used. + */ + boolean P_IsDoomnumAllowed(int doomnum) { + // Do not spawn cool, new monsters if !commercial + if (!DOOM.isCommercial()) { + switch (doomnum) { + case 64: // Archvile + case 65: // Former Human Commando + case 66: // Revenant + case 67: // Mancubus + case 68: // Arachnotron + case 69: // Hell Knight + case 71: // Pain Elemental + case 84: // Wolf SS + case 88: // Boss Brain + case 89: // Boss Shooter + return false; + } + } + + return true; + } + + // + // P_LoadLineDefs + // Also counts secret lines for intermissions. + // ^^^ + // ??? killough ??? + // Does this mean secrets used to be linedef-based, rather than + // sector-based? + // + // killough 4/4/98: split into two functions, to allow sidedef overloading + // + // killough 5/3/98: reformatted, cleaned up + private void P_LoadLineDefs(int lump) { + final maplinedef_t[] data; // cph - final* + + numlines = DOOM.wadLoader.LumpLength(lump) / maplinedef_t.sizeOf(); + lines = calloc_IfSameLevel(lines, numlines, line_t::new, line_t[]::new); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numlines, maplinedef_t::new, maplinedef_t[]::new); // cph + // - + // wad + // lump + // handling + // updated + + for (int i = 0; i < numlines; i++) { + final maplinedef_t mld = data[i]; + line_t ld = lines[i]; + ld.id = i; + vertex_t v1, v2; + + ld.flags = mld.flags; + ld.special = mld.special; + ld.tag = mld.tag; + v1 = ld.v1 = vertexes[mld.v1]; + v2 = ld.v2 = vertexes[mld.v2]; + ld.dx = v2.x - v1.x; + ld.dy = v2.y - v1.y; + // Maes: map value semantics. + ld.assignVertexValues(); + + /* + * #ifdef GL_DOOM // e6y // Rounding the wall length to the nearest + * integer // when determining length instead of always rounding + * down // There is no more glitches on seams between identical + * textures. ld.texel_length = GetTexelDistance(ld.dx, ld.dy); + * #endif + */ + ld.tranlump = -1; // killough 4/11/98: no translucency by default + + ld.slopetype = (ld.dx == 0) + ? slopetype_t.ST_VERTICAL + : (ld.dy == 0) + ? slopetype_t.ST_HORIZONTAL + : fixed_t.FixedDiv(ld.dy, ld.dx) > 0 + ? slopetype_t.ST_POSITIVE + : slopetype_t.ST_NEGATIVE; + + if (v1.x < v2.x) { + ld.bbox[BBox.BOXLEFT] = v1.x; + ld.bbox[BBox.BOXRIGHT] = v2.x; + } else { + ld.bbox[BBox.BOXLEFT] = v2.x; + ld.bbox[BBox.BOXRIGHT] = v1.x; + } + if (v1.y < v2.y) { + ld.bbox[BBox.BOXBOTTOM] = v1.y; + ld.bbox[BBox.BOXTOP] = v2.y; + } else { + ld.bbox[BBox.BOXBOTTOM] = v2.y; + ld.bbox[BBox.BOXTOP] = v1.y; + } + + /* calculate sound origin of line to be its midpoint */ + // e6y: fix sound origin for large levels + // no need for comp_sound test, these are only used when comp_sound + // = 0 + ld.soundorg = new degenmobj_t( + ld.bbox[BBox.BOXLEFT] / 2 + + ld.bbox[BBox.BOXRIGHT] / 2, ld.bbox[BBox.BOXTOP] / 2 + + ld.bbox[BBox.BOXBOTTOM] / 2, 0 + ); + + // TODO + // ld.iLineID=i; // proff 04/05/2000: needed for OpenGL + ld.sidenum[0] = mld.sidenum[0]; + ld.sidenum[1] = mld.sidenum[1]; + + { + /* + * cph 2006/09/30 - fix sidedef errors right away. cph + * 2002/07/20 - these errors are fatal if not fixed, so apply + * them in compatibility mode - a desync is better than a crash! + */ + for (int j = 0; j < 2; j++) { + if (ld.sidenum[j] != NO_INDEX && ld.sidenum[j] >= numsides) { + ld.sidenum[j] = NO_INDEX; + LOGGER.log(Level.WARNING, String.format( + "P_LoadLineDefs: linedef %d has out-of-range sidedef number", + numlines - i - 1) + ); + } + } + + // killough 11/98: fix common wad errors (missing sidedefs): + if (ld.sidenum[0] == NO_INDEX) { + ld.sidenum[0] = 0; // Substitute dummy sidedef for missing + // right side + // cph - print a warning about the bug + LOGGER.log(Level.WARNING, String.format("P_LoadLineDefs: linedef %d missing first sidedef\n", numlines - i - 1)); + } + + if ((ld.sidenum[1] == NO_INDEX) && flags(ld.flags, ML_TWOSIDED)) { + // e6y + // ML_TWOSIDED flag shouldn't be cleared for compatibility + // purposes + // see CLNJ-506.LMP at http://doomedsda.us/wad1005.html + // TODO: we don't really care, but still... + // if (!demo_compatibility || + // !overflows[OVERFLOW.MISSEDBACKSIDE].emulate) + // { + ld.flags &= ~ML_TWOSIDED; // Clear 2s flag for missing left + // side + // } + // Mark such lines and do not draw them only in + // demo_compatibility, + // because Boom's behaviour is different + // See OTTAWAU.WAD E1M1, sectors 226 and 300 + // http://www.doomworld.com/idgames/index.php?id=1651 + // TODO ehhh? + // ld.r_flags = RF_IGNORE_COMPAT; + // cph - print a warning about the bug + LOGGER.log(Level.WARNING, String.format( + "P_LoadLineDefs: linedef %d has two-sided flag set, but no second sidedef\n", + numlines - i - 1) + ); + } + } + + // killough 4/4/98: support special sidedef interpretation below + // TODO: + // if (ld.sidenum[0] != NO_INDEX && ld.special!=0) + // sides[(ld.sidenum[0]<<16)& (0x0000FFFF&ld.sidenum[1])].special = + // ld.special; + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the lump + } + + // killough 4/4/98: delay using sidedefs until they are loaded + // killough 5/3/98: reformatted, cleaned up + private void P_LoadLineDefs2(int lump) { + line_t ld; + + for (int i = 0; i < numlines; i++) { + ld = lines[i]; + ld.frontsector = sides[ld.sidenum[0]].sector; // e6y: Can't be + // NO_INDEX here + ld.backsector + = ld.sidenum[1] != NO_INDEX ? sides[ld.sidenum[1]].sector : null; + switch (ld.special) { // killough 4/11/98: handle special types + case 260: // killough 4/11/98: translucent 2s textures + // TODO: transparentpresent = true;//e6y + // int lmp = sides[ld.getSpecialSidenum()].special; // + // translucency from sidedef + // if (!ld.tag) // if tag==0, + // ld.tranlump = lmp; // affect this linedef only + // else + // for (int j=0;j= numsectors) { + LOGGER.log(Level.WARNING, String.format("P_LoadSideDefs2: sidedef %d has out-of-range sector num %d", i, (int) sector_num)); + sector_num = 0; + } + sd.sector = sec = sectors[sector_num]; + } + + // killough 4/4/98: allow sidedef texture names to be overloaded + // killough 4/11/98: refined to allow colormaps to work as wall + // textures if invalid as colormaps but valid as textures. + switch (sd.special) { + case 242: // variable colormap via 242 linedef + /* + * sd.bottomtexture = (sec.bottommap = + * R.ColormapNumForName(msd.bottomtexture)) < 0 ? sec.bottommap + * = 0, R.TextureNumForName(msd.bottomtexture): 0 ; + * sd.midtexture = (sec.midmap = + * R.ColormapNumForName(msd.midtexture)) < 0 ? sec.midmap = 0, + * R.TextureNumForName(msd.midtexture) : 0 ; sd.toptexture = + * (sec.topmap = R.ColormapNumForName(msd.toptexture)) < 0 ? + * sec.topmap = 0, R.TextureNumForName(msd.toptexture) : 0 ; + */ + + break; + + case 260: // killough 4/11/98: apply translucency to 2s normal texture + if (msd.midtexture.compareToIgnoreCase("TRANMAP") == 0) { + if ((sd.special = DOOM.wadLoader.CheckNumForName(msd.midtexture)) < 0 + || DOOM.wadLoader.LumpLength(sd.special) != 65536) { + sd.special = 0; + sd.midtexture = (short) DOOM.textureManager.TextureNumForName(msd.midtexture); + } else { + sd.special++; + sd.midtexture = 0; + } + } else { + sd.midtexture = (short) (sd.special = 0); + } + sd.toptexture = (short) DOOM.textureManager.TextureNumForName(msd.toptexture); + sd.bottomtexture = (short) DOOM.textureManager.TextureNumForName(msd.bottomtexture); + break; + + /* + * #ifdef GL_DOOM case 271: case 272: if + * (R_CheckTextureNumForName(msd.toptexture) == -1) { + * sd.skybox_index = R_BoxSkyboxNumForName(msd.toptexture); } #endif + */ + default: // normal cases + // TODO: Boom uses "SafeTextureNumForName" here. Find out what + // it does. + sd.midtexture = (short) DOOM.textureManager.CheckTextureNumForName(msd.midtexture); + sd.toptexture = (short) DOOM.textureManager.CheckTextureNumForName(msd.toptexture); + sd.bottomtexture = (short) DOOM.textureManager.CheckTextureNumForName(msd.bottomtexture); + break; + } + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - release the lump + } + + // + // P_LoadBlockMap + // + // killough 3/1/98: substantially modified to work + // towards removing blockmap limit (a wad limitation) + // + // killough 3/30/98: Rewritten to remove blockmap limit, + // though current algorithm is brute-force and unoptimal. + // + private void P_LoadBlockMap(int lump) throws IOException { + int count = 0; + + if (DOOM.cVarManager.bool(CommandVariable.BLOCKMAP) + || DOOM.wadLoader.LumpLength(lump) < 8 + || (count = DOOM.wadLoader.LumpLength(lump) / 2) >= 0x10000) // e6y + { + CreateBlockMap(); + } else { + // cph - final*, wad lump handling updated + final char[] wadblockmaplump; + + DoomBuffer data = DOOM.wadLoader.CacheLumpNum(lump, PU_LEVEL, DoomBuffer.class); + count = DOOM.wadLoader.LumpLength(lump) / 2; + wadblockmaplump = new char[count]; + + data.setOrder(ByteOrder.LITTLE_ENDIAN); + data.rewind(); + data.readCharArray(wadblockmaplump, count); + + if (!samelevel) // Reallocate if required. + { + blockmaplump = new int[count]; + } + + // killough 3/1/98: Expand wad blockmap into larger internal one, + // by treating all offsets except -1 as unsigned and zero-extending + // them. This potentially doubles the size of blockmaps allowed, + // because Doom originally considered the offsets as always signed. + blockmaplump[0] = wadblockmaplump[0]; + blockmaplump[1] = wadblockmaplump[1]; + blockmaplump[2] = wadblockmaplump[2] & 0xffff; + blockmaplump[3] = wadblockmaplump[3] & 0xffff; + + for (int i = 4; i < count; i++) { + short t = (short) wadblockmaplump[i]; // killough 3/1/98 + blockmaplump[i] = (int) (t == -1 ? -1l : t & 0xffff); + } + + DOOM.wadLoader.UnlockLumpNum(lump); // cph - unlock the lump + + bmaporgx = blockmaplump[0] << FRACBITS; + bmaporgy = blockmaplump[1] << FRACBITS; + bmapwidth = blockmaplump[2]; + bmapheight = blockmaplump[3]; + + // haleyjd 03/04/10: check for blockmap problems + // http://www.doomworld.com/idgames/index.php?id=12935 + if (!VerifyBlockMap(count)) { + LOGGER.log(Level.WARNING, String.format("P_LoadBlockMap: erroneous BLOCKMAP lump may cause crashes.")); + LOGGER.log(Level.WARNING, String.format("P_LoadBlockMap: use \"-blockmap\" command line switch for rebuilding")); + } + } + + // MAES: blockmap was generated, rather than loaded. + if (count == 0) { + count = blockmaplump.length - 4; + } + + // clear out mobj chains - CPhipps - use calloc + // blocklinks = calloc_IfSameLevel(blocklinks, bmapwidth * + // bmapheight.mobj_t.); + // clear out mobj chains + // ATTENTION! BUG!!! + // If blocklinks are "cleared" to void -but instantiated- objects, + // very bad bugs happen, especially the second time a level is + // re-instantiated. + // Probably caused other bugs as well, as an extra object would appear + // in iterators. + if (blocklinks != null && samelevel) { + for (int i = 0; i < bmapwidth * bmapheight; i++) { + blocklinks[i] = null; + } + } else { + blocklinks = new mobj_t[bmapwidth * bmapheight]; + } + + // IMPORTANT MODIFICATION: no need to have both blockmaplump AND + // blockmap. + // If the offsets in the lump are OK, then we can modify them (remove 4) + // and copy the rest of the data in one single data array. This avoids + // reserving memory for two arrays (we can't simply alias one in Java) + blockmap = new int[blockmaplump.length - 4]; + count = bmapwidth * bmapheight; + // Offsets are relative to START OF BLOCKMAP, and IN SHORTS, not bytes. + for (int i = 0; i < blockmaplump.length - 4; i++) { + // Modify indexes so that we don't need two different lumps. + // Can probably be further optimized if we simply shift everything + // backwards. + // and reuse the same memory space. + if (i < count) { + blockmaplump[i] = blockmaplump[i + 4] - 4; + } else { + blockmaplump[i] = blockmaplump[i + 4]; + } + } + + // MAES: set blockmapxneg and blockmapyneg + // E.g. for a full 512x512 map, they should be both + // -1. For a 257*257, they should be both -255 etc. + if (bmapwidth > 255) { + blockmapxneg = bmapwidth - 512; + } + if (bmapheight > 255) { + blockmapyneg = bmapheight - 512; + } + + blockmap = blockmaplump; + + } + + // + // P_LoadReject - load the reject table + // + private void P_LoadReject(int lumpnum, int totallines) { + // dump any old cached reject lump, then cache the new one + if (rejectlump != -1) { + DOOM.wadLoader.UnlockLumpNum(rejectlump); + } + rejectlump = lumpnum + ML_REJECT; + rejectmatrix = DOOM.wadLoader.CacheLumpNumAsRawBytes(rejectlump, 0); + + // e6y: check for overflow + // TODO: g.Overflow.RejectOverrun(rejectlump, rejectmatrix, + // totallines,numsectors); + } + + // + // P_GroupLines + // Builds sector line lists and subsector sector numbers. + // Finds block bounding boxes for sectors. + // + // killough 5/3/98: reformatted, cleaned up + // cph 18/8/99: rewritten to avoid O(numlines * numsectors) section + // It makes things more complicated, but saves seconds on big levels + // figgi 09/18/00 -- adapted for gl-nodes + // modified to return totallines (needed by P_LoadReject) + private int P_GroupLines() { + line_t li; + sector_t sector; + int total = numlines; + + // figgi + for (int i = 0; i < numsubsectors; i++) { + int seg = subsectors[i].firstline; + subsectors[i].sector = null; + for (int j = 0; j < subsectors[i].numlines; j++) { + if (segs[seg].sidedef != null) { + subsectors[i].sector = segs[seg].sidedef.sector; + break; + } + seg++; + } + if (subsectors[i].sector == null) { + DOOM.doomSystem.Error("P_GroupLines: Subsector a part of no sector!\n"); + } + } + + // count number of lines in each sector + for (int i = 0; i < numlines; i++) { + li = lines[i]; + li.frontsector.linecount++; + if (li.backsector != null && (li.backsector != li.frontsector)) { + li.backsector.linecount++; + total++; + } + } + + // allocate line tables for each sector + // e6y: REJECT overrun emulation code + // moved to P_LoadReject + for (int i = 0; i < numsectors; i++) { + sector = sectors[i]; + sector.lines = malloc(line_t::new, line_t[]::new, sector.linecount); + // linebuffer += sector.linecount; + sector.linecount = 0; + BBox.ClearBox(sector.blockbox); + } + + // Enter those lines + for (int i = 0; i < numlines; i++) { + li = lines[i]; + AddLineToSector(li, li.frontsector); + if (li.backsector != null && li.backsector != li.frontsector) { + AddLineToSector(li, li.backsector); + } + } + + for (int i = 0; i < numsectors; i++) { + sector = sectors[i]; + int[] bbox = sector.blockbox; // cph - For convenience, so + // I can sue the old code unchanged + int block; + + // set the degenmobj_t to the middle of the bounding box + // TODO + if (true/* comp[comp_sound] */) { + sector.soundorg = new degenmobj_t((bbox[BOXRIGHT] + bbox[BOXLEFT]) / 2, (bbox[BOXTOP] + bbox[BOXBOTTOM]) / 2); + } else { + // e6y: fix sound origin for large levels + sector.soundorg = new degenmobj_t((bbox[BOXRIGHT] / 2 + bbox[BOXLEFT] / 2), bbox[BOXTOP] / 2 + bbox[BOXBOTTOM] / 2); + } + + // adjust bounding box to map blocks + block = getSafeBlockY(bbox[BOXTOP] - bmaporgy + Limits.MAXRADIUS); + block = block >= bmapheight ? bmapheight - 1 : block; + sector.blockbox[BOXTOP] = block; + + block = getSafeBlockY(bbox[BOXBOTTOM] - bmaporgy - Limits.MAXRADIUS); + block = block < 0 ? 0 : block; + sector.blockbox[BOXBOTTOM] = block; + + block = getSafeBlockX(bbox[BOXRIGHT] - bmaporgx + Limits.MAXRADIUS); + block = block >= bmapwidth ? bmapwidth - 1 : block; + sector.blockbox[BOXRIGHT] = block; + + block = getSafeBlockX(bbox[BOXLEFT] - bmaporgx - Limits.MAXRADIUS); + block = block < 0 ? 0 : block; + sector.blockbox[BOXLEFT] = block; + } + + return total; // this value is needed by the reject overrun emulation + // code + } + + // + // killough 10/98 + // + // Remove slime trails. + // + // Slime trails are inherent to Doom's coordinate system -- i.e. there is + // nothing that a node builder can do to prevent slime trails ALL of the + // time, + // because it's a product of the integer coodinate system, and just because + // two lines pass through exact integer coordinates, doesn't necessarily + // mean + // that they will intersect at integer coordinates. Thus we must allow for + // fractional coordinates if we are to be able to split segs with node + // lines, + // as a node builder must do when creating a BSP tree. + // + // A wad file does not allow fractional coordinates, so node builders are + // out + // of luck except that they can try to limit the number of splits (they + // might + // also be able to detect the degree of roundoff error and try to avoid + // splits + // with a high degree of roundoff error). But we can use fractional + // coordinates + // here, inside the engine. It's like the difference between square inches + // and + // square miles, in terms of granularity. + // + // For each vertex of every seg, check to see whether it's also a vertex of + // the linedef associated with the seg (i.e, it's an endpoint). If it's not + // an endpoint, and it wasn't already moved, move the vertex towards the + // linedef by projecting it using the law of cosines. Formula: + // + // 2 2 2 2 + // dx x0 + dy x1 + dx dy (y0 - y1) dy y0 + dx y1 + dx dy (x0 - x1) + // {---------------------------------, ---------------------------------} + // 2 2 2 2 + // dx + dy dx + dy + // + // (x0,y0) is the vertex being moved, and (x1,y1)-(x1+dx,y1+dy) is the + // reference linedef. + // + // Segs corresponding to orthogonal linedefs (exactly vertical or horizontal + // linedefs), which comprise at least half of all linedefs in most wads, + // don't + // need to be considered, because they almost never contribute to slime + // trails + // (because then any roundoff error is parallel to the linedef, which + // doesn't + // cause slime). Skipping simple orthogonal lines lets the code finish + // quicker. + // + // Please note: This section of code is not interchangable with TeamTNT's + // code which attempts to fix the same problem. + // + // Firelines (TM) is a Rezistered Trademark of MBF Productions + // + private void P_RemoveSlimeTrails() { // killough 10/98 + // Hitlist for vertices + boolean[] hit = new boolean[numvertexes]; + + // Searchlist for + for (int i = 0; i < numsegs; i++) { // Go through each seg + final line_t l; + + if (segs[i].miniseg == true) { // figgi -- skip minisegs + return; + } + + l = segs[i].linedef; // The parent linedef + if (l.dx != 0 && l.dy != 0) { // We can ignore orthogonal lines + vertex_t v = segs[i].v1; + do { + int index = C2JUtils.indexOf(vertexes, v); + if (!hit[index]) { // If we haven't processed vertex + hit[index] = true; // Mark this vertex as processed + if (v != l.v1 && v != l.v2) { // Exclude endpoints of linedefs + // Project the vertex back onto the parent linedef + long dx2 = (l.dx >> FRACBITS) * (l.dx >> FRACBITS); + long dy2 = (l.dy >> FRACBITS) * (l.dy >> FRACBITS); + long dxy = (l.dx >> FRACBITS) * (l.dy >> FRACBITS); + long s = dx2 + dy2; + int x0 = v.x, y0 = v.y, x1 = l.v1.x, y1 = l.v1.y; + v.x = (int) ((dx2 * x0 + dy2 * x1 + dxy * (y0 - y1)) / s); + v.y = (int) ((dy2 * y0 + dx2 * y1 + dxy * (x0 - x1)) / s); + } + } // Obsfucated C contest entry: :) + } while ((v != segs[i].v2) && ((v = segs[i].v2) != null)); + } + // Assign modified vertex values. + l.assignVertexValues(); + } + } + + // + // P_CheckLumpsForSameSource + // + // Are these lumps in the same wad file? + // + boolean P_CheckLumpsForSameSource(int lump1, int lump2) { + int wad1_index, wad2_index; + wadfile_info_t wad1, wad2; + + if ((unsigned(lump1) >= unsigned(DOOM.wadLoader.NumLumps())) + || (unsigned(lump2) >= unsigned(DOOM.wadLoader.NumLumps()))) { + return false; + } + + wad1 = DOOM.wadLoader.GetLumpInfo(lump1).wadfile; + wad2 = DOOM.wadLoader.GetLumpInfo(lump2).wadfile; + + if (wad1 == null || wad2 == null) { + return false; + } + + wad1_index = DOOM.wadLoader.GetWadfileIndex(wad1); + wad2_index = DOOM.wadLoader.GetWadfileIndex(wad2); + + if (wad1_index != wad2_index) { + return false; + } + + if ((wad1_index < 0) || (wad1_index >= DOOM.wadLoader.GetNumWadfiles())) { + return false; + } + + return !((wad2_index < 0) || (wad2_index >= DOOM.wadLoader.GetNumWadfiles())); + } + + private static final String[] ml_labels = { + "ML_LABEL", // A separator, name, ExMx or MAPxx + "ML_THINGS", // Monsters, items.. + "ML_LINEDEFS", // LineDefs, from editing + "ML_SIDEDEFS", // SideDefs, from editing + "ML_VERTEXES", // Vertices, edited and BSP splits generated + "ML_SEGS", // LineSegs, from LineDefs split by BSP + "ML_SSECTORS", // SubSectors, list of LineSegs + "ML_NODES", // BSP nodes + "ML_SECTORS", // Sectors, from editing + "ML_REJECT", // LUT, sector-sector visibility + "ML_BLOCKMAP", // LUT, motion clipping, walls/grid element + }; + + private static final boolean GL_DOOM = false; + + // + // P_CheckLevelFormat + // + // Checking for presence of necessary lumps + // + void P_CheckLevelWadStructure(final String mapname) { + int i, lumpnum; + + if (mapname == null) { + DOOM.doomSystem.Error("P_SetupLevel: Wrong map name"); + throw new NullPointerException(); + } + + lumpnum = DOOM.wadLoader.CheckNumForName(mapname.toUpperCase()); + + if (lumpnum < 0) { + DOOM.doomSystem.Error("P_SetupLevel: There is no %s map.", mapname); + } + + for (i = ML_THINGS + 1; i <= ML_SECTORS; i++) { + if (!P_CheckLumpsForSameSource(lumpnum, lumpnum + i)) { + DOOM.doomSystem.Error( + "P_SetupLevel: Level wad structure is incomplete. There is no %s lump. (%s)", + ml_labels[i], DOOM.wadLoader.GetNameForLump(lumpnum)); + } + } + + // refuse to load Hexen-format maps, avoid segfaults + i = lumpnum + ML_BLOCKMAP + 1; + if (P_CheckLumpsForSameSource(lumpnum, i)) { + if (DOOM.wadLoader.GetLumpInfo(i).name.compareToIgnoreCase("BEHAVIOR") == 0) { + DOOM.doomSystem.Error("P_SetupLevel: %s: Hexen format not supported", mapname); + } + } + } + + // + // P_SetupLevel + // + // killough 5/3/98: reformatted, cleaned up + @Override + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @P_Setup.C(P_SetupLevel) + public void SetupLevel(int episode, int map, int playermask, skill_t skill) throws IOException { + String lumpname; + int lumpnum; + + String gl_lumpname; + int gl_lumpnum; + + // e6y + DOOM.totallive = 0; + // TODO: transparentpresent = false; + + // R_StopAllInterpolations(); + DOOM.totallive = DOOM.totalkills = DOOM.totalitems = DOOM.totalsecret = DOOM.wminfo.maxfrags = 0; + DOOM.wminfo.partime = 180; + + for (int i = 0; i < Limits.MAXPLAYERS; i++) { + DOOM.players[i].killcount = DOOM.players[i].secretcount = DOOM.players[i].itemcount = 0; + // TODO DM.players[i].resurectedkillcount = 0;//e6y + } + + // Initial height of PointOfView + // will be set by player think. + DOOM.players[DOOM.consoleplayer].viewz = 1; + + // Make sure all sounds are stopped before Z_FreeTags. + S_Start: + { + DOOM.doomSound.Start(); + } + + Z_FreeTags: + ; // Z_FreeTags(PU_LEVEL, PU_PURGELEVEL-1); + + if (rejectlump != -1) { // cph - unlock the reject table + DOOM.wadLoader.UnlockLumpNum(rejectlump); + rejectlump = -1; + } + + P_InitThinkers: + { + DOOM.actions.InitThinkers(); + } + + // if working with a devlopment map, reload it + W_Reload: + ; // killough 1/31/98: W.Reload obsolete + + // find map name + if (DOOM.isCommercial()) { + lumpname = String.format("map%02d", map); // killough 1/24/98: + // simplify + gl_lumpname = String.format("gl_map%02d", map); // figgi + } else { + lumpname = String.format("E%dM%d", episode, map); // killough + // 1/24/98: + // simplify + gl_lumpname = String.format("GL_E%dM%d", episode, map); // figgi + } + + W_GetNumForName: + { + lumpnum = DOOM.wadLoader.GetNumForName(lumpname); + gl_lumpnum = DOOM.wadLoader.CheckNumForName(gl_lumpname); // figgi + } + + // e6y + // Refuse to load a map with incomplete pwad structure. + // Avoid segfaults on levels without nodes. + P_CheckLevelWadStructure(lumpname); + + DOOM.leveltime = 0; + DOOM.totallive = 0; + + // note: most of this ordering is important + // killough 3/1/98: P_LoadBlockMap call moved down to below + // killough 4/4/98: split load of sidedefs into two parts, + // to allow texture names to be used in special linedefs + // figgi 10/19/00 -- check for gl lumps and load them + P_GetNodesVersion(lumpnum, gl_lumpnum); + + // e6y: speedup of level reloading + // Most of level's structures now are allocated with PU_STATIC instead + // of PU_LEVEL + // It is important for OpenGL, because in case of the same data in + // memory + // we can skip recalculation of much stuff + samelevel = (map == current_map) && (episode == current_episode) && (nodesVersion == current_nodesVersion); + + current_episode = episode; + current_map = map; + current_nodesVersion = nodesVersion; + + if (!samelevel) { + + /* + * if (GL_DOOM){ // proff 11/99: clean the memory from textures etc. + * gld_CleanMemory(); } + */ + // free(segs); + // free(nodes); + // free(subsectors); + /* + * #ifdef GL_DOOM free(map_subsectors); #endif + */ + // free(blocklinks); + // free(blockmaplump); + // free(lines); + // free(sides); + // free(sectors); + // free(vertexes); + } + + if (nodesVersion > 0) { + this.P_LoadVertexes2(lumpnum + ML_VERTEXES, gl_lumpnum + ML_GL_VERTS); + } else { + P_LoadVertexes(lumpnum + ML_VERTEXES); + } + + P_LoadSectors(lumpnum + ML_SECTORS); + P_LoadSideDefs(lumpnum + ML_SIDEDEFS); + P_LoadLineDefs(lumpnum + ML_LINEDEFS); + P_LoadSideDefs2(lumpnum + ML_SIDEDEFS); + P_LoadLineDefs2(lumpnum + ML_LINEDEFS); + + // e6y: speedup of level reloading + // Do not reload BlockMap for same level, + // because in case of big level P_CreateBlockMap eats much time + if (!samelevel) { + P_LoadBlockMap(lumpnum + ML_BLOCKMAP); + } else { + // clear out mobj chains + if (blocklinks != null && blocklinks.length == bmapwidth * bmapheight) { + for (int i = 0; i < bmapwidth * bmapheight; i++) { + blocklinks[i] = null; + } + } else { + blocklinks = new mobj_t[bmapwidth * bmapheight]; + Arrays.setAll(blocklinks, i -> mobj_t.createOn(DOOM)); + } + } + + if (nodesVersion > 0) { + P_LoadSubsectors(gl_lumpnum + ML_GL_SSECT); + P_LoadNodes(gl_lumpnum + ML_GL_NODES); + // TODO: P_LoadGLSegs(gl_lumpnum + ML_GL_SEGS); + } else { + if (P_CheckForZDoomUncompressedNodes(lumpnum, gl_lumpnum)) { + P_LoadZNodes(lumpnum + ML_NODES, 0); + } else if (P_CheckForDeePBSPv4Nodes(lumpnum, gl_lumpnum)) { + P_LoadSubsectors_V4(lumpnum + ML_SSECTORS); + P_LoadNodes_V4(lumpnum + ML_NODES); + P_LoadSegs_V4(lumpnum + ML_SEGS); + } else { + P_LoadSubsectors(lumpnum + ML_SSECTORS); + P_LoadNodes(lumpnum + ML_NODES); + P_LoadSegs(lumpnum + ML_SEGS); + } + } + + /* + * if (GL_DOOM){ map_subsectors = calloc_IfSameLevel(map_subsectors, + * numsubsectors); } + */ + // reject loading and underflow padding separated out into new function + // P_GroupLines modified to return a number the underflow padding needs + // P_LoadReject(lumpnum, P_GroupLines()); + P_GroupLines(); + super.LoadReject(lumpnum + ML_REJECT); + + /** + * TODO: try to fix, since it seems it doesn't work + * - Good Sign 2017/05/07 + */ + // e6y + // Correction of desync on dv04-423.lmp/dv.wad + // http://www.doomworld.com/vb/showthread.php?s=&postid=627257#post627257 + // if (DoomStatus.compatibility_level>=lxdoom_1_compatibility || + // Compatibility.prboom_comp[PC.PC_REMOVE_SLIME_TRAILS.ordinal()].state) + P_RemoveSlimeTrails(); // killough 10/98: remove slime trails from wad + + // Note: you don't need to clear player queue slots -- + // a much simpler fix is in g_game.c -- killough 10/98 + DOOM.bodyqueslot = 0; + + /* cph - reset all multiplayer starts */ + for (int i = 0; i < playerstarts.length; i++) { + DOOM.playerstarts[i] = null; + } + + deathmatch_p = 0; + + for (int i = 0; i < Limits.MAXPLAYERS; i++) { + DOOM.players[i].mo = null; + } + // TODO: TracerClearStarts(); + + // Hmm? P_MapStart(); + P_LoadThings: + { + P_LoadThings(lumpnum + ML_THINGS); + } + + // if deathmatch, randomly spawn the active players + if (DOOM.deathmatch) { + for (int i = 0; i < Limits.MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + DOOM.players[i].mo = null; // not needed? - done before P_LoadThings + G_DeathMatchSpawnPlayer: + { + DOOM.DeathMatchSpawnPlayer(i); + } + } + } + } else { // if !deathmatch, check all necessary player starts actually exist + for (int i = 0; i < Limits.MAXPLAYERS; i++) { + if (DOOM.playeringame[i] && !C2JUtils.eval(DOOM.players[i].mo)) { + DOOM.doomSystem.Error("P_SetupLevel: missing player %d start\n", i + 1); + } + } + } + + // killough 3/26/98: Spawn icon landings: + // TODO: if (DM.isCommercial()) + // P.SpawnBrainTargets(); + if (!DOOM.isShareware()) { + // TODO: S.ParseMusInfo(lumpname); + } + + // clear special respawning que + DOOM.actions.ClearRespawnQueue(); + + // set up world state + P_SpawnSpecials: + { + DOOM.actions.SpawnSpecials(); + } + + // TODO: P.MapEnd(); + // preload graphics + if (DOOM.precache) { + /* @SourceCode.Compatible if together */ + R_PrecacheLevel: + { + DOOM.textureManager.PrecacheLevel(); + + // MAES: thinkers are separate than texture management. Maybe split + // sprite management as well? + DOOM.sceneRenderer.PreCacheThinkers(); + } + } + + /* + * if (GL_DOOM){ if (V_GetMode() == VID_MODEGL) { // e6y // Do not + * preprocess GL data during skipping, // because it potentially will + * not be used. // But preprocessing must be called immediately after + * stop of skipping. if (!doSkip) { // proff 11/99: calculate all OpenGL + * specific tables etc. gld_PreprocessLevel(); } } } + */ + // e6y + // TODO P_SyncWalkcam(true, true); + // TODO R_SmoothPlaying_Reset(NULL); + } + +} \ No newline at end of file diff --git a/doom/src/p/ChaseDirections.java b/doom/src/p/ChaseDirections.java new file mode 100644 index 0000000..af7cc71 --- /dev/null +++ b/doom/src/p/ChaseDirections.java @@ -0,0 +1,46 @@ +package p; + +import static data.Defines.TIC_MUL; +import static m.fixed_t.MAPFRACUNIT; + +public final class ChaseDirections { + + public static final int DI_EAST = 0; + + public static final int DI_NORTHEAST = 1; + + public static final int DI_NORTH = 2; + + public static final int DI_NORTHWEST = 3; + + public static final int DI_WEST = 4; + + public static final int DI_SOUTHWEST = 5; + + public static final int DI_SOUTH = 6; + + public static final int DI_SOUTHEAST = 7; + + public static final int DI_NODIR = 8; + + public static final int NUMDIR = 9; + + // + // P_NewChaseDir related LUT. + // + public final static int opposite[] + = {DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, DI_EAST, DI_NORTHEAST, + DI_NORTH, DI_NORTHWEST, DI_NODIR}; + + public final static int diags[] + = {DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST}; + + public final static int[] xspeed + = {MAPFRACUNIT, 47000 / TIC_MUL, 0, -47000 / TIC_MUL, -MAPFRACUNIT, -47000 / TIC_MUL, 0, 47000 / TIC_MUL}; // all + // fixed + + public final static int[] yspeed + = {0, 47000 / TIC_MUL, MAPFRACUNIT, 47000 / TIC_MUL, 0, -47000 / TIC_MUL, -MAPFRACUNIT, -47000 / TIC_MUL}; // all + // fixed + +} \ No newline at end of file diff --git a/doom/src/p/DoomPlayer.java b/doom/src/p/DoomPlayer.java new file mode 100644 index 0000000..5dd6485 --- /dev/null +++ b/doom/src/p/DoomPlayer.java @@ -0,0 +1,33 @@ +package p; + +import m.fixed_t; + +public interface DoomPlayer { + + public fixed_t + AimLineAttack(mobj_t t1, + int angle, + fixed_t distance); + + public void + LineAttack(mobj_t t1, + int angle, + fixed_t distance, + fixed_t slope, + int damage); + + void + RadiusAttack(mobj_t spot, + mobj_t source, + int damage); + + void + TouchSpecialThing(mobj_t special, + mobj_t toucher); + + void + DamageMobj(mobj_t target, + mobj_t inflictor, + mobj_t source, + int damage); +} \ No newline at end of file diff --git a/doom/src/p/DoorDefines.java b/doom/src/p/DoorDefines.java new file mode 100644 index 0000000..f26adee --- /dev/null +++ b/doom/src/p/DoorDefines.java @@ -0,0 +1,20 @@ +package p; + +import static m.fixed_t.MAPFRACUNIT; + +public final class DoorDefines { + + // Doors + public static final int VDOORSPEED = MAPFRACUNIT * 2; + public static final int VDOORWAIT = 150; + + // Lights + public static final int GLOWSPEED = 5; + + public static final int STROBEBRIGHT = 5; + + public static final int FASTDARK = 15; + + public static final int SLOWDARK = 35; + +} \ No newline at end of file diff --git a/doom/src/p/ILevelLoader.java b/doom/src/p/ILevelLoader.java new file mode 100644 index 0000000..6ff34d8 --- /dev/null +++ b/doom/src/p/ILevelLoader.java @@ -0,0 +1,84 @@ +package p; + +import defines.skill_t; +import doom.SourceCode.P_Setup; +import static doom.SourceCode.P_Setup.P_SetupLevel; +import java.io.IOException; +import rr.subsector_t; + +public interface ILevelLoader { + + // Lump order in a map WAD: each map needs a couple of lumps + // to provide a complete scene geometry description. + public static final int ML_LABEL = 0; + + /** A separator, name, ExMx or MAPxx */ + public static final int ML_THINGS = 1; + + /** Monsters, items.. */ + public static final int ML_LINEDEFS = 2; + + /** LineDefs, from editing */ + public static final int ML_SIDEDEFS = 3; + + /** SideDefs, from editing */ + public static final int ML_VERTEXES = 4; + + /** Vertices, edited and BSP splits generated */ + public static final int ML_SEGS = 5; + + /** LineSegs, from LineDefs split by BSP */ + public static final int ML_SSECTORS = 6; + + /** SubSectors, list of LineSegs */ + public static final int ML_NODES = 7; + + /** BSP nodes */ + public static final int ML_SECTORS = 8; + + /** Sectors, from editing */ + public static final int ML_REJECT = 9; + + /** LUT, sector-sector visibility */ + public static final int ML_BLOCKMAP = 10; + + // Maintain single and multi player starting spots. + public static final int MAX_DEATHMATCH_STARTS = 10; + + /** Expected lump names for verification */ + public static final String[] LABELS = {"MAPNAME", "THINGS", "LINEDEFS", "SIDEDEFS", + "VERTEXES", "SEGS", "SSECTORS", "NODES", + "SECTORS", "REJECT", "BLOCKMAP"}; + + /** P_SetupLevel + * + * @param episode + * @param map + * @param playermask + * @param skill + * @throws IOException + */ + @P_Setup.C(P_SetupLevel) + void SetupLevel(int episode, int map, int playermask, skill_t skill) throws IOException; + + /** + * P_SetThingPosition Links a thing into both a block and a subsector based + * on it's x y. Sets thing.subsector properly + * + * + * @param thing + */ + void SetThingPosition(mobj_t thing); + + /** + * R_PointInSubsector + * + * MAES: it makes more sense to have this here. + * + * @param x fixed + * @param y fixed + * + */ + subsector_t PointInSubsector(int x, int y); + +} \ No newline at end of file diff --git a/doom/src/p/ISightChecker.java b/doom/src/p/ISightChecker.java new file mode 100644 index 0000000..51894d5 --- /dev/null +++ b/doom/src/p/ISightChecker.java @@ -0,0 +1,11 @@ +package p; + +public interface ISightChecker { + + public void setZStartTopBOttom(int zstart, int top, int bottom); + + public void setSTrace(mobj_t t1, mobj_t t2); + + public boolean CrossBSPNode(int bspnum); + +} \ No newline at end of file diff --git a/doom/src/p/Interceptable.java b/doom/src/p/Interceptable.java new file mode 100644 index 0000000..819b6f3 --- /dev/null +++ b/doom/src/p/Interceptable.java @@ -0,0 +1,5 @@ +package p; + +public interface Interceptable { + +} \ No newline at end of file diff --git a/doom/src/p/LevelLoader.java b/doom/src/p/LevelLoader.java new file mode 100644 index 0000000..a3af585 --- /dev/null +++ b/doom/src/p/LevelLoader.java @@ -0,0 +1,943 @@ +package p; + +import static data.Defines.MAPBLOCKSHIFT; +import static data.Defines.NF_SUBSECTOR; +import static data.Defines.NF_SUBSECTOR_CLASSIC; +import static data.Defines.PU_LEVEL; +import static data.Limits.MAXPLAYERS; +import static data.Limits.MAXRADIUS; +import data.maplinedef_t; +import data.mapnode_t; +import data.mapsector_t; +import data.mapseg_t; +import data.mapsidedef_t; +import data.mapsubsector_t; +import data.mapthing_t; +import data.mapvertex_t; +import defines.skill_t; +import defines.slopetype_t; +import doom.CommandVariable; +import doom.DoomMain; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.BBox; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedDiv; +import mochadoom.Loggers; +import rr.line_t; +import static rr.line_t.ML_TWOSIDED; +import rr.node_t; +import rr.sector_t; +import rr.seg_t; +import rr.side_t; +import rr.subsector_t; +import rr.vertex_t; +import s.degenmobj_t; +import static utils.C2JUtils.flags; +import static utils.GenericCopy.malloc; +import w.DoomBuffer; + +//Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: LevelLoader.java,v 1.44 2012/09/24 17:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Do all the WAD I/O, get map description, +// set up initial state and misc. LUTs. +// +//----------------------------------------------------------------------------- +public class LevelLoader extends AbstractLevelLoader { + + private static final Logger LOGGER = Loggers.getLogger(LevelLoader.class.getName()); + + public static final String rcsid = "$Id: LevelLoader.java,v 1.44 2012/09/24 17:16:23 velktron Exp $"; + + public LevelLoader(DoomMain DM) { + super(DM); + // Traditional loader sets limit. + deathmatchstarts = new mapthing_t[MAX_DEATHMATCH_STARTS]; + } + + /** + * P_LoadVertexes + * + * @throws IOException + */ + public void LoadVertexes(int lump) throws IOException { + // Make a lame-ass attempt at loading some vertexes. + + // Determine number of lumps: + // total lump length / vertex record length. + numvertexes = DOOM.wadLoader.LumpLength(lump) / mapvertex_t.sizeOf(); + + // Load data into cache. + // MAES: we now have a mismatch between memory/disk: in memory, we need an array. + // On disk, we have a single lump/blob. Thus, we need to find a way to deserialize this... + vertexes = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numvertexes, vertex_t::new, vertex_t[]::new); + + // Copy and convert vertex coordinates, + // MAES: not needed. Intermediate mapvertex_t struct skipped. + } + + /** + * P_LoadSegs + * + * @throws IOException + */ + public void LoadSegs(int lump) throws IOException { + + mapseg_t[] data; + mapseg_t ml; + seg_t li; + line_t ldef; + int linedef; + int side; + + // Another disparity between disk/memory. Treat it the same as VERTEXES. + numsegs = DOOM.wadLoader.LumpLength(lump) / mapseg_t.sizeOf(); + segs = malloc(seg_t::new, seg_t[]::new, numsegs); + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsegs, mapseg_t::new, mapseg_t[]::new); + + // We're not done yet! + for (int i = 0; i < numsegs; i++) { + li = segs[i]; + ml = data[i]; + li.v1 = vertexes[ml.v1]; + li.v2 = vertexes[ml.v2]; + li.assignVertexValues(); + + li.angle = ((ml.angle) << 16) & 0xFFFFFFFFL; + li.offset = (ml.offset) << 16; + linedef = ml.linedef; + li.linedef = ldef = lines[linedef]; + side = ml.side; + li.sidedef = sides[ldef.sidenum[side]]; + li.frontsector = sides[ldef.sidenum[side]].sector; + if (flags(ldef.flags, ML_TWOSIDED)) { + // MAES: Fix double sided without back side. E.g. Linedef 16103 in Europe.wad + if (ldef.sidenum[side ^ 1] != line_t.NO_INDEX) { + li.backsector = sides[ldef.sidenum[side ^ 1]].sector; + } + // Fix two-sided with no back side. + //else { + //li.backsector=null; + //ldef.flags^=ML_TWOSIDED; + //} + } else { + li.backsector = null; + } + } + + } + + /** + * P_LoadSubsectors + * + * @throws IOException + */ + public void LoadSubsectors(int lump) throws IOException { + mapsubsector_t ms; + subsector_t ss; + mapsubsector_t[] data; + + numsubsectors = DOOM.wadLoader.LumpLength(lump) / mapsubsector_t.sizeOf(); + subsectors = malloc(subsector_t::new, subsector_t[]::new, numsubsectors); + + // Read "mapsubsectors" + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsubsectors, mapsubsector_t::new, mapsubsector_t[]::new); + + for (int i = 0; i < numsubsectors; i++) { + ms = data[i]; + ss = subsectors[i]; + ss.numlines = ms.numsegs; + ss.firstline = ms.firstseg; + } + + } + + /** + * P_LoadSectors + * + * @throws IOException + */ + public void LoadSectors(int lump) throws IOException { + mapsector_t[] data; + mapsector_t ms; + sector_t ss; + + numsectors = DOOM.wadLoader.LumpLength(lump) / mapsector_t.sizeOf(); + sectors = malloc(sector_t::new, sector_t[]::new, numsectors); + + // Read "mapsectors" + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsectors, mapsector_t::new, mapsector_t[]::new); + + for (int i = 0; i < numsectors; i++) { + ms = data[i]; + ss = sectors[i]; + ss.floorheight = ms.floorheight << FRACBITS; + ss.ceilingheight = ms.ceilingheight << FRACBITS; + ss.floorpic = (short) DOOM.textureManager.FlatNumForName(ms.floorpic); + ss.ceilingpic = (short) DOOM.textureManager.FlatNumForName(ms.ceilingpic); + ss.lightlevel = ms.lightlevel; + ss.special = ms.special; + ss.tag = ms.tag; + ss.thinglist = null; + ss.id = i; + ss.TL = this.DOOM.actions; + ss.RND = this.DOOM.random; + } + + } + + /** + * P_LoadNodes + * + * @throws IOException + */ + public void LoadNodes(int lump) throws IOException { + mapnode_t[] data; + int i; + int j; + int k; + mapnode_t mn; + node_t no; + + numnodes = DOOM.wadLoader.LumpLength(lump) / mapnode_t.sizeOf(); + nodes = malloc(node_t::new, node_t[]::new, numnodes); + + // Read "mapnodes" + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numnodes, mapnode_t::new, mapnode_t[]::new); + + for (i = 0; i < numnodes; i++) { + mn = data[i]; + no = nodes[i]; + + no.x = mn.x << FRACBITS; + no.y = mn.y << FRACBITS; + no.dx = mn.dx << FRACBITS; + no.dy = mn.dy << FRACBITS; + for (j = 0; j < 2; j++) { + // e6y: support for extended nodes + no.children[j] = (char) mn.children[j]; + + // e6y: support for extended nodes + if (no.children[j] == 0xFFFF) { + no.children[j] = 0xFFFFFFFF; + } else if (flags(no.children[j], NF_SUBSECTOR_CLASSIC)) { + // Convert to extended type + no.children[j] &= ~NF_SUBSECTOR_CLASSIC; + + // haleyjd 11/06/10: check for invalid subsector reference + if (no.children[j] >= numsubsectors) { + LOGGER.log(Level.WARNING, String.format( + "P_LoadNodes: BSP tree references invalid subsector %d.", + no.children[j])); + no.children[j] = 0; + } + + no.children[j] |= NF_SUBSECTOR; + } + + for (k = 0; k < 4; k++) { + no.bbox[j].set(k, mn.bbox[j][k] << FRACBITS); + } + } + } + + } + + /** + * P_LoadThings + * + * @throws IOException + */ + public void LoadThings(int lump) throws IOException { + mapthing_t[] data; + mapthing_t mt; + int numthings; + boolean spawn; + + numthings = DOOM.wadLoader.LumpLength(lump) / mapthing_t.sizeOf(); + // VERY IMPORTANT: since now caching is near-absolute, + // the mapthing_t instances must be CLONED rather than just + // referenced, otherwise missing mobj bugs start happening. + + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numthings, mapthing_t::new, mapthing_t[]::new); + + for (int i = 0; i < numthings; i++) { + mt = data[i]; + spawn = true; + + // Do not spawn cool, new monsters if !commercial + if (!DOOM.isCommercial()) { + switch (mt.type) { + case 68: // Arachnotron + case 64: // Archvile + case 88: // Boss Brain + case 89: // Boss Shooter + case 69: // Hell Knight + case 67: // Mancubus + case 71: // Pain Elemental + case 65: // Former Human Commando + case 66: // Revenant + case 84: // Wolf SS + spawn = false; + break; + } + } + if (spawn == false) { + break; + } + + // Do spawn all other stuff. + // MAES: we have loaded the shit with the proper endianness, so no fucking around, bitch. + /*mt.x = SHORT(mt.x); + mt.y = SHORT(mt.y); + mt.angle = SHORT(mt.angle); + mt.type = SHORT(mt.type); + mt.options = SHORT(mt.options);*/ + //System.out.printf("Spawning %d %s\n",i,mt.type); + DOOM.actions.SpawnMapThing(mt); + } + + // Status may have changed. It's better to release the resources anyway + //W.UnlockLumpNum(lump); + } + + /** + * P_LoadLineDefs + * Also counts secret lines for intermissions. + * + * @throws IOException + */ + public void LoadLineDefs(int lump) throws IOException { + maplinedef_t[] data; + maplinedef_t mld; + line_t ld; + vertex_t v1; + vertex_t v2; + + numlines = DOOM.wadLoader.LumpLength(lump) / maplinedef_t.sizeOf(); + lines = malloc(line_t::new, line_t[]::new, numlines); + + // Check those actually used in sectors, later on. + used_lines = new boolean[numlines]; + + // read "maplinedefs" + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numlines, maplinedef_t::new, maplinedef_t[]::new); + + for (int i = 0; i < numlines; i++) { + mld = data[i]; + ld = lines[i]; + + ld.flags = mld.flags; + ld.special = mld.special; + ld.tag = mld.tag; + v1 = ld.v1 = vertexes[(char) mld.v1]; + v2 = ld.v2 = vertexes[(char) mld.v2]; + ld.dx = v2.x - v1.x; + ld.dy = v2.y - v1.y; + // Map value semantics. + ld.assignVertexValues(); + + if (ld.dx == 0) { + ld.slopetype = slopetype_t.ST_VERTICAL; + } else if (ld.dy == 0) { + ld.slopetype = slopetype_t.ST_HORIZONTAL; + } else { + if (FixedDiv(ld.dy, ld.dx) > 0) { + ld.slopetype = slopetype_t.ST_POSITIVE; + } else { + ld.slopetype = slopetype_t.ST_NEGATIVE; + } + } + + if (v1.x < v2.x) { + ld.bbox[BOXLEFT] = v1.x; + ld.bbox[BOXRIGHT] = v2.x; + } else { + ld.bbox[BOXLEFT] = v2.x; + ld.bbox[BOXRIGHT] = v1.x; + } + + if (v1.y < v2.y) { + ld.bbox[BOXBOTTOM] = v1.y; + ld.bbox[BOXTOP] = v2.y; + } else { + ld.bbox[BOXBOTTOM] = v2.y; + ld.bbox[BOXTOP] = v1.y; + } + + ld.sidenum[0] = mld.sidenum[0]; + ld.sidenum[1] = mld.sidenum[1]; + + // Sanity check for two-sided without two valid sides. + if (flags(ld.flags, ML_TWOSIDED)) { + if ((ld.sidenum[0] == line_t.NO_INDEX) || (ld.sidenum[1] == line_t.NO_INDEX)) { + // Well, dat ain't so tu-sided now, ey esse? + ld.flags ^= ML_TWOSIDED; + } + } + + // Front side defined without a valid frontsector. + if (ld.sidenum[0] != line_t.NO_INDEX) { + ld.frontsector = sides[ld.sidenum[0]].sector; + if (ld.frontsector == null) { // // Still null? Bad map. Map to dummy. + ld.frontsector = dummy_sector; + } + + } else { + ld.frontsector = null; + } + + // back side defined without a valid backsector. + if (ld.sidenum[1] != line_t.NO_INDEX) { + ld.backsector = sides[ld.sidenum[1]].sector; + if (ld.backsector == null) { // Still null? Bad map. Map to dummy. + ld.backsector = dummy_sector; + } + } else { + ld.backsector = null; + } + + // If at least one valid sector is defined, then it's not null. + if (ld.frontsector != null || ld.backsector != null) { + this.used_lines[i] = true; + } + + } + + } + + /** + * P_LoadSideDefs + */ + public void LoadSideDefs(int lump) throws IOException { + mapsidedef_t[] data; + mapsidedef_t msd; + side_t sd; + + numsides = DOOM.wadLoader.LumpLength(lump) / mapsidedef_t.sizeOf(); + sides = malloc(side_t::new, side_t[]::new, numsides); + + data = DOOM.wadLoader.CacheLumpNumIntoArray(lump, numsides, mapsidedef_t::new, mapsidedef_t[]::new); + + for (int i = 0; i < numsides; i++) { + msd = data[i]; + sd = sides[i]; + + sd.textureoffset = (msd.textureoffset) << FRACBITS; + sd.rowoffset = (msd.rowoffset) << FRACBITS; + sd.toptexture = (short) DOOM.textureManager.TextureNumForName(msd.toptexture); + sd.bottomtexture = (short) DOOM.textureManager.TextureNumForName(msd.bottomtexture); + sd.midtexture = (short) DOOM.textureManager.TextureNumForName(msd.midtexture); + if (msd.sector < 0) { + sd.sector = dummy_sector; + } else { + sd.sector = sectors[msd.sector]; + } + } + } + + // MAES 22/5/2011 This hack added for PHOBOS2.WAD, in order to + // accomodate for some linedefs having a sector number of "-1". + // Any negative sector will get rewired to this dummy sector. + // PROBABLY, this will handle unused sector/linedefes cleanly? + sector_t dummy_sector = new sector_t(); + + /** + * P_LoadBlockMap + * + * @throws IOException + * + * TODO: generate BLOCKMAP dynamically to + * handle missing cases and increase accuracy. + * + */ + public void LoadBlockMap(int lump) throws IOException { + int count = 0; + + if (DOOM.cVarManager.bool(CommandVariable.BLOCKMAP) || DOOM.wadLoader.LumpLength(lump) < 8 + || (count = DOOM.wadLoader.LumpLength(lump) / 2) >= 0x10000) // e6y + { + CreateBlockMap(); + } else { + + DoomBuffer data = (DoomBuffer) DOOM.wadLoader.CacheLumpNum(lump, PU_LEVEL, DoomBuffer.class); + count = DOOM.wadLoader.LumpLength(lump) / 2; + blockmaplump = new int[count]; + + data.setOrder(ByteOrder.LITTLE_ENDIAN); + data.rewind(); + data.readCharArray(blockmaplump, count); + + // Maes: first four shorts are header data. + bmaporgx = blockmaplump[0] << FRACBITS; + bmaporgy = blockmaplump[1] << FRACBITS; + bmapwidth = blockmaplump[2]; + bmapheight = blockmaplump[3]; + + // MAES: use killough's code to convert terminators to -1 beforehand + for (int i = 4; i < count; i++) { + short t = (short) blockmaplump[i]; // killough 3/1/98 + blockmaplump[i] = (int) (t == -1 ? -1l : t & 0xffff); + } + + // haleyjd 03/04/10: check for blockmap problems + // http://www.doomworld.com/idgames/index.php?id=12935 + if (!VerifyBlockMap(count)) { + LOGGER.log(Level.WARNING, "P_LoadBlockMap: erroneous BLOCKMAP lump may cause crashes.\n"); + LOGGER.log(Level.WARNING, "P_LoadBlockMap: use \"-blockmap\" command line switch for rebuilding\n"); + } + + } + count = bmapwidth * bmapheight; + + // IMPORTANT MODIFICATION: no need to have both blockmaplump AND blockmap. + // If the offsets in the lump are OK, then we can modify them (remove 4) + // and copy the rest of the data in one single data array. This avoids + // reserving memory for two arrays (we can't simply alias one in Java) + blockmap = new int[blockmaplump.length - 4]; + + // Offsets are relative to START OF BLOCKMAP, and IN SHORTS, not bytes. + for (int i = 0; i < blockmaplump.length - 4; i++) { + // Modify indexes so that we don't need two different lumps. + // Can probably be further optimized if we simply shift everything backwards. + // and reuse the same memory space. + if (i < count) { + blockmaplump[i] = blockmaplump[i + 4] - 4; + } else { + // Make terminators definitively -1, different that 0xffff + short t = (short) blockmaplump[i + 4]; // killough 3/1/98 + blockmaplump[i] = (int) (t == -1 ? -1l : t & 0xffff); + } + } + + // clear out mobj chains + // ATTENTION! BUG!!! + // If blocklinks are "cleared" to void -but instantiated- objects, + // very bad bugs happen, especially the second time a level is re-instantiated. + // Probably caused other bugs as well, as an extra object would appear in iterators. + if (blocklinks != null && blocklinks.length == count) { + for (int i = 0; i < count; i++) { + blocklinks[i] = null; + } + } else { + blocklinks = new mobj_t[count]; + } + + // Bye bye. Not needed. + blockmap = blockmaplump; + } + + /** + * P_GroupLines + * Builds sector line lists and subsector sector numbers. + * Finds block bounding boxes for sectors. + */ + public void GroupLines() { + int total; + line_t li; + sector_t sector; + subsector_t ss; + seg_t seg; + int[] bbox = new int[4]; + int block; + + // look up sector number for each subsector + for (int i = 0; i < numsubsectors; i++) { + ss = subsectors[i]; + seg = segs[ss.firstline]; + ss.sector = seg.sidedef.sector; + } + + //linebuffer=new line_t[numsectors][0]; + // count number of lines in each sector + total = 0; + + for (int i = 0; i < numlines; i++) { + li = lines[i]; + total++; + li.frontsector.linecount++; + + if ((li.backsector != null) && (li.backsector != li.frontsector)) { + li.backsector.linecount++; + total++; + } + + } + + // build line tables for each sector + // MAES: we don't really need this in Java. + // linebuffer = new line_t[total]; + // int linebuffercount=0; + // We scan through ALL sectors. + for (int i = 0; i < numsectors; i++) { + sector = sectors[i]; + BBox.ClearBox(bbox); + //sector->lines = linebuffer; + // We can just construct line tables of the correct size + // for each sector. + int countlines = 0; + // We scan through ALL lines.... + + // System.out.println(i+ ": looking for sector -> "+sector); + for (int j = 0; j < numlines; j++) { + li = lines[j]; + + //System.out.println(j+ " front "+li.frontsector+ " back "+li.backsector); + if (li.frontsector == sector || li.backsector == sector) { + // This sector will have one more line. + countlines++; + // Expand bounding box... + BBox.AddToBox(bbox, li.v1.x, li.v1.y); + BBox.AddToBox(bbox, li.v2.x, li.v2.y); + } + } + + // So, this sector must have that many lines. + sector.lines = new line_t[countlines]; + + int addedlines = 0; + int pointline = 0; + + // Add actual lines into sectors. + for (int j = 0; j < numlines; j++) { + li = lines[j]; + // If + if (li.frontsector == sector || li.backsector == sector) { + // This sector will have one more line. + sectors[i].lines[pointline++] = lines[j]; + addedlines++; + } + } + + if (addedlines != sector.linecount) { + DOOM.doomSystem.Error("P_GroupLines: miscounted"); + } + + // set the degenmobj_t to the middle of the bounding box + sector.soundorg = new degenmobj_t(((bbox[BOXRIGHT] + bbox[BOXLEFT]) / 2), + ((bbox[BOXTOP] + bbox[BOXBOTTOM]) / 2), (sector.ceilingheight - sector.floorheight) / 2); + + // adjust bounding box to map blocks + block = (bbox[BOXTOP] - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT; + block = block >= bmapheight ? bmapheight - 1 : block; + sector.blockbox[BOXTOP] = block; + + block = (bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector.blockbox[BOXBOTTOM] = block; + + block = (bbox[BOXRIGHT] - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT; + block = block >= bmapwidth ? bmapwidth - 1 : block; + sector.blockbox[BOXRIGHT] = block; + + block = (bbox[BOXLEFT] - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT; + block = block < 0 ? 0 : block; + sector.blockbox[BOXLEFT] = block; + } + + } + + @Override + public void + SetupLevel(int episode, + int map, + int playermask, + skill_t skill) { + int i; + String lumpname; + int lumpnum; + + try { + DOOM.totalkills = DOOM.totalitems = DOOM.totalsecret = DOOM.wminfo.maxfrags = 0; + DOOM.wminfo.partime = 180; + for (i = 0; i < MAXPLAYERS; i++) { + DOOM.players[i].killcount = DOOM.players[i].secretcount + = DOOM.players[i].itemcount = 0; + } + + // Initial height of PointOfView + // will be set by player think. + DOOM.players[DOOM.consoleplayer].viewz = 1; + + // Make sure all sounds are stopped before Z_FreeTags. + DOOM.doomSound.Start(); + + /* + #if 0 // UNUSED + if (debugfile) + { + Z_FreeTags (PU_LEVEL, MAXINT); + Z_FileDumpHeap (debugfile); + } + else + #endif + */ + // Z_FreeTags (PU_LEVEL, PU_PURGELEVEL-1); + // UNUSED W_Profile (); + DOOM.actions.InitThinkers(); + + // if working with a development map, reload it + DOOM.wadLoader.Reload(); + + // find map name + if (DOOM.isCommercial()) { + if (map < 10) { + lumpname = "MAP0" + map; + } else { + lumpname = "MAP" + map; + } + } else { + lumpname = ("E" + + (char) ('0' + episode) + + "M" + + (char) ('0' + map)); + } + + lumpnum = DOOM.wadLoader.GetNumForName(lumpname); + + DOOM.leveltime = 0; + + if (!DOOM.wadLoader.verifyLumpName(lumpnum + ML_BLOCKMAP, LABELS[ML_BLOCKMAP])) { + LOGGER.log(Level.WARNING, "Blockmap missing!"); + } + + // note: most of this ordering is important + this.LoadVertexes(lumpnum + ML_VERTEXES); + this.LoadSectors(lumpnum + ML_SECTORS); + this.LoadSideDefs(lumpnum + ML_SIDEDEFS); + this.LoadLineDefs(lumpnum + ML_LINEDEFS); + this.LoadSubsectors(lumpnum + ML_SSECTORS); + this.LoadNodes(lumpnum + ML_NODES); + this.LoadSegs(lumpnum + ML_SEGS); + + // MAES: in order to apply optimizations and rebuilding, order must be changed. + this.LoadBlockMap(lumpnum + ML_BLOCKMAP); + //this.SanitizeBlockmap(); + //this.getMapBoundingBox(); + + this.LoadReject(lumpnum + ML_REJECT); + + this.GroupLines(); + + DOOM.bodyqueslot = 0; + // Reset to "deathmatch starts" + DOOM.deathmatch_p = 0; + this.LoadThings(lumpnum + ML_THINGS); + + // if deathmatch, randomly spawn the active players + if (DOOM.deathmatch) { + for (i = 0; i < MAXPLAYERS; i++) { + if (DOOM.playeringame[i]) { + DOOM.players[i].mo = null; + DOOM.DeathMatchSpawnPlayer(i); + } + } + + } + + // clear special respawning que + DOOM.actions.ClearRespawnQueue(); + + // set up world state + DOOM.actions.SpawnSpecials(); + + // build subsector connect matrix + // UNUSED P_ConnectSubsectors (); + // preload graphics + if (DOOM.precache) { + DOOM.textureManager.PrecacheLevel(); + // MAES: thinkers are separate than texture management. Maybe split sprite management as well? + DOOM.sceneRenderer.PreCacheThinkers(); + + } + + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while loading level", e); + } + } + +} + +//$Log: LevelLoader.java,v $ +//Revision 1.44 2012/09/24 17:16:23 velktron +//Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +//Revision 1.43.2.2 2012/09/24 16:57:16 velktron +//Addressed generics warnings. +// +//Revision 1.43.2.1 2012/03/26 09:53:44 velktron +//Use line_t.NO_INDEX for good measure, when possible. +// +//Revision 1.43 2011/11/03 15:19:51 velktron +//Adapted to using ISpriteManager +// +//Revision 1.42 2011/10/07 16:05:52 velktron +//Now using line_t for ML_* definitions. +// +//Revision 1.41 2011/10/06 16:44:32 velktron +//Proper support for extended nodes, made reject loading into a separate method. +// +//Revision 1.40 2011/09/30 15:20:24 velktron +//Very modified, useless SanitizeBlockmap method ditched. +//Common utility methods moved to superclass. Shares blockmap checking and generation +//with Boom-derived code. Now capable of running Europe.wad. +//TODO: Blockmap generation can be really slow on large levels. +//Optimize better for Java, or parallelize. +// +//Revision 1.39 2011/09/29 17:22:08 velktron +//Blockchain terminators are now -1 (extended) +// +//Revision 1.38 2011/09/29 17:11:32 velktron +//Blockmap optimizations. +// +//Revision 1.37 2011/09/29 15:17:48 velktron +//SetupLevel can propagate exceptions. +// +//Revision 1.36 2011/09/29 13:28:01 velktron +//Extends AbstractLevelLoader +// +//Revision 1.35 2011/09/27 18:04:36 velktron +//Fixed major blockmap bug +// +//Revision 1.34 2011/09/27 16:00:20 velktron +//Minor blockmap stuff. +// +//Revision 1.33 2011/08/24 15:52:04 velktron +//Sets proper ISoundOrigin for sectors (height, too) +// +//Revision 1.32 2011/08/24 15:00:34 velktron +//Improved version, now using createArrayOfObjects. Much better syntax. +// +//Revision 1.31 2011/08/23 16:17:22 velktron +//Got rid of Z remnants. +// +//Revision 1.30 2011/07/27 21:26:19 velktron +//Quieted down debugging for v1.5 release +// +//Revision 1.29 2011/07/25 19:56:53 velktron +//reject matrix size bugfix, fron danmaku branch. +// +//Revision 1.28 2011/07/22 15:37:52 velktron +//Began blockmap autogen code...still WIP +// +//Revision 1.27 2011/07/20 16:14:45 velktron +//Bullet-proofing vs missing or corrupt REJECT table. TODO: built-in system to re-compute it. +// +//Revision 1.26 2011/06/18 23:25:33 velktron +//Removed debugginess +// +//Revision 1.25 2011/06/18 23:21:26 velktron +//-id +// +//Revision 1.24 2011/06/18 23:18:24 velktron +//Added sanitization for broken two-sided sidedefs, and semi-support for extended blockmaps. +// +//Revision 1.23 2011/05/24 11:31:47 velktron +//Adapted to IDoomStatusBar +// +//Revision 1.22 2011/05/22 21:09:34 velktron +//Added spechit overflow handling, and unused linedefs (with -1 sector) handling. +// +//Revision 1.21 2011/05/21 14:53:57 velktron +//Adapted to use new gamemode system. +// +//Revision 1.20 2011/05/20 14:52:23 velktron +//Moved several function from the Renderer and Action code in here, since it made more sense. +// +//Revision 1.19 2011/05/18 16:55:44 velktron +//TEMPORARY TESTING VERSION, DO NOT USE +// +//Revision 1.18 2011/05/17 16:51:20 velktron +//Switched to DoomStatus +// +//Revision 1.17 2011/05/10 10:39:18 velktron +//Semi-playable Techdemo v1.3 milestone +// +//Revision 1.16 2011/05/05 17:24:22 velktron +//Started merging more of _D_'s changes. +// +//Revision 1.15 2010/12/20 17:15:08 velktron +//Made the renderer more OO -> TextureManager and other changes as well. +// +//Revision 1.14 2010/11/22 21:41:22 velktron +//Parallel rendering...sort of.It works, but either the barriers are broken or it's simply not worthwhile at this point :-/ +// +//Revision 1.13 2010/11/22 14:54:53 velktron +//Greater objectification of sectors etc. +// +//Revision 1.12 2010/11/22 01:17:16 velktron +//Fixed blockmap (for the most part), some actions implemented and functional, ambient animation/lighting functional. +// +//Revision 1.11 2010/11/14 20:00:21 velktron +//Bleeding floor bug fixed! +// +//Revision 1.10 2010/11/03 16:48:04 velktron +//"Bling" view angles fixed (perhaps related to the "bleeding line bug"?) +// +//Revision 1.9 2010/09/27 02:27:29 velktron +//BEASTLY update +// +//Revision 1.8 2010/09/23 20:36:45 velktron +//*** empty log message *** +// +//Revision 1.7 2010/09/23 15:11:57 velktron +//A bit closer... +// +//Revision 1.6 2010/09/22 16:40:02 velktron +//MASSIVE changes in the status passing model. +//DoomMain and DoomGame unified. +//Doomstat merged into DoomMain (now status and game functions are one). +// +//Most of DoomMain implemented. Possible to attempt a "classic type" start but will stop when reading sprites. +// +//Revision 1.5 2010/09/21 15:53:37 velktron +//Split the Map ...somewhat... +// +//Revision 1.4 2010/09/14 15:34:01 velktron +//The enormity of this commit is incredible (pun intended) +// +//Revision 1.3 2010/09/08 15:22:18 velktron +//x,y coords in some structs as value semantics. Possible speed increase? +// +//Revision 1.2 2010/09/02 15:56:54 velktron +//Bulk of unified renderer copyediting done. +// +//Some changes like e.g. global separate limits class and instance methods for seg_t and node_t introduced. +// +//Revision 1.1 2010/09/01 15:53:42 velktron +//Graphics data loader implemented....still need to figure out how column caching works, though. +// +//Revision 1.4 2010/08/19 23:14:49 velktron +//Automap +// +//Revision 1.3 2010/08/13 14:06:36 velktron +//Endlevel screen fully functional! +// +//Revision 1.2 2010/08/11 16:31:34 velktron +//Map loading works! Check out LevelLoaderTester for more. +// +//Revision 1.1 2010/08/10 16:41:57 velktron +//Threw some work into map loading. +// \ No newline at end of file diff --git a/doom/src/p/MapUtils.java b/doom/src/p/MapUtils.java new file mode 100644 index 0000000..896afdd --- /dev/null +++ b/doom/src/p/MapUtils.java @@ -0,0 +1,152 @@ +package p; + +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import static utils.C2JUtils.eval; + +public class MapUtils { + + /** + * AproxDistance + * Gives an estimation of distance (not exact) + * + * @param dx fixed_t + * @param dy fixed_t + * @return fixed_t + */ + // + public static int + AproxDistance(int dx, + int dy) { + dx = Math.abs(dx); + dy = Math.abs(dy); + if (dx < dy) { + return dx + dy - (dx >> 1); + } + return dx + dy - (dy >> 1); + } + + /** + * P_InterceptVector + * Returns the fractional intercept point + * along the first divline. + * This is only called by the addthings + * and addlines traversers. + * + * @return int to be treated as fixed_t + */ + public static int + InterceptVector(divline_t v2, + divline_t v1) { + int frac, num, den; // fixed_t + + den = FixedMul(v1.dy >> 8, v2.dx) - FixedMul(v1.dx >> 8, v2.dy); + + if (den == 0) { + return 0; + } + // I_Error ("P_InterceptVector: parallel"); + + num + = FixedMul((v1.x - v2.x) >> 8, v1.dy) + + FixedMul((v2.y - v1.y) >> 8, v1.dx); + + frac = FixedDiv(num, den); + + return frac; + /* + #else // UNUSED, float debug. + float frac; + float num; + float den; + float v1x; + float v1y; + float v1dx; + float v1dy; + float v2x; + float v2y; + float v2dx; + float v2dy; + + v1x = (float)v1.x/FRACUNIT; + v1y = (float)v1.y/FRACUNIT; + v1dx = (float)v1.dx/FRACUNIT; + v1dy = (float)v1.dy/FRACUNIT; + v2x = (float)v2.x/FRACUNIT; + v2y = (float)v2.y/FRACUNIT; + v2dx = (float)v2.dx/FRACUNIT; + v2dy = (float)v2.dy/FRACUNIT; + + den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0) + return 0; // parallel + + num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + frac = num / den; + + return frac*FRACUNIT; + #endif */ + } + + + /* cph - this is killough's 4/19/98 version of P_InterceptVector and + * P_InterceptVector2 (which were interchangeable). We still use this + * in compatibility mode. */ + private static final int P_InterceptVector2(final divline_t v2, final divline_t v1) { + int den; + return eval(den = FixedMul(v1.dy >> 8, v2.dx) - FixedMul(v1.dx >> 8, v2.dy)) + ? FixedDiv(FixedMul((v1.x - v2.x) >> 8, v1.dy) + + FixedMul((v2.y - v1.y) >> 8, v1.dx), den) : 0; + } + + /** Used by CrossSubSector + * + * @param v2 + * @param v1 + * @return + */ + public static final int P_InterceptVector(final divline_t v2, final divline_t v1) { + if (false/*compatibility_level < prboom_4_compatibility*/) { + return P_InterceptVector2(v2, v1); + } else { + /* cph - This was introduced at prboom_4_compatibility - no precision/overflow problems */ + long den = (long) v1.dy * v2.dx - (long) v1.dx * v2.dy; + den >>= 16; + if (!eval(den)) { + return 0; + } + return (int) (((long) (v1.x - v2.x) * v1.dy - (long) (v1.y - v2.y) * v1.dx) / den); + } + } + + /** + * P_InterceptVector2 Returns the fractional intercept point along the + * first divline. This is only called by the addthings and addlines + * traversers. + * + * @param v2 + * @param v1 + * @returnP_InterceptVector2 + */ + public static final int InterceptVector2(divline_t v2, divline_t v1) { + int frac; // fixed_t + int num; // fixed_t + int den; // fixed_t + + den = FixedMul(v1.dy >> 8, v2.dx) - FixedMul(v1.dx >> 8, v2.dy); + + if (den == 0) { + return 0; + } + // I_Error ("P_InterceptVector: parallel"); + + num + = FixedMul((v1.x - v2.x) >> 8, v1.dy) + + FixedMul((v2.y - v1.y) >> 8, v1.dx); + frac = FixedDiv(num, den); + + return frac; + } + +} \ No newline at end of file diff --git a/doom/src/p/MobjFlags.java b/doom/src/p/MobjFlags.java new file mode 100644 index 0000000..e12f048 --- /dev/null +++ b/doom/src/p/MobjFlags.java @@ -0,0 +1,107 @@ +package p; + +/** YEAH, I'M USING THE CONSTANTS INTERFACE PATTERN. DEAL WITH IT */ +public interface MobjFlags { + // // MF_ flags for mobjs. + + // Call P_SpecialThing when touched. + public static final long MF_SPECIAL = 1; + // Blocks. + public static final long MF_SOLID = 2; + // Can be hit. + public static final long MF_SHOOTABLE = 4; + // Don't use the sector links (invisible but touchable). + public static final long MF_NOSECTOR = 8; + // Don't use the blocklinks (inert but displayable) + public static final long MF_NOBLOCKMAP = 16; + + // Not to be activated by sound, deaf monster. + public static final long MF_AMBUSH = 32; + // Will try to attack right back. + public static final long MF_JUSTHIT = 64; + // Will take at least one step before attacking. + public static final long MF_JUSTATTACKED = 128; + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + public static final long MF_SPAWNCEILING = 256; + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + public static final long MF_NOGRAVITY = 512; + + // Movement flags. + // This allows jumps from high places. + public static final long MF_DROPOFF = 0x400; + // For players, will pick up items. + public static final long MF_PICKUP = 0x800; + // Player cheat. ??? + public static final int MF_NOCLIP = 0x1000; + // Player: keep info about sliding along walls. + public static final int MF_SLIDE = 0x2000; + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + public static final int MF_FLOAT = 0x4000; + // Don't cross lines + // ??? or look at heights on teleport. + public static final int MF_TELEPORT = 0x8000; + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + public static final int MF_MISSILE = 0x10000; + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + public static final int MF_DROPPED = 0x20000; + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + public static final int MF_SHADOW = 0x40000; + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + public static final long MF_NOBLOOD = 0x80000; + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + public static final long MF_CORPSE = 0x100000; + // Floating to a height for a move, ??? + // don't auto float to target's height. + public static final long MF_INFLOAT = 0x200000; + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + public static final long MF_COUNTKILL = 0x400000; + + // On picking up, count this item object + // towards intermission item total. + public static final long MF_COUNTITEM = 0x800000; + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + public static final long MF_SKULLFLY = 0x1000000; + + // Don't spawn this object + // in death match mode (e.g. key cards). + public static final long MF_NOTDMATCH = 0x2000000; + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + public static final long MF_TRANSLATION = 0xc000000; + // Hmm ???. + public static final long MF_TRANSSHIFT = 26; + + public static final long MF_UNUSED2 = (0x0000000010000000); + public static final long MF_UNUSED3 = (0x0000000020000000); + + // Translucent sprite? // phares + public static final long MF_TRANSLUCENT = (0x0000000040000000); + + // this is free LONGLONG(0x0000000100000000) + // these are greater than an int. That's why the flags below are now uint_64_t + public static final long MF_TOUCHY = (0x0000000100000000L); + public static final long MF_BOUNCES = (0x0000000200000000L); + public static final long MF_FRIEND = (0x0000000400000000L); + + public static final long MF_RESSURECTED = (0x0000001000000000L); + public static final long MF_NO_DEPTH_TEST = (0x0000002000000000L); + public static final long MF_FOREGROUND = (0x0000004000000000L); + +} \ No newline at end of file diff --git a/doom/src/p/RemoveState.java b/doom/src/p/RemoveState.java new file mode 100644 index 0000000..cd0824f --- /dev/null +++ b/doom/src/p/RemoveState.java @@ -0,0 +1,5 @@ +package p; + +public enum RemoveState implements ThinkerStates { + REMOVE; +} \ No newline at end of file diff --git a/doom/src/p/Resettable.java b/doom/src/p/Resettable.java new file mode 100644 index 0000000..e4fd3aa --- /dev/null +++ b/doom/src/p/Resettable.java @@ -0,0 +1,8 @@ +package p; + +/** For objects that needed to be memset to 0 in C, + * rather than being reallocated. */ +public interface Resettable { + + public void reset(); +} \ No newline at end of file diff --git a/doom/src/p/Specials.java b/doom/src/p/Specials.java new file mode 100644 index 0000000..b748355 --- /dev/null +++ b/doom/src/p/Specials.java @@ -0,0 +1,342 @@ +package p; + +import doom.player_t; +import m.fixed_t; +import static m.fixed_t.FRACUNIT; +import p.Actions.ActionsLights.glow_t; +import p.Actions.ActionsLights.lightflash_t; +import rr.line_t; +import rr.sector_t; +import rr.side_t; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: Specials.java,v 1.7 2011/06/01 00:09:08 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: none +// Implements special effects: +// Texture animation, height or lighting changes +// according to adjacent sectors, respective +// utility functions, etc. +// +//----------------------------------------------------------------------------- +public interface Specials { + +// +// End-level timer (-TIMER option) +// +//extern boolean levelTimer; +//extern int levelTimeCount; +// Define values for map objects + public static final int MO_TELEPORTMAN = 14; + +// at game start + public void P_InitPicAnims(); + +// at map load + public void P_SpawnSpecials(); + +// every tic + public void P_UpdateSpecials(); + +// when needed + public boolean + P_UseSpecialLine(mobj_t thing, + line_t line, + int side); + + public void + P_ShootSpecialLine(mobj_t thing, + line_t line); + + public void + P_CrossSpecialLine(int linenum, + int side, + mobj_t thing); + + public void P_PlayerInSpecialSector(player_t player); + + public int + twoSided(int sector, + int line); + + public sector_t + getSector(int currentSector, + int line, + int side); + + side_t + getSide(int currentSector, + int line, + int side); + + public fixed_t P_FindLowestFloorSurrounding(sector_t sec); + + public fixed_t P_FindHighestFloorSurrounding(sector_t sec); + + public fixed_t + P_FindNextHighestFloor(sector_t sec, + int currentheight); + + public fixed_t P_FindLowestCeilingSurrounding(sector_t sec); + + public fixed_t P_FindHighestCeilingSurrounding(sector_t sec); + + public int + P_FindSectorFromLineTag(line_t line, + int start); + + public int + P_FindMinSurroundingLight(sector_t sector, + int max); + + public sector_t + getNextSector(line_t line, + sector_t sec); + +// +// SPECIAL +// + int EV_DoDonut(line_t line); + + public static final int GLOWSPEED = 8; + public static final int STROBEBRIGHT = 5; + public static final int FASTDARK = 15; + public static final int SLOWDARK = 35; + + public void P_SpawnFireFlicker(sector_t sector); + + public void T_LightFlash(lightflash_t flash); + + public void P_SpawnLightFlash(sector_t sector); + + public void T_StrobeFlash(strobe_t flash); + + public void + P_SpawnStrobeFlash(sector_t sector, + int fastOrSlow, + int inSync); + + public void EV_StartLightStrobing(line_t line); + + public void EV_TurnTagLightsOff(line_t line); + + public void + EV_LightTurnOn(line_t line, + int bright); + + public void T_Glow(glow_t g); + + public void P_SpawnGlowingLight(sector_t sector); + + // max # of wall switches in a level + public static final int MAXSWITCHES = 50; + + // 4 players, 4 buttons each at once, max. + public static final int MAXBUTTONS = 16; + + // 1 second, in ticks. + public static final int BUTTONTIME = 35; + +//extern button_t buttonlist[MAXBUTTONS]; + public void + P_ChangeSwitchTexture(line_t line, + int useAgain); + + public void P_InitSwitchList(); + + public static final int PLATWAIT = 3; + public static final int PLATSPEED = FRACUNIT; + public static final int MAXPLATS = 30; + +//extern plat_t* activeplats[MAXPLATS]; + public void T_PlatRaise(plat_t plat); + + public int + EV_DoPlat(line_t line, + plattype_e type, + int amount); + + void P_AddActivePlat(plat_t plat); + + void P_RemoveActivePlat(plat_t plat); + + void EV_StopPlat(line_t line); + + void P_ActivateInStasis(int tag); + + public static final int VDOORSPEED = FRACUNIT * 2; + public static final int VDOORWAIT = 150; + + void + EV_VerticalDoor(line_t line, + mobj_t thing); + + int + EV_DoDoor(line_t line, + vldoor_e type); + + int + EV_DoLockedDoor(line_t line, + vldoor_e type, + mobj_t thing); + + public void T_VerticalDoor(vldoor_t door); + + public void P_SpawnDoorCloseIn30(sector_t sec); + + void + P_SpawnDoorRaiseIn5Mins(sector_t sec, + int secnum); + +} + +// UNUSED +// +// Sliding doors... +// + +/* +typedef enum +{ + sd_opening, + sd_waiting, + sd_closing + +} sd_e; + + + +typedef enum +{ + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose + +} sdt_e; + */ + + /* + +typedef struct +{ + thinker_t thinker; + sdt_e type; + line_t* line; + int frame; + int whichDoorIndex; + int timer; + sector_t* frontsector; + sector_t* backsector; + sd_e status; + +} slidedoor_t; + */ + + /* + +typedef struct +{ + char frontFrame1[9]; + char frontFrame2[9]; + char frontFrame3[9]; + char frontFrame4[9]; + char backFrame1[9]; + char backFrame2[9]; + char backFrame3[9]; + char backFrame4[9]; + +} slidename_t; + */ + + /* + +typedef struct +{ + int frontFrames[4]; + int backFrames[4]; + +} slideframe_t; + + */ + + /* +// how many frames of animation +#define SNUMFRAMES 4 + +#define SDOORWAIT 35*3 +#define SWAITTICS 4 + +// how many diff. types of anims +#define MAXSLIDEDOORS 5 + +void P_InitSlidingDoorFrames(void); + +void +EV_SlidingDoor +( line_t* line, + mobj_t* thing ); +#endif + +#define CEILSPEED FRACUNIT +#define CEILWAIT 150 +#define MAXCEILINGS 30 + +extern ceiling_t* activeceilings[MAXCEILINGS]; + +int +EV_DoCeiling +( line_t* line, + ceiling_e type ); + +void T_MoveCeiling (ceiling_t* ceiling); +void P_AddActiveCeiling(ceiling_t* c); +void P_RemoveActiveCeiling(ceiling_t* c); +int EV_CeilingCrushStop(line_t* line); +void P_ActivateInStasisCeiling(line_t* line); + +#define FLOORSPEED FRACUNIT + +result_e +T_MovePlane +( sector_t* sector, + fixed_t speed, + fixed_t dest, + boolean crush, + int floorOrCeiling, + int direction ); + +int +EV_BuildStairs +( line_t* line, + stair_e type ); + +int +EV_DoFloor +( line_t* line, + floor_e floortype ); + +void T_MoveFloor( floormove_t* floor); + +// +// P_TELEPT +// +int +EV_Teleport +( line_t* line, + int side, + mobj_t* thing ); + */ \ No newline at end of file diff --git a/doom/src/p/ThinkerList.java b/doom/src/p/ThinkerList.java new file mode 100644 index 0000000..11d4f40 --- /dev/null +++ b/doom/src/p/ThinkerList.java @@ -0,0 +1,23 @@ +package p; + +import doom.SourceCode.P_Tick; +import static doom.SourceCode.P_Tick.P_AddThinker; +import static doom.SourceCode.P_Tick.P_InitThinkers; +import static doom.SourceCode.P_Tick.P_RemoveThinker; +import doom.thinker_t; + +public interface ThinkerList { + + @P_Tick.C(P_AddThinker) + void AddThinker(thinker_t thinker); + + @P_Tick.C(P_RemoveThinker) + void RemoveThinker(thinker_t thinker); + + @P_Tick.C(P_InitThinkers) + void InitThinkers(); + + thinker_t getRandomThinker(); + + thinker_t getThinkerCap(); +} \ No newline at end of file diff --git a/doom/src/p/ThinkerStates.java b/doom/src/p/ThinkerStates.java new file mode 100644 index 0000000..5c59dc6 --- /dev/null +++ b/doom/src/p/ThinkerStates.java @@ -0,0 +1,6 @@ +package p; + +public interface ThinkerStates { + + int ordinal(); +} \ No newline at end of file diff --git a/doom/src/p/UnifiedGameMap.java b/doom/src/p/UnifiedGameMap.java new file mode 100644 index 0000000..648116f --- /dev/null +++ b/doom/src/p/UnifiedGameMap.java @@ -0,0 +1,702 @@ +package p; + +import data.Limits; +import static data.Limits.BUTTONTIME; +import static data.Limits.MAXANIMS; +import static data.Limits.MAXBUTTONS; +import static data.Limits.MAXSWITCHES; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.P_Tick; +import static doom.SourceCode.P_Tick.P_AddThinker; +import static doom.SourceCode.P_Tick.P_InitThinkers; +import doom.thinker_t; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.Settings; +import static m.fixed_t.MAPFRACUNIT; +import mochadoom.Engine; +import mochadoom.Loggers; +import rr.ISpriteManager; +import rr.line_t; +import utils.C2JUtils; +import static utils.C2JUtils.eval; +import static utils.GenericCopy.malloc; + +// // FROM SIGHT +public abstract class UnifiedGameMap implements ThinkerList { + + private static final Logger LOGGER = Loggers.getLogger(UnifiedGameMap.class.getName()); + + /** + * killough's code for thinkers seems to be totally broken in M.D, + * so commented it out and will not probably restore, but may invent + * something new in future + * - Good Sign 2017/05/1 + */ + public UnifiedGameMap(DoomMain DOOM) { + this.SW = new Switches(); + this.SPECS = new Specials(); + this.thinkercap = new thinker_t(); + /*for (int i=0; i DOOM; + + // //////////// Internal singletons ////////////// + public ActionFunctions A; + + Specials SPECS; + + Switches SW; + + // //////////////////////////////////////////// + // + // THING POSITION SETTING + // + // + // BLOCK MAP ITERATORS + // For each line/thing in the given mapblock, + // call the passed PIT_* function. + // If the function returns false, + // exit with false without checking anything else. + // + int ptflags; + + /** + * killough's code for thinkers seems to be totally broken in M.D, + * this method is unused + */ + /*protected void UpdateThinker(thinker_t thinker) { + thinker_t th; + // find the class the thinker belongs to + + th_class cls = thinker.thinkerFunction == NOP + ? th_class.th_delete + : (thinker.thinkerFunction == P_MobjThinker + && ((mobj_t) thinker).health > 0 + && (eval((((mobj_t) thinker).flags) & MF_COUNTKILL) + || ((mobj_t) thinker).type == mobjtype_t.MT_SKULL) + ? ( + eval((((mobj_t) thinker).flags) & MF_FRIEND) + ? th_class.th_friends + : th_class.th_enemies + ) : th_class.th_misc + ); + + { + /* Remove from current thread, if in one */ + /*if ((th = thinker.cnext) != null) { + (th.cprev = thinker.cprev).cnext = th; + } + } + + // Add to appropriate thread + th = thinkerclasscap[cls.ordinal()]; + th.cprev.cnext = thinker; + thinker.cnext = th; + thinker.cprev = th.cprev; + th.cprev = thinker; + } + + protected final thinker_t[] thinkerclasscap=new thinker_t[th_class.NUMTHCLASS];*/ + public boolean sight_debug; + + // + // P_InitPicAnims + // + /** + * Floor/ceiling animation sequences, defined by first and last frame, i.e. + * the flat (64x64 tile) name to be used. The full animation sequence is + * given using all the flats between the start and end entry, in the order + * found in the WAD file. + */ + private final animdef_t[] animdefs = { + new animdef_t(false, "NUKAGE3", "NUKAGE1", 8), + new animdef_t(false, "FWATER4", "FWATER1", 8), + new animdef_t(false, "SWATER4", "SWATER1", 8), + new animdef_t(false, "LAVA4", "LAVA1", 8), + new animdef_t(false, "BLOOD3", "BLOOD1", 8), + // DOOM II flat animations. + new animdef_t(false, "RROCK08", "RROCK05", 8), + new animdef_t(false, "SLIME04", "SLIME01", 8), + new animdef_t(false, "SLIME08", "SLIME05", 8), + new animdef_t(false, "SLIME12", "SLIME09", 8), + new animdef_t(true, "BLODGR4", "BLODGR1", 8), + new animdef_t(true, "SLADRIP3", "SLADRIP1", 8), + new animdef_t(true, "BLODRIP4", "BLODRIP1", 8), + new animdef_t(true, "FIREWALL", "FIREWALA", 8), + new animdef_t(true, "GSTFONT3", "GSTFONT1", 8), + new animdef_t(true, "FIRELAVA", "FIRELAV3", 8), + new animdef_t(true, "FIREMAG3", "FIREMAG1", 8), + new animdef_t(true, "FIREBLU2", "FIREBLU1", 8), + new animdef_t(true, "ROCKRED3", "ROCKRED1", 8), + new animdef_t(true, "BFALL4", "BFALL1", 8), + new animdef_t(true, "SFALL4", "SFALL1", 8), + new animdef_t(true, "WFALL4", "WFALL1", 8), + new animdef_t(true, "DBRAIN4", "DBRAIN1", 8) + }; + + // MAES: this was a cheap trick to mark the end of the sequence + // with a value of "-1". + // It won't work in Java, so just use animdefs.length-1 + // new animdef_t(false, "", "", 0) }; + // + // SPECIAL SPAWNING + // + public class Specials { + + public static final int OK = 0, CRUSHED = 1, PASTDEST = 2; + + public line_t[] linespeciallist = new line_t[Limits.MAXLINEANIMS]; + public short numlinespecials; + + /** + * These are NOT the same anims found in defines. Dunno why they fucked up + * this one so badly. Even the type has the same name, but is entirely + * different. No way they could be overlapped/unionized either. So WTF. + * Really. WTF. + */ + public anim_t[] anims = new anim_t[MAXANIMS]; + + // MAES: was a pointer + public int lastanim; + + // + // P_UpdateSpecials + // Animate planes, scroll walls, etc. + // + public boolean levelTimer; + + public int levelTimeCount; + + private Specials() { + + } + + public void UpdateSpecials() { + int pic; + line_t line; + anim_t anim; + + // LEVEL TIMER + if (levelTimer == true) { + levelTimeCount--; + if (levelTimeCount == 0) { + DOOM.ExitLevel(); + } + } + + // ANIMATE FLATS AND TEXTURES GLOBALLY + for (int j = 0; j < lastanim; j++) { + anim = anims[j]; + + for (int i = anim.basepic; i < anim.basepic + anim.numpics; i++) { + pic + = anim.basepic + + ((DOOM.leveltime / anim.speed + i) % anim.numpics); + if (anim.istexture) { + DOOM.textureManager.setTextureTranslation(i, pic); + } else { + DOOM.textureManager.setFlatTranslation(i, pic); + } + } + } + + // ANIMATE LINE SPECIALS + for (int i = 0; i < numlinespecials; i++) { + line = linespeciallist[i]; + switch (line.special) { + case 48: + // EFFECT FIRSTCOL SCROLL + + DOOM.levelLoader.sides[line.sidenum[0]].textureoffset += MAPFRACUNIT; + break; + } + } + + // DO BUTTONS + SW.doButtons(); + } + + public void InitPicAnims() { + Arrays.setAll(anims, i -> new anim_t()); + anim_t lstanim; + // Init animation. MAES: sneaky base pointer conversion ;-) + this.lastanim = 0; + // MAES: for (i=0 ; animdefs[i].istexture != -1 ; i++) + for (int i = 0; i < animdefs.length - 1; i++) { + lstanim = anims[this.lastanim]; + if (animdefs[i].istexture) { + // different episode ? + if (DOOM.textureManager.CheckTextureNumForName(animdefs[i].startname) == -1) { + continue; + } + // So, if it IS a valid texture, it goes straight into anims. + lstanim.picnum = DOOM.textureManager.TextureNumForName(animdefs[i].endname); + lstanim.basepic = DOOM.textureManager.TextureNumForName(animdefs[i].startname); + } else { // If not a texture, it's a flat. + if (DOOM.wadLoader.CheckNumForName(animdefs[i].startname) == -1) { + continue; + } + LOGGER.log(Level.FINER, animdefs[i]::toString); + // Otherwise, lstanim seems to go nowhere :-/ + lstanim.picnum = DOOM.textureManager.FlatNumForName(animdefs[i].endname); + lstanim.basepic = DOOM.textureManager.FlatNumForName(animdefs[i].startname); + } + + lstanim.istexture = animdefs[i].istexture; + lstanim.numpics = lstanim.picnum - lstanim.basepic + 1; + + if (lstanim.numpics < 2) { + DOOM.doomSystem.Error("P_InitPicAnims: bad cycle from %s to %s", + animdefs[i].startname, animdefs[i].endname); + } + + lstanim.speed = animdefs[i].speed; + this.lastanim++; + } + } + + public final void resizeLinesSpecialList() { + linespeciallist = C2JUtils.resize(linespeciallist[0], linespeciallist, linespeciallist.length * 2); + } + + } + + public class Switches { + + private Switches() { + switchlist = new int[MAXSWITCHES]; + initButtonList(); + } + + public void doButtons() { + for (final button_t buttonlist1 : buttonlist) { + if (eval(buttonlist1.btimer)) { + buttonlist1.btimer--; + if (!eval(buttonlist1.btimer)) { + switch (buttonlist1.where) { + case top: + DOOM.levelLoader.sides[buttonlist1.line.sidenum[0]].toptexture = (short) buttonlist1.btexture; + break; + case middle: + DOOM.levelLoader.sides[buttonlist1.line.sidenum[0]].midtexture = (short) buttonlist1.btexture; + break; + case bottom: + DOOM.levelLoader.sides[buttonlist1.line.sidenum[0]].bottomtexture = (short) buttonlist1.btexture; + break; + } + DOOM.doomSound.StartSound(buttonlist1.soundorg, sfxenum_t.sfx_swtchn); + buttonlist1.reset(); + } + } + } + } + + // + // CHANGE THE TEXTURE OF A WALL SWITCH TO ITS OPPOSITE + // + switchlist_t[] alphSwitchList = { + // Doom shareware episode 1 switches + new switchlist_t("SW1BRCOM", "SW2BRCOM", 1), + new switchlist_t("SW1BRN1", "SW2BRN1", 1), + new switchlist_t("SW1BRN2", "SW2BRN2", 1), + new switchlist_t("SW1BRNGN", "SW2BRNGN", 1), + new switchlist_t("SW1BROWN", "SW2BROWN", 1), + new switchlist_t("SW1COMM", "SW2COMM", 1), + new switchlist_t("SW1COMP", "SW2COMP", 1), + new switchlist_t("SW1DIRT", "SW2DIRT", 1), + new switchlist_t("SW1EXIT", "SW2EXIT", 1), + new switchlist_t("SW1GRAY", "SW2GRAY", 1), + new switchlist_t("SW1GRAY1", "SW2GRAY1", 1), + new switchlist_t("SW1METAL", "SW2METAL", 1), + new switchlist_t("SW1PIPE", "SW2PIPE", 1), + new switchlist_t("SW1SLAD", "SW2SLAD", 1), + new switchlist_t("SW1STARG", "SW2STARG", 1), + new switchlist_t("SW1STON1", "SW2STON1", 1), + new switchlist_t("SW1STON2", "SW2STON2", 1), + new switchlist_t("SW1STONE", "SW2STONE", 1), + new switchlist_t("SW1STRTN", "SW2STRTN", 1), + // Doom registered episodes 2&3 switches + new switchlist_t("SW1BLUE", "SW2BLUE", 2), + new switchlist_t("SW1CMT", "SW2CMT", 2), + new switchlist_t("SW1GARG", "SW2GARG", 2), + new switchlist_t("SW1GSTON", "SW2GSTON", 2), + new switchlist_t("SW1HOT", "SW2HOT", 2), + new switchlist_t("SW1LION", "SW2LION", 2), + new switchlist_t("SW1SATYR", "SW2SATYR", 2), + new switchlist_t("SW1SKIN", "SW2SKIN", 2), + new switchlist_t("SW1VINE", "SW2VINE", 2), + new switchlist_t("SW1WOOD", "SW2WOOD", 2), + // Doom II switches + new switchlist_t("SW1PANEL", "SW2PANEL", 3), + new switchlist_t("SW1ROCK", "SW2ROCK", 3), + new switchlist_t("SW1MET2", "SW2MET2", 3), + new switchlist_t("SW1WDMET", "SW2WDMET", 3), + new switchlist_t("SW1BRIK", "SW2BRIK", 3), + new switchlist_t("SW1MOD1", "SW2MOD1", 3), + new switchlist_t("SW1ZIM", "SW2ZIM", 3), + new switchlist_t("SW1STON6", "SW2STON6", 3), + new switchlist_t("SW1TEK", "SW2TEK", 3), + new switchlist_t("SW1MARB", "SW2MARB", 3), + new switchlist_t("SW1SKULL", "SW2SKULL", 3), + new switchlist_t("\0", "\0", 0) + }; + + /** A (runtime generated) list of the KNOWN button types */ + int[] switchlist; + int numswitches; + button_t[] buttonlist; + + // + // P_InitSwitchList + // Only called at game initialization. + // + public void InitSwitchList() { + int i; + int index; + int episode; + + episode = 1; + + // MAES: if this isn't changed Ultimate Doom's switches + // won't work visually. + if (DOOM.isRegistered()) { + episode = 2; + } else if (DOOM.isCommercial()) { + episode = 3; + } + + for (index = 0, i = 0; i < MAXSWITCHES; i++) { + if (index >= switchlist.length) { + // Remove limit + switchlist = Arrays.copyOf(switchlist, switchlist.length > 0 ? switchlist.length * 2 : 8); + } + + // Trickery. Looks for "end of list" marker + // Since the list has pairs of switches, the + // actual number of distinct switches is index/2 + if (alphSwitchList[i].episode == 0) { + numswitches = index / 2; + switchlist[index] = -1; + break; + } + + if (alphSwitchList[i].episode <= episode) { + /* + * // UNUSED - debug? int value; if + * (R_CheckTextureNumForName(alphSwitchList[i].name1) < 0) { + * system.Error("Can't find switch texture '%s'!", + * alphSwitchList[i].name1); continue; } value = + * R_TextureNumForName(alphSwitchList[i].name1); + */ + switchlist[index++] = DOOM.textureManager.TextureNumForName(alphSwitchList[i].name1); + switchlist[index++] = DOOM.textureManager.TextureNumForName(alphSwitchList[i].name2); + } + } + } + + // + // Start a button counting down till it turns off. + // + public final void StartButton(line_t line, bwhere_e w, int texture, int time) { + // See if button is already pressed + for (button_t buttonlist1 : buttonlist) { + if (buttonlist1.btimer != 0 && buttonlist1.line == line) { + return; + } + } + + // At this point, it may mean that THE button of that particular + // line was not active, or simply that there were not enough + // buttons in buttonlist to support an additional entry. + // Search for a free button slot. + for (button_t buttonlist1 : buttonlist) { + if (buttonlist1.btimer == 0) { + buttonlist1.line = line; + buttonlist1.where = w; + buttonlist1.btexture = texture; + buttonlist1.btimer = time; + buttonlist1.soundorg = line.soundorg; + return; + } + } + + /** + * Added config option to disable resize + * - Good Sign 2017/04/26 + */ + // Extremely rare event, We must be able to push more than MAXBUTTONS buttons + // in one tic, which can't normally happen except in really pathological maps. + // In any case, resizing should solve this problem. + if (Engine.getConfig().equals(Settings.extend_button_slots_limit, Boolean.TRUE)) { + buttonlist = C2JUtils.resize(buttonlist[0], buttonlist, buttonlist.length * 2); + // Try again + StartButton(line, w, texture, time); + } else { + LOGGER.log(Level.SEVERE, "P_StartButton: no button slots left!"); + System.exit(1); + } + } + + // + // Function that changes wall texture. + // Tell it if switch is ok to use again (true=yes, it's a button). + // + public void ChangeSwitchTexture(line_t line, boolean useAgain) { + int texTop; + int texMid; + int texBot; + int sound; + + if (!useAgain) { + line.special = 0; + } + + texTop = DOOM.levelLoader.sides[line.sidenum[0]].toptexture; + texMid = DOOM.levelLoader.sides[line.sidenum[0]].midtexture; + texBot = DOOM.levelLoader.sides[line.sidenum[0]].bottomtexture; + + sound = sfxenum_t.sfx_swtchn.ordinal(); + + // EXIT SWITCH? + if (line.special == 11) { + sound = sfxenum_t.sfx_swtchx.ordinal(); + } + + for (int i = 0; i < numswitches * 2; i++) { + if (switchlist[i] == texTop) { + DOOM.doomSound.StartSound(buttonlist[0].soundorg, sound); + DOOM.levelLoader.sides[line.sidenum[0]].toptexture = (short) switchlist[i ^ 1]; + + if (useAgain) { + StartButton(line, bwhere_e.top, switchlist[i], BUTTONTIME); + } + + return; + } else { + if (switchlist[i] == texMid) { + DOOM.doomSound.StartSound(buttonlist[0].soundorg, sound); + DOOM.levelLoader.sides[line.sidenum[0]].midtexture = (short) switchlist[i ^ 1]; + + if (useAgain) { + StartButton(line, bwhere_e.middle, switchlist[i], BUTTONTIME); + } + + return; + } else { + if (switchlist[i] == texBot) { + DOOM.doomSound.StartSound(buttonlist[0].soundorg, sound); + DOOM.levelLoader.sides[line.sidenum[0]].bottomtexture = (short) switchlist[i ^ 1]; + + if (useAgain) { + StartButton(line, bwhere_e.bottom, switchlist[i], BUTTONTIME); + } + + return; + } + } + } + } + } + + public final void initButtonList() { + // Unlike plats, buttonlist needs statically allocated and reusable + // objects. The MAXBUTTONS limit actually applied to buttons PRESSED + // or ACTIVE at once, not how many there can actually be in a map. + + buttonlist = malloc(button_t::new, button_t[]::new, MAXBUTTONS); + } + } + + /* enum PTR { + SlideTraverse, + AimTraverse, + ShootTraverse, + UseTraverse + } */ + /////////// BEGIN MAP OBJECT CODE, USE AS BASIC + // //////////////////////////////// THINKER CODE, GLOBALLY VISIBLE + // ///////////////// + // + // THINKERS + // All thinkers should be allocated by Z_Malloc + // so they can be operated on uniformly. + // The actual structures will vary in size, + // but the first element must be thinker_t. + // + /** Both the head and the tail of the thinkers list */ + public thinker_t thinkercap; + + /** + * killough's code for thinkers seems to be totally broken in M.D, + * so commented it out and will not probably restore, but may invent + * something new in future + * - Good Sign 2017/05/1 + * + * P_InitThinkers + */ + @Override + @SourceCode.Suspicious(CauseOfDesyncProbability.MEDIUM) + @P_Tick.C(P_InitThinkers) + public void InitThinkers() { + + /*for (int i=0; i{ + //@Override + //public void accept(thinker_t thinker) { + /* + try { + System.err.printf("Delete: %s %d<= %s %d => %s %d\n", + ((mobj_t)thinker.prev).type,((mobj_t)thinker.prev).thingnum, + ((mobj_t)thinker).type,((mobj_t)thinker).thingnum, + ((mobj_t)thinker.next).type,((mobj_t)thinker.next).thingnum); + } catch (ClassCastException e){ + + } */ + // Unlike Boom, if we reach here it gets zapped anyway + //if (!thinker->references) + //{ + //{ /* Remove from main thinker list */ + //thinker_t next = thinker.next; + /* Note that currentthinker is guaranteed to point to us, + * and since we're freeing our memory, we had better change that. So + * point it to thinker->prev, so the iterator will correctly move on to + * thinker->prev->next = thinker->next */ + //(next.prev = currentthinker = thinker.prev).next = next; + //thinker.next=thinker.prev=null; + //try { + // System.err.printf("Delete: %s %d <==> %s %d\n", + // ((mobj_t)currentthinker.prev).type,((mobj_t)currentthinker.prev).thingnum, + // ((mobj_t)currentthinker.next).type,((mobj_t)currentthinker.next).thingnum); + //} catch (ClassCastException e){ + //} + //} + //{ + /* Remove from current thinker class list */ + //thinker_t th = thinker.cnext; + //(th.cprev = thinker.cprev).cnext = th; + //thinker.cnext=thinker.cprev=null; + //} + //} + //} +} // End unified map \ No newline at end of file diff --git a/doom/src/p/anim_t.java b/doom/src/p/anim_t.java new file mode 100644 index 0000000..db5cc08 --- /dev/null +++ b/doom/src/p/anim_t.java @@ -0,0 +1,30 @@ +package p; + +/** Animating textures and planes + * There is another anim_t used in wi_stuff, unrelated. + * + * @author admin + * + */ +public class anim_t { + + public anim_t() { + + } + + public anim_t(boolean istexture, int picnum, int basepic, int numpics, + int speed) { + super(); + this.istexture = istexture; + this.picnum = picnum; + this.basepic = basepic; + this.numpics = numpics; + this.speed = speed; + } + public boolean istexture; + public int picnum; + public int basepic; + public int numpics; + public int speed; + +} \ No newline at end of file diff --git a/doom/src/p/animdef_t.java b/doom/src/p/animdef_t.java new file mode 100644 index 0000000..c4e8636 --- /dev/null +++ b/doom/src/p/animdef_t.java @@ -0,0 +1,65 @@ +package p; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; +import w.DoomBuffer; + +/** + * Source animation definition. Made readable for compatibility with Boom's + * SWANTBLS system. + * + * @author velktron + */ +public class animdef_t + implements CacheableDoomObject { + + public animdef_t() { + + } + + public animdef_t(boolean istexture, String endname, String startname, + int speed) { + super(); + this.istexture = istexture; + this.endname = endname; + this.startname = startname; + this.speed = speed; + } + + /** if false, it is a flat, and will NOT be used as a texture. Unless you + * use "flats on walls functionality of course. */ + public boolean istexture; + + /** The END name and START name of a texture, given in this order when reading a lump + * The animation system is agnostic to the actual names of of the "in-between" + * frames, it's purely pointer based, and only the start/end are constant. It only + * counts the actual number of existing textures during initialization time. + * + */ + public String endname, startname; + + public int speed; + + public String toString() { + return String.format("%s %s %s %d", istexture, startname, endname, + speed); + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + // Like most Doom structs... + buf.order(ByteOrder.LITTLE_ENDIAN); + this.istexture = (buf.get() != 0); + this.startname = DoomBuffer.getNullTerminatedString(buf, 9); + this.endname = DoomBuffer.getNullTerminatedString(buf, 9); + this.speed = buf.getInt(); + } + + public static int size() { + return 23; + } + +} \ No newline at end of file diff --git a/doom/src/p/button_t.java b/doom/src/p/button_t.java new file mode 100644 index 0000000..ac958b3 --- /dev/null +++ b/doom/src/p/button_t.java @@ -0,0 +1,29 @@ +package p; + +import rr.line_t; +import s.degenmobj_t; + +public class button_t implements Resettable { + + public line_t line; + public bwhere_e where; + public int btexture; + public int btimer; + public degenmobj_t soundorg; + + public button_t() { + this.btexture = 0; + this.btimer = 0; + this.where = bwhere_e.top; + } + + public void reset() { + this.line = null; + this.where = bwhere_e.top; + this.btexture = 0; + this.btimer = 0; + this.soundorg = null; + + } + +} \ No newline at end of file diff --git a/doom/src/p/bwhere_e.java b/doom/src/p/bwhere_e.java new file mode 100644 index 0000000..492d2ff --- /dev/null +++ b/doom/src/p/bwhere_e.java @@ -0,0 +1,7 @@ +package p; + +public enum bwhere_e { + top, + middle, + bottom +} \ No newline at end of file diff --git a/doom/src/p/ceiling_e.java b/doom/src/p/ceiling_e.java new file mode 100644 index 0000000..72bfb17 --- /dev/null +++ b/doom/src/p/ceiling_e.java @@ -0,0 +1,15 @@ +package p; + +// +// P_CEILNG +// +public enum ceiling_e { + + lowerToFloor, + raiseToHighest, + lowerAndCrush, + crushAndRaise, + fastCrushAndRaise, + silentCrushAndRaise; + +} \ No newline at end of file diff --git a/doom/src/p/ceiling_t.java b/doom/src/p/ceiling_t.java new file mode 100644 index 0000000..261e7f6 --- /dev/null +++ b/doom/src/p/ceiling_t.java @@ -0,0 +1,79 @@ +package p; + +import doom.SourceCode.fixed_t; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import rr.SectorAction; +import w.CacheableDoomObject; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +public class ceiling_t extends SectorAction implements CacheableDoomObject, IReadableDoomObject, IPackableDoomObject { + + public ceiling_e type; + @fixed_t + public int bottomheight; + @fixed_t + public int topheight; + @fixed_t + public int speed; + public boolean crush; + + // 1 = up, 0 = waiting, -1 = down + public int direction; + + // ID + public int tag; + public int olddirection; + + public ceiling_t() { + // Set to the smallest ordinal type. + this.type = ceiling_e.lowerToFloor; + } + + // HACK for speed. + public static final ceiling_e[] values = ceiling_e.values(); + + @Override + public void read(DataInputStream f) throws IOException { + // Read 48 bytes. + readbuffer.position(0); + readbuffer.order(ByteOrder.LITTLE_ENDIAN); + f.read(readbuffer.array(), 0, 48); + unpack(readbuffer); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + b.order(ByteOrder.LITTLE_ENDIAN); + super.pack(b); //12 + b.putInt(type.ordinal()); // 16 + b.putInt(super.sectorid); // 20 + b.putInt(bottomheight); + b.putInt(topheight); // 28 + b.putInt(speed); + b.putInt(crush ? 1 : 0); + b.putInt(direction); // 40 + b.putInt(tag); + b.putInt(olddirection); //48 + } + + @Override + public void unpack(ByteBuffer b) throws IOException { + b.order(ByteOrder.LITTLE_ENDIAN); + super.unpack(b); // Call thinker reader first + type = values[b.getInt()]; + super.sectorid = b.getInt(); // sector pointer. + bottomheight = b.getInt(); + topheight = b.getInt(); + speed = b.getInt(); + crush = (b.getInt() != 0); + direction = b.getInt(); + tag = b.getInt(); + olddirection = b.getInt(); + } + + private static final ByteBuffer readbuffer = ByteBuffer.allocate(48); +} \ No newline at end of file diff --git a/doom/src/p/divline_t.java b/doom/src/p/divline_t.java new file mode 100644 index 0000000..48a0d57 --- /dev/null +++ b/doom/src/p/divline_t.java @@ -0,0 +1,165 @@ +package p; + +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import rr.line_t; +import static utils.C2JUtils.eval; + +public class divline_t { + + /** fixed_t */ + public int x, y, dx, dy; + + /** + *P_PointOnDivlineSide + *Returns 0 or 1. (false or true) + *@param x fixed + *@param y fixed + *@param divline_t + */ + public boolean + PointOnDivlineSide(int x, + int y + ) { + + // Using Killough's version. + return (dx == 0) ? x <= this.x ? dy > 0 : dy < 0 + : (dy == 0) ? y <= this.y ? dx < 0 : dx > 0 + : (dy ^ dx ^ (x -= this.x) ^ (y -= this.y)) < 0 ? (dy ^ x) < 0 + : FixedMul(y >> 8, this.dx >> 8) >= FixedMul(this.dy >> 8, x >> 8); + /* + int PUREFUNC P_PointOnDivlineSide(fixed_t x, fixed_t y, const divline_t *line) + { + return + !line->dx ? x <= line->x ? line->dy > 0 : line->dy < 0 : + !line->dy ? y <= line->y ? line->dx < 0 : line->dx > 0 : + (line->dy^line->dx^(x -= line->x)^(y -= line->y)) < 0 ? (line->dy^x) < 0 : + FixedMul(y>>8, line->dx>>8) >= FixedMul(line->dy>>8, x>>8); + }*/ + + /* + int dx; + int dy; + int left; + int right; + + if (this.dx==0) + { + if (x <= this.x) + return this.dy > 0; + + return this.dy < 0; + } + if (this.dy==0) + { + if (y <= this.y) + return this.dx < 0; + + return this.dx > 0; + } + + dx = (x - this.x); + dy = (y - this.y); + + // try to quickly decide by looking at sign bits + if ( ((this.dy ^ this.dx ^ dx ^ dy)&0x80000000) !=0) + { + if (((this.dy ^ dx) & 0x80000000) !=0) + return true; // (left is negative) + return false; + } + + left = FixedMul ( this.dy>>8, dx>>8 ); + right = FixedMul ( dy>>8 , this.dx>>8 ); + + if (right < left) + return false; // front side + return true; // back side + */ + } + + // + //P_MakeDivline + // + public void + MakeDivline(line_t li) { + this.x = li.v1x; + this.y = li.v1y; + this.dx = li.dx; + this.dy = li.dy; + } + + public divline_t(line_t li) { + this.x = li.v1x; + this.y = li.v1y; + this.dx = li.dx; + this.dy = li.dy; + } + + public divline_t() { + // TODO Auto-generated constructor stub + } + + /** + * P_DivlineSide + * Returns side 0 (front), 1 (back), or 2 (on). + */ + public int + DivlineSide(int x, + int y) { + + int left, right; + // Boom-style code. Da fack. + // [Maes:] it is MUCH more corrent than the linuxdoom one, for whatever reason. + + return (this.dx == 0) ? x == this.x ? 2 : x <= this.x ? eval(this.dy > 0) : eval(this.dy < 0) + : (this.dy == 0) ? (olddemo ? x : y) == this.y ? 2 : y <= this.y ? eval(this.dx < 0) : eval(this.dx > 0) + : (this.dy == 0) ? y == this.y ? 2 : y <= this.y ? eval(this.dx < 0) : eval(this.dx > 0) + : (right = ((y - this.y) >> FRACBITS) * (this.dx >> FRACBITS)) + < (left = ((x - this.x) >> FRACBITS) * (this.dy >> FRACBITS)) ? 0 + : right == left ? 2 : 1; + + /* + + int left,right,dx,dy; + + if (this.dx==0) + { + if (x==this.x) + return 2; + + if (x <= this.x) + return eval(this.dy > 0); + + return eval(this.y < 0); + } + + if (this.dy==0) + { + if (x==this.y) + return 2; + + if (y <= this.y) + return eval(this.dx < 0); + + return eval(this.dx > 0); + } + + dx = (x - this.x); + dy = (y - this.y); + + left = (this.dy>>FRACBITS) * (dx>>FRACBITS); + right = (dy>>FRACBITS) * (this.dx>>FRACBITS); + + if (right < left) + return 0; // front side + + if (left == right) + return 2; + return 1; // back side + */ + } + + private static final boolean olddemo = true; + +} \ No newline at end of file diff --git a/doom/src/p/floor_e.java b/doom/src/p/floor_e.java new file mode 100644 index 0000000..787ef6e --- /dev/null +++ b/doom/src/p/floor_e.java @@ -0,0 +1,27 @@ +package p; + +public enum floor_e { + // lower floor to highest surrounding floor + lowerFloor, + // lower floor to lowest surrounding floor + lowerFloorToLowest, + // lower floor to highest surrounding floor VERY FAST + turboLower, + // raise floor to lowest surrounding CEILING + raiseFloor, + // raise floor to next highest surrounding floor + raiseFloorToNearest, + // raise floor to shortest height texture around it + raiseToTexture, + // lower floor to lowest surrounding floor + // and change floorpic + lowerAndChange, + raiseFloor24, + raiseFloor24AndChange, + raiseFloorCrush, + // raise to next highest floor, turbo-speed + raiseFloorTurbo, + donutRaise, + raiseFloor512 + +} \ No newline at end of file diff --git a/doom/src/p/floormove_t.java b/doom/src/p/floormove_t.java new file mode 100644 index 0000000..a15f052 --- /dev/null +++ b/doom/src/p/floormove_t.java @@ -0,0 +1,56 @@ +package p; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import rr.SectorAction; +import w.DoomIO; +import w.IReadableDoomObject; + +public class floormove_t extends SectorAction implements IReadableDoomObject { + + public floormove_t() { + // MAES HACK: floors are implied to be at least of "lowerFloor" type + // unless set otherwise, due to implicit zero-enum value. + this.type = floor_e.lowerFloor; + } + + public floor_e type; + public boolean crush; + + public int direction; + public int newspecial; + public short texture; + /** fixed_t */ + public int floordestheight; + /** fixed_t */ + public int speed; + + @Override + public void read(DataInputStream f) throws IOException { + + super.read(f); // Call thinker reader first + type = floor_e.values()[DoomIO.readLEInt(f)]; + crush = DoomIO.readIntBoolean(f); + super.sectorid = DoomIO.readLEInt(f); // Sector index (or pointer?) + direction = DoomIO.readLEInt(f); + newspecial = DoomIO.readLEInt(f); + texture = DoomIO.readLEShort(f); + floordestheight = DoomIO.readLEInt(f); + speed = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(type.ordinal()); // 16 + b.putInt(crush ? 1 : 0); //20 + b.putInt(super.sectorid); // 24 + b.putInt(direction); // 28 + b.putInt(newspecial); // 32 + b.putShort(texture); // 34 + b.putInt(floordestheight); // 38 + b.putInt(speed); // 42 + } + +} \ No newline at end of file diff --git a/doom/src/p/intercept_t.java b/doom/src/p/intercept_t.java new file mode 100644 index 0000000..7620198 --- /dev/null +++ b/doom/src/p/intercept_t.java @@ -0,0 +1,48 @@ +package p; + +import doom.SourceCode.fixed_t; +import rr.line_t; + +/** + * An object that carries...interception information, I guess...with either a line + * or an object? + * + * @author Velktron + * + */ +public class intercept_t { + + /** + * most intercepts will belong to a static pool + */ + public intercept_t() { + } + + public intercept_t(int frac, mobj_t thing) { + this.frac = frac; + this.thing = thing; + this.isaline = false; + } + + public intercept_t(int frac, line_t line) { + this.frac = frac; + this.line = line; + this.isaline = true; + } + + /** + * fixed_t, along trace line + */ + @fixed_t + public int frac; + public boolean isaline; + // MAES: this was an union of a mobj_t and a line_t, + // returned as "d". + public mobj_t thing; + public line_t line; + + public Interceptable d() { + return (isaline) ? line : thing; + } + +} \ No newline at end of file diff --git a/doom/src/p/mobj_t.java b/doom/src/p/mobj_t.java new file mode 100644 index 0000000..75b750b --- /dev/null +++ b/doom/src/p/mobj_t.java @@ -0,0 +1,584 @@ +package p; + +import static data.Defines.FLOATSPEED; +import static data.Defines.GRAVITY; +import static data.Defines.VIEWHEIGHT; +import data.Tables; +import static data.info.states; +import data.mapthing_t; +import data.mobjinfo_t; +import data.mobjtype_t; +import data.sounds.sfxenum_t; +import data.spritenum_t; +import data.state_t; +import defines.statenum_t; +import doom.DoomMain; +import doom.SourceCode.fixed_t; +import doom.player_t; +import doom.thinker_t; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.ActiveStates.MobjConsumer; +import static p.MapUtils.AproxDistance; +import rr.subsector_t; +import s.ISoundOrigin; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.pointer; +import w.IPackableDoomObject; +import w.IReadableDoomObject; +import w.IWritableDoomObject; + +/** + * + * NOTES: mobj_t + * + * mobj_ts are used to tell the refresh where to draw an image, tell the world + * simulation when objects are contacted, and tell the sound driver how to + * position a sound. + * + * The refresh uses the next and prev links to follow lists of things in sectors + * as they are being drawn. The sprite, frame, and angle elements determine + * which patch_t is used to draw the sprite if it is visible. The sprite and + * frame values are allmost allways set from state_t structures. The + * statescr.exe utility generates the states.h and states.c files that contain + * the sprite/frame numbers from the statescr.txt source file. The xyz origin + * point represents a point at the bottom middle of the sprite (between the feet + * of a biped). This is the default origin position for patch_ts grabbed with + * lumpy.exe. A walking creature will have its z equal to the floor it is + * standing on. + * + * The sound code uses the x,y, and subsector fields to do stereo positioning of + * any sound effited by the mobj_t. + * + * The play simulation uses the blocklinks, x,y,z, radius, height to determine + * when mobj_ts are touching each other, touching lines in the map, or hit by + * trace lines (gunshots, lines of sight, etc). The mobj_t->flags element has + * various bit flags used by the simulation. + * + * Every mobj_t is linked into a single sector based on its origin coordinates. + * The subsector_t is found with R_PointInSubsector(x,y), and the sector_t can + * be found with subsector->sector. The sector links are only used by the + * rendering code, the play simulation does not care about them at all. + * + * Any mobj_t that needs to be acted upon by something else in the play world + * (block movement, be shot, etc) will also need to be linked into the blockmap. + * If the thing has the MF_NOBLOCK flag set, it will not use the block links. It + * can still interact with other things, but only as the instigator (missiles + * will run into other things, but nothing can run into a missile). Each block + * in the grid is 128*128 units, and knows about every line_t that it contains a + * piece of, and every interactable mobj_t that has its origin contained. + * + * A valid mobj_t is a mobj_t that has the proper subsector_t filled in for its + * xy coordinates and is linked into the sector from which the subsector was + * made, or has the MF_NOSECTOR flag set (the subsector_t needs to be valid even + * if MF_NOSECTOR is set), and is linked into a blockmap block or has the + * MF_NOBLOCKMAP flag set. Links should only be modified by the + * P_[Un]SetThingPosition() functions. Do not change the MF_NO? flags while a + * thing is valid. + * + * Any questions? + * + * @author admin + * + */ +public class mobj_t extends thinker_t implements ISoundOrigin, Interceptable, + IWritableDoomObject, IPackableDoomObject, IReadableDoomObject { + + private static final Logger LOGGER = Loggers.getLogger(mobj_t.class.getName()); + + public final ActionFunctions A; + + public static mobj_t createOn(final DoomMain context) { + if (eval(context.actions)) { + return new mobj_t(context.actions); + } + + return new mobj_t(); + } + + private mobj_t() { + this.spawnpoint = new mapthing_t(); + this.A = null; + } + + private mobj_t(final ActionFunctions A) { + this.spawnpoint = new mapthing_t(); + this.A = A; + // A mobj_t is ALSO a thinker, as it always contains the struct. + // Don't fall for C's trickery ;-) + // this.thinker=new thinker_t(); + } + + /* List: thinker links. */ + // public thinker_t thinker; + /** Info for drawing: position. */ + @fixed_t + public int x, y, z; + + /** More list: links in sector (if needed) */ + public thinker_t snext, sprev; + + // More drawing info: to determine current sprite. + /** + * orientation. This needs to be long or else certain checks will fail...but + * I need to see it working in order to confirm + */ + public long angle; + + /** used to find patch_t and flip value */ + public spritenum_t mobj_sprite; + /** might be ORed with FF_FULLBRIGHT */ + public int mobj_frame; + + /** Interaction info, by BLOCKMAP. Links in blocks (if needed). */ + public thinker_t bnext, bprev; + + /** MAES: was actually a pointer to a struct subsector_s */ + public subsector_t subsector; + + /** The closest interval over all contacted Sectors. */ + @fixed_t + public int floorz, ceilingz; + + /** For movement checking. */ + @fixed_t + public int radius, height; + + /** Momentums, used to update position. */ + @fixed_t + public int momx, momy, momz; + + /** If == validcount, already checked. */ + public int validcount; + + public mobjtype_t type; + // MAES: was a pointer + public mobjinfo_t info; // &mobjinfo[mobj.type] + + public long mobj_tics; // state tic counter + // MAES: was a pointer + public state_t mobj_state; + public long flags; + public int health; + + /** Movement direction, movement generation (zig-zagging). */ + public int movedir; // 0-7 + public int movecount; // when 0, select a new dir + + /** + * Thing being chased/attacked (or NULL), also the originator for missiles. + * MAES: was a pointer + */ + public mobj_t target; + public int p_target; // for savegames + + /** + * Reaction time: if non 0, don't attack yet. Used by player to freeze a bit + * after teleporting. + */ + public int reactiontime; + + /** + * If >0, the target will be chased no matter what (even if shot) + */ + public int threshold; + + /** + * Additional info record for player avatars only. Only valid if type == + * MT_PLAYER struct player_s* player; + */ + public player_t player; + + /** Player number last looked for. */ + public int lastlook; + + /** For nightmare respawn. */ + public mapthing_t spawnpoint; // struct + + /** Thing being chased/attacked for tracers. */ + public mobj_t tracer; // MAES: was a pointer + + // // MF_ flags for mobjs. + // Call P_SpecialThing when touched. + public static final int MF_SPECIAL = 1; + // Blocks. + public static final int MF_SOLID = 2; + // Can be hit. + public static final int MF_SHOOTABLE = 4; + // Don't use the sector links (invisible but touchable). + public static final int MF_NOSECTOR = 8; + // Don't use the blocklinks (inert but displayable) + public static final int MF_NOBLOCKMAP = 16; + + // Not to be activated by sound, deaf monster. + public static final int MF_AMBUSH = 32; + // Will try to attack right back. + public static final int MF_JUSTHIT = 64; + // Will take at least one step before attacking. + public static final int MF_JUSTATTACKED = 128; + // On level spawning (initial position), + // hang from ceiling instead of stand on floor. + public static final int MF_SPAWNCEILING = 256; + // Don't apply gravity (every tic), + // that is, object will float, keeping current height + // or changing it actively. + public static final int MF_NOGRAVITY = 512; + + // Movement flags. + // This allows jumps from high places. + public static final int MF_DROPOFF = 0x400; + // For players, will pick up items. + public static final int MF_PICKUP = 0x800; + // Player cheat. ??? + public static final int MF_NOCLIP = 0x1000; + // Player: keep info about sliding along walls. + public static final int MF_SLIDE = 0x2000; + // Allow moves to any height, no gravity. + // For active floaters, e.g. cacodemons, pain elementals. + public static final int MF_FLOAT = 0x4000; + // Don't cross lines + // ??? or look at heights on teleport. + public static final int MF_TELEPORT = 0x8000; + // Don't hit same species, explode on block. + // Player missiles as well as fireballs of various kinds. + public static final int MF_MISSILE = 0x10000; + // Dropped by a demon, not level spawned. + // E.g. ammo clips dropped by dying former humans. + public static final int MF_DROPPED = 0x20000; + // Use fuzzy draw (shadow demons or spectres), + // temporary player invisibility powerup. + public static final int MF_SHADOW = 0x40000; + // Flag: don't bleed when shot (use puff), + // barrels and shootable furniture shall not bleed. + public static final int MF_NOBLOOD = 0x80000; + // Don't stop moving halfway off a step, + // that is, have dead bodies slide down all the way. + public static final int MF_CORPSE = 0x100000; + // Floating to a height for a move, ??? + // don't auto float to target's height. + public static final int MF_INFLOAT = 0x200000; + + // On kill, count this enemy object + // towards intermission kill total. + // Happy gathering. + public static final int MF_COUNTKILL = 0x400000; + + // On picking up, count this item object + // towards intermission item total. + public static final int MF_COUNTITEM = 0x800000; + + // Special handling: skull in flight. + // Neither a cacodemon nor a missile. + public static final int MF_SKULLFLY = 0x1000000; + + // Don't spawn this object + // in death match mode (e.g. key cards). + public static final int MF_NOTDMATCH = 0x2000000; + + // Player sprites in multiplayer modes are modified + // using an internal color lookup table for re-indexing. + // If 0x4 0x8 or 0xc, + // use a translation table for player colormaps + public static final int MF_TRANSLATION = 0xc000000; + // Hmm ???. + public static final int MF_TRANSSHIFT = 26; + + /* + * The following methods were for the most part "contextless" and + * instance-specific, so they were implemented here rather that being + * scattered all over the package. + */ + /** + * P_SetMobjState Returns true if the mobj is still present. + */ + public boolean SetMobjState(statenum_t state) { + state_t st; + + do { + if (state == statenum_t.S_NULL) { + mobj_state = null; + // MAES/_D_: uncommented this as it should work by now (?). + A.RemoveMobj(this); + return false; + } + + st = states[state.ordinal()]; + mobj_state = st; + mobj_tics = st.tics; + mobj_sprite = st.sprite; + mobj_frame = st.frame; + + // Modified handling. + // Call action functions when the state is set + // TODO: try find a bug + if (st.action.isParamType(MobjConsumer.class)) { + st.action.fun(MobjConsumer.class).accept(A, this); + } + + state = st.nextstate; + } while (!eval(mobj_tics)); + + return true; + } + + /** + * P_ZMovement + */ + public void ZMovement() { + @fixed_t + int dist, delta; + + // check for smooth step up + if ((player != null) && z < floorz) { + player.viewheight -= floorz - z; + + player.deltaviewheight = (VIEWHEIGHT - player.viewheight) >> 3; + } + + // adjust height + z += momz; + + if (((flags & MF_FLOAT) != 0) && target != null) { + // float down towards target if too close + if ((flags & MF_SKULLFLY) == 0 && (flags & MF_INFLOAT) == 0) { + dist = AproxDistance(x - target.x, y - target.y); + + delta = (target.z + (height >> 1)) - z; + + if (delta < 0 && dist < -(delta * 3)) { + z -= FLOATSPEED; + } else if (delta > 0 && dist < (delta * 3)) { + z += FLOATSPEED; + } + } + + } + + // clip movement + if (z <= floorz) { + // hit the floor + + // Note (id): + // somebody left this after the setting momz to 0, + // kinda useless there. + if ((flags & MF_SKULLFLY) != 0) { + // the skull slammed into something + momz = -momz; + } + + if (momz < 0) { + if (player != null && (momz < -GRAVITY * 8)) { + // Squat down. + // Decrease viewheight for a moment + // after hitting the ground (hard), + // and utter appropriate sound. + player.deltaviewheight = momz >> 3; + A.DOOM.doomSound.StartSound(this, sfxenum_t.sfx_oof); + } + momz = 0; + } + z = floorz; + + if ((flags & MF_MISSILE) != 0 && (flags & MF_NOCLIP) == 0) { + A.ExplodeMissile(this); + return; + } + } else if ((flags & MF_NOGRAVITY) == 0) { + if (momz == 0) { + momz = -GRAVITY * 2; + } else { + momz -= GRAVITY; + } + } + + if (z + height > ceilingz) { + // hit the ceiling + if (momz > 0) { + momz = 0; + } + { + z = ceilingz - height; + } + + if ((flags & MF_SKULLFLY) != 0) { // the skull slammed into + // something + momz = -momz; + } + + if ((flags & MF_MISSILE) != 0 && (flags & MF_NOCLIP) == 0) { + A.ExplodeMissile(this); + } + } + } + + public int eflags; // DOOM LEGACY + + // Fields used only during DSG unmarshalling + public int stateid; + public int playerid; + public int p_tracer; + + /** Unique thing id, used during sync debugging */ + public int thingnum; + + public void clear() { + fastclear.rewind(); + try { + this.unpack(mobj_t.fastclear); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "clear failure", e); + } + } + + // _D_: to permit this object to save/load + @Override + public void read(DataInputStream f) throws IOException { + // More efficient, avoids duplicating code and + // handles little endian better. + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + f.read(buffer.array()); + this.unpack(buffer); + } + + @Override + public void write(DataOutputStream f) throws IOException { + + // More efficient, avoids duplicating code and + // handles little endian better. + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + this.pack(buffer); + f.write(buffer.array()); + + } + + @Override + public void pack(ByteBuffer b) throws IOException { + b.order(ByteOrder.LITTLE_ENDIAN); + super.pack(b); // Pack the head thinker. + b.putInt(x); + b.putInt(y); + b.putInt(z); + b.putInt(pointer(snext)); + b.putInt(pointer(sprev)); + b.putInt((int) (this.angle & Tables.BITS32)); + b.putInt(this.mobj_sprite.ordinal()); + b.putInt(this.mobj_frame); + b.putInt(pointer(bnext)); + b.putInt(pointer(bprev)); + b.putInt(pointer(subsector)); + b.putInt(floorz); + b.putInt(ceilingz); + b.putInt(radius); + b.putInt(height); + b.putInt(momx); + b.putInt(momy); + b.putInt(momz); + b.putInt(validcount); + b.putInt(type.ordinal()); + b.putInt(pointer(info)); // TODO: mobjinfo + b.putInt((int) (this.mobj_tics & Tables.BITS32)); + b.putInt(this.mobj_state.id); // TODO: state OK? + b.putInt((int) this.flags); // truncate + b.putInt(this.health); + b.putInt(this.movedir); + b.putInt(this.movecount); + b.putInt(pointer(target)); // TODO: p_target? + b.putInt(this.reactiontime); + b.putInt(this.threshold); + // Check for player. + if (this.player != null) { + b.putInt(1 + this.player.identify()); + + // System.out.printf("Mobj with hashcode %d is player %d",pointer(this),1+this.player.identify()); + } else { + b.putInt(0); + } + b.putInt(lastlook); + spawnpoint.pack(b); + b.putInt(pointer(tracer)); // tracer pointer stored. + + } + + @Override + public void unpack(ByteBuffer b) throws IOException { + b.order(ByteOrder.LITTLE_ENDIAN); + super.unpack(b); // 12 Read the head thinker. + this.x = b.getInt(); // 16 + this.y = b.getInt(); // 20 + this.z = b.getInt(); // 24 + b.getLong(); // TODO: snext, sprev. When are those set? 32 + this.angle = Tables.BITS32 & b.getInt(); // 36 + this.mobj_sprite = spritenum_t.values()[b.getInt()]; // 40 + this.mobj_frame = b.getInt(); // 44 + b.getLong(); // TODO: bnext, bprev. When are those set? 52 + b.getInt(); // TODO: subsector 56 + this.floorz = b.getInt(); // 60 + this.ceilingz = b.getInt(); // 64 + this.radius = b.getInt(); // 68 + this.height = b.getInt(); // 72 + this.momx = b.getInt(); // 76 + this.momy = b.getInt(); // 80 + this.momz = b.getInt(); // 84 + this.validcount = b.getInt(); // 88 + this.type = mobjtype_t.values()[b.getInt()]; // 92 + b.getInt(); // TODO: mobjinfo (deduced from type) //96 + this.mobj_tics = Tables.BITS32 & b.getInt(); // 100 + // System.out.println("State"+f.readLEInt()); + this.stateid = b.getInt(); // TODO: state OK? + this.flags = b.getInt() & Tables.BITS32; // Only 32-bit flags can be restored + this.health = b.getInt(); + this.movedir = b.getInt(); + this.movecount = b.getInt(); + this.p_target = b.getInt(); + this.reactiontime = b.getInt(); + this.threshold = b.getInt(); + this.playerid = b.getInt(); // TODO: player. Non null should mean that + // it IS a player. + this.lastlook = b.getInt(); + spawnpoint.unpack(b); + this.p_tracer = b.getInt(); // TODO: tracer + } + + private static ByteBuffer buffer = ByteBuffer.allocate(154); + private static ByteBuffer fastclear = ByteBuffer.allocate(154); + + /* + * @Override protected void finalize(){ count++; if (count%100==0) + * System.err + * .printf("Total %d Mobj %s@%d finalized free memory: %d\n",count, + * this.type.name(),this.hashCode(),Runtime.getRuntime().freeMemory()); } + */ + protected static int count = 0; + + // TODO: a linked list of sectors where this object appears + // public msecnode_t touching_sectorlist; + // Sound origin stuff + @Override + public final int getX() { + return x; + } + + @Override + public final int getY() { + return y; + } + + @Override + public final int getZ() { + return z; + } + + @Override + public String toString() { + return String.format("%s %d", this.type, this.thingnum); + } + +} \ No newline at end of file diff --git a/doom/src/p/plat_e.java b/doom/src/p/plat_e.java new file mode 100644 index 0000000..b618a83 --- /dev/null +++ b/doom/src/p/plat_e.java @@ -0,0 +1,11 @@ +package p; + +// +// P_PLATS +// +public enum plat_e { + up, + down, + waiting, + in_stasis +} \ No newline at end of file diff --git a/doom/src/p/plat_t.java b/doom/src/p/plat_t.java new file mode 100644 index 0000000..6303a91 --- /dev/null +++ b/doom/src/p/plat_t.java @@ -0,0 +1,105 @@ +package p; + +import doom.SourceCode.fixed_t; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import rr.SectorAction; +import rr.sector_t; +import w.DoomIO; +import w.IReadableDoomObject; + +public class plat_t extends SectorAction implements IReadableDoomObject { + + public sector_t sector; + public @fixed_t + int speed, low, high; + public int wait; + public int count; + public plat_e status; + public plat_e oldstatus; + public boolean crush; + public int tag; + public plattype_e type; + + public plat_t() { + // These must never be null so they get the lowest ordinal value. + // by default. + this.status = plat_e.up; + this.oldstatus = plat_e.up; + } + + @Override + public void read(DataInputStream f) throws IOException { + + super.read(f); // Call thinker reader first + super.sectorid = DoomIO.readLEInt(f); // Sector index + speed = DoomIO.readLEInt(f); + low = DoomIO.readLEInt(f); + high = DoomIO.readLEInt(f); + wait = DoomIO.readLEInt(f); + count = DoomIO.readLEInt(f); + status = plat_e.values()[DoomIO.readLEInt(f)]; + oldstatus = plat_e.values()[DoomIO.readLEInt(f)]; + //System.out.println(status); + //System.out.println(oldstatus); + crush = DoomIO.readIntBoolean(f); + tag = DoomIO.readLEInt(f); + type = plattype_e.values()[DoomIO.readLEInt(f)]; + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(super.sectorid); // 16 + b.putInt(speed);//20 + b.putInt(low); // 24 + b.putInt(high); //28 + b.putInt(wait); //32 + b.putInt(count); //36 + b.putInt(status.ordinal()); //40 + b.putInt(oldstatus.ordinal()); //44 + //System.out.println(status); + //System.out.println(oldstatus); + b.putInt(crush ? 1 : 0); // 48 + b.putInt(tag); // 52 + b.putInt(type.ordinal()); // 56 + } + + public vldoor_t asVlDoor(sector_t[] sectors) { + /* + typedef struct + { + thinker_t thinker; + vldoor_e type; + sector_t* sector; + fixed_t topheight; + fixed_t speed; + + // 1 = up, 0 = waiting at top, -1 = down + int direction; + + // tics to wait at the top + int topwait; + // (keep in case a door going down is reset) + // when it reaches 0, start going down + int topcountdown; + + } vldoor_t; + */ + + vldoor_t tmp = new vldoor_t(); + tmp.next = this.next; + tmp.prev = this.prev; + tmp.thinkerFunction = this.thinkerFunction; + tmp.type = vldoor_e.values()[sector.id % vldoor_e.VALUES]; + tmp.sector = sectors[this.speed % sectors.length]; + tmp.topheight = this.low; + tmp.speed = this.high; + tmp.direction = this.wait; + tmp.topwait = this.count; + tmp.topcountdown = this.status.ordinal(); + + return tmp; + } +} \ No newline at end of file diff --git a/doom/src/p/plattype_e.java b/doom/src/p/plattype_e.java new file mode 100644 index 0000000..76da5b7 --- /dev/null +++ b/doom/src/p/plattype_e.java @@ -0,0 +1,10 @@ +package p; + +public enum plattype_e { + perpetualRaise, + downWaitUpStay, + raiseAndChange, + raiseToNearestAndChange, + blazeDWUS + +} \ No newline at end of file diff --git a/doom/src/p/pspdef_t.java b/doom/src/p/pspdef_t.java new file mode 100644 index 0000000..5c3d54b --- /dev/null +++ b/doom/src/p/pspdef_t.java @@ -0,0 +1,46 @@ +package p; + +import data.state_t; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import w.DoomIO; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +public class pspdef_t implements IReadableDoomObject, IPackableDoomObject { + + public pspdef_t() { + state = new state_t(); + } + + /** a NULL state means not active */ + public state_t state; + public int tics; + /** fixed_t */ + public int sx, sy; + // When read from disk. + public int readstate; + + @Override + public void read(DataInputStream f) throws IOException { + //state=data.info.states[f.readLEInt()]; + readstate = DoomIO.readLEInt(f); + tics = DoomIO.readLEInt(f); + sx = DoomIO.readLEInt(f); + sy = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer f) throws IOException { + if (state == null) { + f.putInt(0); + } else { + f.putInt(state.id); + } + f.putInt(tics); + f.putInt(sx); + f.putInt(sy); + } + +} \ No newline at end of file diff --git a/doom/src/p/result_e.java b/doom/src/p/result_e.java new file mode 100644 index 0000000..a74331c --- /dev/null +++ b/doom/src/p/result_e.java @@ -0,0 +1,7 @@ +package p; + +public enum result_e { + ok, + crushed, + pastdest +} \ No newline at end of file diff --git a/doom/src/p/sd_e.java b/doom/src/p/sd_e.java new file mode 100644 index 0000000..41ca544 --- /dev/null +++ b/doom/src/p/sd_e.java @@ -0,0 +1,7 @@ +package p; + +public enum sd_e { + sd_opening, + sd_waiting, + sd_closing +} \ No newline at end of file diff --git a/doom/src/p/sdt_e.java b/doom/src/p/sdt_e.java new file mode 100644 index 0000000..ce4e7f2 --- /dev/null +++ b/doom/src/p/sdt_e.java @@ -0,0 +1,7 @@ +package p; + +public enum sdt_e { + sdt_openOnly, + sdt_closeOnly, + sdt_openAndClose +} \ No newline at end of file diff --git a/doom/src/p/slidedoor_t.java b/doom/src/p/slidedoor_t.java new file mode 100644 index 0000000..37cd6c7 --- /dev/null +++ b/doom/src/p/slidedoor_t.java @@ -0,0 +1,24 @@ +package p; + +import static p.ActiveStates.T_SlidingDoor; +import rr.SectorAction; +import rr.line_t; +import rr.sector_t; + +public class slidedoor_t extends SectorAction { + + public sdt_e type; + public line_t line; + public int frame; + public int whichDoorIndex; + public int timer; + public sector_t frontsector; + public sector_t backsector; + public sd_e status; + + public slidedoor_t() { + type = sdt_e.sdt_closeOnly; + status = sd_e.sd_closing; + thinkerFunction = T_SlidingDoor; + } +} \ No newline at end of file diff --git a/doom/src/p/slideframe_t.java b/doom/src/p/slideframe_t.java new file mode 100644 index 0000000..1076ec8 --- /dev/null +++ b/doom/src/p/slideframe_t.java @@ -0,0 +1,7 @@ +package p; + +public class slideframe_t { + + public int[] frontFrames = new int[4]; + public int[] backFrames = new int[4]; +} \ No newline at end of file diff --git a/doom/src/p/slidename_t.java b/doom/src/p/slidename_t.java new file mode 100644 index 0000000..4cdb80d --- /dev/null +++ b/doom/src/p/slidename_t.java @@ -0,0 +1,31 @@ +package p; + +public class slidename_t { + + public slidename_t() { + + } + + public slidename_t(String frontFrame1, String frontFrame2, + String frontFrame3, String frontFrame4, String backFrame1, + String backFrame2, String backFrame3, String backFrame4) { + this.frontFrame1 = frontFrame1; + this.frontFrame2 = frontFrame2; + this.frontFrame3 = frontFrame3; + this.frontFrame4 = frontFrame4; + this.backFrame1 = backFrame1; + this.backFrame2 = backFrame2; + this.backFrame3 = backFrame3; + this.backFrame4 = backFrame4; + } + + public String frontFrame1; + public String frontFrame2; + public String frontFrame3; + public String frontFrame4; + public String backFrame1; + public String backFrame2; + public String backFrame3; + public String backFrame4; + +} \ No newline at end of file diff --git a/doom/src/p/stair_e.java b/doom/src/p/stair_e.java new file mode 100644 index 0000000..7fe40af --- /dev/null +++ b/doom/src/p/stair_e.java @@ -0,0 +1,6 @@ +package p; + +public enum stair_e { + build8, // slowly build by 8 + turbo16 // quickly build by 16 +} \ No newline at end of file diff --git a/doom/src/p/strobe_t.java b/doom/src/p/strobe_t.java new file mode 100644 index 0000000..c70ad06 --- /dev/null +++ b/doom/src/p/strobe_t.java @@ -0,0 +1,57 @@ +package p; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import rr.SectorAction; +import w.DoomIO; + +public class strobe_t extends SectorAction { + + public int count; + public int minlight; + public int maxlight; + public int darktime; + public int brighttime; + + // + // T_StrobeFlash + // + public void StrobeFlash() { + if (--count != 0) { + return; + } + + if (sector.lightlevel == minlight) { + sector.lightlevel = (short) maxlight; + count = brighttime; + } else { + sector.lightlevel = (short) minlight; + count = darktime; + } + + } + + @Override + public void read(DataInputStream f) throws IOException { + + super.read(f); // Call thinker reader first + super.sectorid = DoomIO.readLEInt(f); // Sector index + count = DoomIO.readLEInt(f); + maxlight = DoomIO.readLEInt(f); + minlight = DoomIO.readLEInt(f); + darktime = DoomIO.readLEInt(f); + brighttime = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(super.sectorid); // 16 + b.putInt(count); //20 + b.putInt(maxlight);//24 + b.putInt(minlight);//28 + b.putInt(darktime);//32 + b.putInt(brighttime);//36 + } +}; \ No newline at end of file diff --git a/doom/src/p/switchlist_t.java b/doom/src/p/switchlist_t.java new file mode 100644 index 0000000..ef26320 --- /dev/null +++ b/doom/src/p/switchlist_t.java @@ -0,0 +1,47 @@ +package p; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; +import w.DoomBuffer; + +public class switchlist_t + implements CacheableDoomObject { + + public switchlist_t() { + + } + + // Were char[9] + public String name1; + + public String name2; + + public short episode; + + public switchlist_t(String name1, String name2, int episode) { + super(); + this.name1 = name1; + this.name2 = name2; + this.episode = (short) episode; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + // Like most Doom structs... + buf.order(ByteOrder.LITTLE_ENDIAN); + this.name1 = DoomBuffer.getNullTerminatedString(buf, 9); + this.name2 = DoomBuffer.getNullTerminatedString(buf, 9); + this.episode = buf.getShort(); + } + + public final static int size() { + return 20; + } + + public String toString() { + return String.format("%s %s %d", name1, name2, episode); + } +} \ No newline at end of file diff --git a/doom/src/p/vldoor_e.java b/doom/src/p/vldoor_e.java new file mode 100644 index 0000000..2bb9f6b --- /dev/null +++ b/doom/src/p/vldoor_e.java @@ -0,0 +1,17 @@ +package p; + +// +// P_DOORS +// +public enum vldoor_e { + normal, + close30ThenOpen, + close, + open, + raiseIn5Mins, + blazeRaise, + blazeOpen, + blazeClose; + + public static final int VALUES = vldoor_e.values().length; +} \ No newline at end of file diff --git a/doom/src/p/vldoor_t.java b/doom/src/p/vldoor_t.java new file mode 100644 index 0000000..049ef70 --- /dev/null +++ b/doom/src/p/vldoor_t.java @@ -0,0 +1,51 @@ +package p; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import rr.SectorAction; +import w.DoomIO; +import w.IReadableDoomObject; + +public class vldoor_t extends SectorAction implements IReadableDoomObject { + + public vldoor_e type; + /** fixed_t */ + public int topheight, speed; + + /** 1 = up, 0 = waiting at top, -1 = down */ + public int direction; + + /** tics to wait at the top */ + public int topwait; + + /**(keep in case a door going down is reset) + when it reaches 0, start going down */ + public int topcountdown; + + @Override + public void read(DataInputStream f) throws IOException { + + super.read(f); // Call thinker reader first + type = vldoor_e.values()[DoomIO.readLEInt(f)]; + super.sectorid = DoomIO.readLEInt(f); // Sector index (or pointer?) + topheight = DoomIO.readLEInt(f); + speed = DoomIO.readLEInt(f); + direction = DoomIO.readLEInt(f); + topwait = DoomIO.readLEInt(f); + topcountdown = DoomIO.readLEInt(f); + } + + @Override + public void pack(ByteBuffer b) throws IOException { + super.pack(b); //12 + b.putInt(type.ordinal()); // 16 + b.putInt(super.sectorid); // 20 + b.putInt(topheight); // 24 + b.putInt(speed); //28 + b.putInt(direction); // 32 + b.putInt(topwait); //36 + b.putInt(topcountdown); //40 + } + +} \ No newline at end of file diff --git a/doom/src/pooling/AudioChunkPool.java b/doom/src/pooling/AudioChunkPool.java new file mode 100644 index 0000000..3323f4e --- /dev/null +++ b/doom/src/pooling/AudioChunkPool.java @@ -0,0 +1,26 @@ +package pooling; + +import s.AudioChunk; + +// Referenced classes of package pooling: +// ObjectPool +public class AudioChunkPool extends ObjectQueuePool { + + public AudioChunkPool() { + // A reasonable time limit for Audio chunks + super(10000L); + } + + protected AudioChunk create() { + return new AudioChunk(); + } + + public void expire(AudioChunk o) { + o.free = true; + } + + public boolean validate(AudioChunk o) { + return o.free; + } + +} \ No newline at end of file diff --git a/doom/src/pooling/GenericIntMap.java b/doom/src/pooling/GenericIntMap.java new file mode 100644 index 0000000..c864e2d --- /dev/null +++ b/doom/src/pooling/GenericIntMap.java @@ -0,0 +1,72 @@ +package pooling; + +import java.util.Arrays; + +public abstract class GenericIntMap { + + protected static final int DEFAULT_CAPACITY = 16; + + /** Concrete implementations must allocate patches + * + */ + GenericIntMap() { + + lumps = new int[DEFAULT_CAPACITY]; + // patches = new K[DEFAULT_CAPACITY]; + } + + public boolean containsKey(int lump) { + return indexOf(lump) >= 0; + } + + public K get(int lump) { + int index = indexOf(lump); + if (index >= 0) { + return patches[index]; + } else { + return null; + } + } + + public void put(int lump, K patch) { + int index = indexOf(lump); + if (index >= 0) { + patches[index] = patch; + } else { + ensureCapacity(numEntries + 1); + int newIndex = ~index; + int moveCount = numEntries - newIndex; + if (moveCount > 0) { + System.arraycopy(lumps, newIndex, lumps, newIndex + 1, moveCount); + System.arraycopy(patches, newIndex, patches, newIndex + 1, moveCount); + } + lumps[newIndex] = lump; + patches[newIndex] = patch; + ++numEntries; + } + } + + protected void ensureCapacity(int cap) { + while (lumps.length <= cap) { + lumps + = Arrays.copyOf(lumps, Math.max(lumps.length * 2, DEFAULT_CAPACITY)); + } + while (patches.length <= cap) { + patches + = Arrays.copyOf(patches, Math.max(patches.length * 2, DEFAULT_CAPACITY)); + } + } + + protected int indexOf(int lump) { + return Arrays.binarySearch(lumps, 0, numEntries, lump); + //for (int i=0;i { + + private static final Logger LOGGER = Loggers.getLogger(ObjectPool.class.getName()); + + private static final boolean D = false; + + public ObjectPool(long expirationTime) { + this.expirationTime = expirationTime; + locked = new Hashtable<>(); + unlocked = new Hashtable<>(); + } + + protected abstract K create(); + + public abstract boolean validate(K obj); + + public abstract void expire(K obj); + + public synchronized K checkOut() { + long now = System.currentTimeMillis(); + K t; + if (!unlocked.isEmpty()) { + Enumeration e = unlocked.keys(); + // System.out.println((new StringBuilder("Pool size ")).append(unlocked.size()).toString()); + while (e.hasMoreElements()) { + t = e.nextElement(); + if (now - ((Long) unlocked.get(t)).longValue() > expirationTime) { + // object has expired + if (t instanceof mobj_t) { + if (D) { + LOGGER.log(Level.FINE, String.format("Object %s expired", t.toString())); + } + } + unlocked.remove(t); + expire(t); + t = null; + } else { + if (validate(t)) { + unlocked.remove(t); + locked.put(t, Long.valueOf(now)); + if (D) { + if (t instanceof mobj_t) { + LOGGER.log(Level.FINE, String.format("Object %s reused", t.toString())); + } + } + return t; + } + + // object failed validation + unlocked.remove(t); + expire(t); + t = null; + } + } + } + + t = create(); + locked.put(t, Long.valueOf(now)); + return t; + } + + public synchronized void checkIn(K t) { + if (D) { + if (t instanceof mobj_t) { + LOGGER.log(Level.FINE, String.format("Object %s returned to the pool", t.toString())); + } + } + locked.remove(t); + unlocked.put(t, Long.valueOf(System.currentTimeMillis())); + } + + private long expirationTime; + protected Hashtable locked; + private Hashtable unlocked; +} \ No newline at end of file diff --git a/doom/src/pooling/ObjectQueuePool.java b/doom/src/pooling/ObjectQueuePool.java new file mode 100644 index 0000000..dde91bd --- /dev/null +++ b/doom/src/pooling/ObjectQueuePool.java @@ -0,0 +1,59 @@ +package pooling; + +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.mobj_t; + +/** A convenient object pooling class, derived from the stock ObjectPool. + * + * It's about 50% faster than calling new, and MUCH faster than ObjectPool + * because it doesn't do that bullshit object cleanup every so often. + * + */ +public abstract class ObjectQueuePool { + + private static final Logger LOGGER = Loggers.getLogger(ObjectQueuePool.class.getName()); + + private static final boolean D = false; + + public ObjectQueuePool(long expirationTime) { + locked = new Stack<>(); + + } + + protected abstract K create(); + + public abstract boolean validate(K obj); + + public abstract void expire(K obj); + + public void drain() { + locked.clear(); + } + + public K checkOut() { + + K t; + if (!locked.isEmpty()) { + return locked.pop(); + + } + + t = create(); + return t; + } + + public void checkIn(K t) { + if (D) { + if (t instanceof mobj_t) { + LOGGER.log(Level.FINE, String.format("Object %s returned to the pool", t.toString())); + } + } + locked.push(t); + } + + protected Stack locked; + // private Hashtable unlocked; +} \ No newline at end of file diff --git a/doom/src/pooling/RoguePatchMap.java b/doom/src/pooling/RoguePatchMap.java new file mode 100644 index 0000000..41bf6d2 --- /dev/null +++ b/doom/src/pooling/RoguePatchMap.java @@ -0,0 +1,9 @@ +package pooling; + +public class RoguePatchMap extends GenericIntMap { + + public RoguePatchMap() { + super(); + patches = new byte[DEFAULT_CAPACITY][][]; + } +} \ No newline at end of file diff --git a/doom/src/pooling/RoguePatchMap2.java b/doom/src/pooling/RoguePatchMap2.java new file mode 100644 index 0000000..6b840dc --- /dev/null +++ b/doom/src/pooling/RoguePatchMap2.java @@ -0,0 +1,62 @@ +package pooling; + +import java.util.Arrays; + +public class RoguePatchMap2 { + + private static final int DEFAULT_CAPACITY = 16; + + public RoguePatchMap2() { + lumps = new int[DEFAULT_CAPACITY]; + patches = new byte[DEFAULT_CAPACITY][][]; + } + + boolean containsKey(int lump) { + return indexOf(lump) >= 0; + } + + public byte[][] get(int lump) { + int index = indexOf(lump); + if (index >= 0) { + return patches[index]; + } else { + return null; + } + } + + public void put(int lump, byte[][] patch) { + int index = indexOf(lump); + if (index >= 0) { + patches[index] = patch; + } else { + ensureCapacity(numEntries + 1); + int newIndex = ~index; + int moveCount = numEntries - newIndex; + if (moveCount > 0) { + System.arraycopy(lumps, newIndex, lumps, newIndex + 1, moveCount); + System.arraycopy(patches, newIndex, patches, newIndex + 1, moveCount); + } + lumps[newIndex] = lump; + patches[newIndex] = patch; + ++numEntries; + } + } + + private void ensureCapacity(int cap) { + while (lumps.length <= cap) { + lumps + = Arrays.copyOf(lumps, Math.max(lumps.length * 2, DEFAULT_CAPACITY)); + } + while (patches.length <= cap) { + patches + = Arrays.copyOf(patches, Math.max(patches.length * 2, DEFAULT_CAPACITY)); + } + } + + private int indexOf(int lump) { + return Arrays.binarySearch(lumps, 0, numEntries, lump); + } + private int[] lumps; + private int numEntries; + private byte[][][] patches; +} \ No newline at end of file diff --git a/doom/src/rr/AbstractThings.java b/doom/src/rr/AbstractThings.java new file mode 100644 index 0000000..89ef98a --- /dev/null +++ b/doom/src/rr/AbstractThings.java @@ -0,0 +1,852 @@ +package rr; + +import static data.Defines.FF_FRAMEMASK; +import static data.Defines.FF_FULLBRIGHT; +import static data.Defines.SIL_BOTTOM; +import static data.Defines.SIL_TOP; +import static data.Defines.pw_invisibility; +import static doom.player_t.NUMPSPRITES; +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import static p.mobj_t.MF_TRANSLATION; +import p.pspdef_t; +import rr.drawfuns.ColFuncs; +import rr.drawfuns.ColVars; +import rr.drawfuns.ColumnFunction; +import static rr.line_t.ML_DONTPEGBOTTOM; +import v.graphics.Palettes; +import v.scale.VideoScale; +import v.tables.LightsAndColors; +import w.IWadLoader; + +/** + * Refresh of things, i.e. objects represented by sprites. This abstract + * class is the base for all implementations, and contains the gory clipping + * and priority stuff. It can terminate by drawing directly, or by buffering + * into a pipeline for parallelized drawing. + * + * It need to be aware of almost everything in the renderer, which means that + * it's a PITA to keep "disembodied". Then again, this probably means it's more + * extensible... + * + * + * + */ +public abstract class AbstractThings implements IMaskedDrawer { + + private final static boolean RANGECHECK = false; + + protected short[] maskedtexturecol; + protected int pmaskedtexturecol = 0; + + // Cache those you get from the sprite manager + protected int[] spritewidth, spriteoffset, spritetopoffset; + + /** fixed_t */ + protected int pspritescale, pspriteiscale, pspritexscale, + pspriteyscale, skyscale; + + // Used for masked segs + protected int rw_scalestep; + + protected int spryscale; + + protected int sprtopscreen; + + protected short[] mfloorclip; + + protected int p_mfloorclip; + + protected short[] mceilingclip; + + protected int p_mceilingclip; + + protected sector_t frontsector; + + protected sector_t backsector; + + // This must be "pegged" to the one used by the default renderer. + protected ColVars maskedcvars; + + protected ColumnFunction colfunc; + protected ColFuncs colfuncs; + protected ColFuncs colfuncshi; + protected ColFuncs colfuncslow; + protected final VideoScale vs; + protected final LightsAndColors colormaps; + protected final ViewVars view; + protected final SegVars seg_vars; + protected final TextureManager TexMan; + protected final IDoomSystem I; + protected final ISpriteManager SM; + protected final BSPVars MyBSP; + protected final IVisSpriteManagement VIS; + protected final IWadLoader W; + protected final vissprite_t avis; + + public AbstractThings(VideoScale vs, SceneRenderer R) { + this.colfuncshi = R.getColFuncsHi(); + this.colfuncslow = R.getColFuncsLow(); + this.colormaps = R.getColorMap(); + this.view = R.getView(); + this.seg_vars = R.getSegVars(); + this.TexMan = R.getTextureManager(); + this.I = R.getDoomSystem(); + this.SM = R.getSpriteManager(); + this.MyBSP = R.getBSPVars(); + this.VIS = R.getVisSpriteManager(); + this.W = R.getWadLoader(); + this.avis = new vissprite_t<>(); + this.maskedcvars = R.getMaskedDCVars(); + this.vs = vs; + clipbot = new short[vs.getScreenWidth()]; + cliptop = new short[vs.getScreenWidth()]; + } + + @Override + public void cacheSpriteManager(ISpriteManager SM) { + this.spritewidth = SM.getSpriteWidth(); + this.spriteoffset = SM.getSpriteOffset(); + this.spritetopoffset = SM.getSpriteTopOffset(); + } + + /** + * R_DrawVisSprite mfloorclip and mceilingclip should also be set. + * Sprites are actually drawn here. MAES: Optimized. No longer needed to + * pass x1 and x2 parameters (useless) +2 fps on nuts.wad timedemo. + */ + @SuppressWarnings("unchecked") + protected void DrawVisSprite(vissprite_t vis) { + column_t column; + int texturecolumn; + int frac; // fixed_t + patch_t patch; + + // At this point, the view angle (and patch) has already been + // chosen. Go back. + patch = W.CachePatchNum(vis.patch + SM.getFirstSpriteLump()); + + maskedcvars.dc_colormap = vis.colormap; + // colfunc=glasscolfunc; + if (maskedcvars.dc_colormap == null) { + // NULL colormap = shadow draw + colfunc = colfuncs.fuzz; + } else if ((vis.mobjflags & MF_TRANSLATION) != 0) { + colfunc = colfuncs.trans; + maskedcvars.dc_translation = (T) colormaps.getTranslationTable(vis.mobjflags); + } + + maskedcvars.dc_iscale = Math.abs(vis.xiscale) >> view.detailshift; + maskedcvars.dc_texturemid = vis.texturemid; + frac = vis.startfrac; + spryscale = vis.scale; + sprtopscreen + = view.centeryfrac + - FixedMul(maskedcvars.dc_texturemid, spryscale); + + // A texture height of 0 means "not tiling" and holds for + // all sprite/masked renders. + maskedcvars.dc_texheight = 0; + + for (maskedcvars.dc_x = vis.x1; maskedcvars.dc_x <= vis.x2; maskedcvars.dc_x++, frac + += vis.xiscale) { + texturecolumn = frac >> FRACBITS; + if (RANGECHECK) { + if (texturecolumn < 0 || texturecolumn >= patch.width) { + I.Error("R_DrawSpriteRange: bad texturecolumn"); + } + } + column = patch.columns[texturecolumn]; + DrawMaskedColumn(column); + } + + colfunc = colfuncs.masked; + } + + /** + * R_RenderMaskedSegRange + * + * @param ds + * @param x1 + * @param x2 + */ + protected void RenderMaskedSegRange(drawseg_t ds, int x1, int x2) { + int index; + + int lightnum; + int texnum; + + // System.out.printf("RenderMaskedSegRange from %d to %d\n",x1,x2); + // Calculate light table. + // Use different light tables + // for horizontal / vertical / diagonal. Diagonal? + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + MyBSP.curline = ds.curline; + frontsector = MyBSP.curline.frontsector; + backsector = MyBSP.curline.backsector; + texnum = TexMan.getTextureTranslation(MyBSP.curline.sidedef.midtexture); + // System.out.print(" for texture "+textures[texnum].name+"\n:"); + lightnum + = (frontsector.lightlevel >> colormaps.lightSegShift()) + colormaps.extralight; + + if (MyBSP.curline.v1y == MyBSP.curline.v2y) { + lightnum--; + } else if (MyBSP.curline.v1x == MyBSP.curline.v2x) { + lightnum++; + } + + // Killough code. + colormaps.walllights + = lightnum >= colormaps.lightLevels() ? colormaps.scalelight[colormaps.lightLevels() - 1] + : lightnum < 0 ? colormaps.scalelight[0] + : colormaps.scalelight[lightnum]; + + // Get the list + maskedtexturecol = ds.getMaskedTextureColList(); + // And this is the pointer. + pmaskedtexturecol = ds.getMaskedTextureColPointer(); + + rw_scalestep = ds.scalestep; + spryscale = ds.scale1 + (x1 - ds.x1) * rw_scalestep; + + // HACK to get "pointers" inside clipping lists + mfloorclip = ds.getSprBottomClipList(); + p_mfloorclip = ds.getSprBottomClipPointer(); + mceilingclip = ds.getSprTopClipList(); + p_mceilingclip = ds.getSprTopClipPointer(); + // find positioning + if ((MyBSP.curline.linedef.flags & ML_DONTPEGBOTTOM) != 0) { + maskedcvars.dc_texturemid + = frontsector.floorheight > backsector.floorheight ? frontsector.floorheight + : backsector.floorheight; + maskedcvars.dc_texturemid + = maskedcvars.dc_texturemid + TexMan.getTextureheight(texnum) + - view.z; + } else { + maskedcvars.dc_texturemid + = frontsector.ceilingheight < backsector.ceilingheight ? frontsector.ceilingheight + : backsector.ceilingheight; + maskedcvars.dc_texturemid = maskedcvars.dc_texturemid - view.z; + } + maskedcvars.dc_texturemid += MyBSP.curline.sidedef.rowoffset; + + if (colormaps.fixedcolormap != null) { + maskedcvars.dc_colormap = colormaps.fixedcolormap; + } + + // Texture height must be set at this point. This will trigger + // tiling. For sprites, it should be set to 0. + maskedcvars.dc_texheight + = TexMan.getTextureheight(texnum) >> FRACBITS; + + // draw the columns + for (maskedcvars.dc_x = x1; maskedcvars.dc_x <= x2; maskedcvars.dc_x++) { + // calculate lighting + if (maskedtexturecol[pmaskedtexturecol + maskedcvars.dc_x] != Short.MAX_VALUE) { + if (colormaps.fixedcolormap == null) { + index = spryscale >>> colormaps.lightScaleShift(); + + if (index >= colormaps.maxLightScale()) { + index = colormaps.maxLightScale() - 1; + } + + maskedcvars.dc_colormap = colormaps.walllights[index]; + } + + sprtopscreen + = view.centeryfrac + - FixedMul(maskedcvars.dc_texturemid, spryscale); + maskedcvars.dc_iscale = (int) (0xffffffffL / spryscale); + + // draw the texture + column_t data = TexMan.GetColumnStruct(texnum, + (int) maskedtexturecol[pmaskedtexturecol + + maskedcvars.dc_x]);// -3); + + DrawMaskedColumn(data); + + maskedtexturecol[pmaskedtexturecol + maskedcvars.dc_x] + = Short.MAX_VALUE; + } + spryscale += rw_scalestep; + } + + } + + /** + * R_DrawPSprite Draws a "player sprite" with slighly different rules + * than normal sprites. This is actually a PITA, at best :-/ + */ + protected void DrawPSprite(pspdef_t psp) { + + int tx; + int x1; + int x2; + spritedef_t sprdef; + spriteframe_t sprframe; + vissprite_t vis; + int lump; + boolean flip; + + // decide which patch to use (in terms of angle?) + if (RANGECHECK) { + if (psp.state.sprite.ordinal() >= SM.getNumSprites()) { + I.Error("R_ProjectSprite: invalid sprite number %d ", + psp.state.sprite); + } + } + + sprdef = SM.getSprite(psp.state.sprite.ordinal()); + + if (RANGECHECK) { + if ((psp.state.frame & FF_FRAMEMASK) >= sprdef.numframes) { + I.Error("R_ProjectSprite: invalid sprite frame %d : %d ", + psp.state.sprite, psp.state.frame); + } + } + + sprframe = sprdef.spriteframes[psp.state.frame & FF_FRAMEMASK]; + + // Base frame for "angle 0" aka viewed from dead-front. + lump = sprframe.lump[0]; + // Q: where can this be set? A: at sprite loadtime. + flip = (boolean) (sprframe.flip[0] != 0); + + // calculate edges of the shape. tx is expressed in "view units". + tx = (int) (FixedMul(psp.sx, view.BOBADJUST) - view.WEAPONADJUST); + + tx -= spriteoffset[lump]; + + // So...centerxfrac is the center of the screen (pixel coords in + // fixed point). + x1 = (view.centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS; + + // off the right side + if (x1 > view.width) { + return; + } + + tx += spritewidth[lump]; + x2 + = ((view.centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS) - 1; + + // off the left side + if (x2 < 0) { + return; + } + + // store information in a vissprite ? + vis = avis; + vis.mobjflags = 0; + vis.texturemid + = ((BASEYCENTER + view.lookdir) << FRACBITS) + FRACUNIT / 2 + - (psp.sy - spritetopoffset[lump]); + vis.x1 = x1 < 0 ? 0 : x1; + vis.x2 = x2 >= view.width ? view.width - 1 : x2; + vis.scale = (pspritescale) << view.detailshift; + + if (flip) { + vis.xiscale = -pspriteiscale; + vis.startfrac = spritewidth[lump] - 1; + } else { + vis.xiscale = pspriteiscale; + vis.startfrac = 0; + } + + if (vis.x1 > x1) { + vis.startfrac += vis.xiscale * (vis.x1 - x1); + } + + vis.patch = lump; + + if ((view.player.powers[pw_invisibility] > 4 * 32) + || (view.player.powers[pw_invisibility] & 8) != 0) { + // shadow draw + vis.colormap = null; + + } else if (colormaps.fixedcolormap != null) { + // fixed color + vis.colormap = colormaps.fixedcolormap; + // vis.pcolormap=0; + } else if ((psp.state.frame & FF_FULLBRIGHT) != 0) { + // full bright + vis.colormap = colormaps.colormaps[Palettes.COLORMAP_FIXED]; + // vis.pcolormap=0; + } else { + // local light + vis.colormap = colormaps.spritelights[colormaps.maxLightScale() - 1]; + } + + // System.out.println("Weapon draw "+vis); + DrawVisSprite(vis); + } + + protected int PSpriteSY[] = {0, // staff + 5 * FRACUNIT, // goldwand + 15 * FRACUNIT, // crossbow + 15 * FRACUNIT, // blaster + 15 * FRACUNIT, // skullrod + 15 * FRACUNIT, // phoenix rod + 15 * FRACUNIT, // mace + 15 * FRACUNIT, // gauntlets + 15 * FRACUNIT // beak +}; + + /** + * R_DrawPlayerSprites This is where stuff like guns is drawn...right? + */ + protected final void DrawPlayerSprites() { + int i; + int lightnum; + pspdef_t psp; + + // get light level + lightnum + = (view.player.mo.subsector.sector.lightlevel >> colormaps.lightSegShift()) + + colormaps.extralight; + + if (lightnum < 0) { + colormaps.spritelights = colormaps.scalelight[0]; + } else if (lightnum >= colormaps.lightLevels()) { + colormaps.spritelights = colormaps.scalelight[colormaps.lightLevels() - 1]; + } else { + colormaps.spritelights = colormaps.scalelight[lightnum]; + } + + // clip to screen bounds + mfloorclip = view.screenheightarray; + p_mfloorclip = 0; + mceilingclip = view.negonearray; + p_mceilingclip = 0; + + // add all active psprites + // MAES 25/5/2011 Fixed another stupid bug that prevented + // PSP from actually being updated. This in turn uncovered + // other bugs in the way psp and state were treated, and the way + // flash states were set. It should be OK now. + for (i = 0; i < NUMPSPRITES; i++) { + psp = view.player.psprites[i]; + if (psp.state != null && psp.state.id != 0) { + DrawPSprite(psp); + } + } + } + + // MAES: Scale to vs.getScreenWidth() + protected short[] clipbot; + + protected short[] cliptop; + + /** + * R_DrawSprite + */ + protected final void DrawSprite(vissprite_t spr) { + int ds; + drawseg_t dss; + + int x; + int r1; + int r2; + int scale; // fixed + int lowscale; // fixed + int silhouette; + + for (x = spr.x1; x <= spr.x2; x++) { + clipbot[x] = cliptop[x] = -2; + } + + // Scan drawsegs from end to start for obscuring segs. + // The first drawseg that has a greater scale + // is the clip seg. + for (ds = seg_vars.ds_p - 1; ds >= 0; ds--) { + // determine if the drawseg obscures the sprite + // System.out.println("Drawseg "+ds+"of "+(ds_p-1)); + dss = seg_vars.drawsegs[ds]; + if (dss.x1 > spr.x2 + || dss.x2 < spr.x1 + || ((dss.silhouette == 0) && (dss + .nullMaskedTextureCol()))) { + // does not cover sprite + continue; + } + + r1 = dss.x1 < spr.x1 ? spr.x1 : dss.x1; + r2 = dss.x2 > spr.x2 ? spr.x2 : dss.x2; + + if (dss.scale1 > dss.scale2) { + lowscale = dss.scale2; + scale = dss.scale1; + } else { + lowscale = dss.scale1; + scale = dss.scale2; + } + + if (scale < spr.scale + || (lowscale < spr.scale && (dss.curline + .PointOnSegSide(spr.gx, spr.gy) == 0))) { + // masked mid texture? + if (!dss.nullMaskedTextureCol()) { + RenderMaskedSegRange(dss, r1, r2); + } + // seg is behind sprite + continue; + } + + // clip this piece of the sprite + silhouette = dss.silhouette; + + if (spr.gz >= dss.bsilheight) { + silhouette &= ~SIL_BOTTOM; + } + + if (spr.gzt <= dss.tsilheight) { + silhouette &= ~SIL_TOP; + } + + // BOTTOM clipping + if (silhouette == 1) { + // bottom sil + for (x = r1; x <= r2; x++) { + if (clipbot[x] == -2) { + clipbot[x] = dss.getSprBottomClip(x); + } + } + + } else if (silhouette == 2) { + // top sil + for (x = r1; x <= r2; x++) { + if (cliptop[x] == -2) { + cliptop[x] = dss.getSprTopClip(x); + } + } + } else if (silhouette == 3) { + // both + for (x = r1; x <= r2; x++) { + if (clipbot[x] == -2) { + clipbot[x] = dss.getSprBottomClip(x); + } + if (cliptop[x] == -2) { + cliptop[x] = dss.getSprTopClip(x); + } + } + } + + } + + // all clipping has been performed, so draw the sprite + // check for unclipped columns + for (x = spr.x1; x <= spr.x2; x++) { + if (clipbot[x] == -2) { + clipbot[x] = (short) view.height; + } + // ?? What's this bullshit? + if (cliptop[x] == -2) { + cliptop[x] = -1; + } + } + + mfloorclip = clipbot; + p_mfloorclip = 0; + mceilingclip = cliptop; + p_mceilingclip = 0; + DrawVisSprite(spr); + } + + /** + * R_DrawMasked Sorts and draws vissprites (room for optimization in + * sorting func.) Draws masked textures. Draws player weapons and + * overlays (psprites). Sorting function can be swapped for almost + * anything, and it will work better, in-place and be simpler to draw, + * too. + */ + @Override + public void DrawMasked() { + // vissprite_t spr; + int ds; + drawseg_t dss; + + // Well, it sorts visspite objects. + // It actually IS faster to sort with comparators, but you need to + // go into NUTS.WAD-like wads. + // numbers. The built-in sort if about as good as it gets. In fact, + // it's hardly slower + // to draw sprites without sorting them when using the built-in + // modified mergesort, while + // the original algorithm is so dreadful it actually does slow + // things down. + VIS.SortVisSprites(); + + // If you are feeling adventurous, try these ones. They *might* + // perform + // better in very extreme situations where all sprites are always on + // one side + // of your view, but I hardly see any benefits in that. They are + // both + // much better than the original anyway. + // combSort(vissprites,vissprite_p); + // shellsort(vissprites,vissprite_p); + // pQuickSprite.sort(vissprites); + // The original sort. It's incredibly bad on so many levels (uses a + // separate + // linked list for the sorted sequence, which is pointless since the + // vissprite_t + // array is gonna be changed all over in the next frame anyway, it's + // not like + // it helps preseving or anything. It does work in Java too, but I'd + // say to Keep Away. No srsly. + + /* + * SortVisSprites (); // Sprite "0" not visible? /*if (vissprite_p > + * 0) { // draw all vissprites back to front for (spr = + * vsprsortedhead.next ; spr != vsprsortedhead ; spr=spr.next) { + * DrawSprite (spr); } } + */ + // After using in-place sorts, sprites can be drawn as simply as + // that. + colfunc = colfuncs.masked; // Sprites use fully-masked capable + // function. + + final vissprite_t[] vissprites = VIS.getVisSprites(); + final int numvissprites = VIS.getNumVisSprites(); + + for (int i = 0; i < numvissprites; i++) { + DrawSprite(vissprites[i]); + } + + // render any remaining masked mid textures + for (ds = seg_vars.ds_p - 1; ds >= 0; ds--) { + dss = seg_vars.drawsegs[ds]; + if (!dss.nullMaskedTextureCol()) { + RenderMaskedSegRange(dss, dss.x1, dss.x2); + } + } + // draw the psprites on top of everything + // but does not draw on side views + // if (viewangleoffset==0) + + colfunc = colfuncs.player; + DrawPlayerSprites(); + colfunc = colfuncs.masked; + } + + /** + * R_DrawMaskedColumn Used for sprites and masked mid textures. Masked + * means: partly transparent, i.e. stored in posts/runs of opaque + * pixels. NOTE: this version accepts raw bytes, in case you know what + * you're doing. + */ + + /* protected final void DrawMaskedColumn(T column) { + int topscreen; + int bottomscreen; + int basetexturemid; // fixed_t + int topdelta; + int length; + + basetexturemid = maskedcvars.dc_texturemid; + // That's true for the whole column. + maskedcvars.dc_source = (T) column; + int pointer = 0; + + // for each post... + while ((topdelta = 0xFF & column[pointer]) != 0xFF) { + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale * topdelta; + length = 0xff & column[pointer + 1]; + bottomscreen = topscreen + spryscale * length; + + maskedcvars.dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + maskedcvars.dc_yh = (bottomscreen - 1) >> FRACBITS; + + if (maskedcvars.dc_yh >= mfloorclip[p_mfloorclip + + maskedcvars.dc_x]) + maskedcvars.dc_yh = + mfloorclip[p_mfloorclip + maskedcvars.dc_x] - 1; + + if (maskedcvars.dc_yl <= mceilingclip[p_mceilingclip + + maskedcvars.dc_x]) + maskedcvars.dc_yl = + mceilingclip[p_mceilingclip + maskedcvars.dc_x] + 1; + + // killough 3/2/98, 3/27/98: Failsafe against overflow/crash: + if (maskedcvars.dc_yl <= maskedcvars.dc_yh + && maskedcvars.dc_yh < view.height) { + // Set pointer inside column to current post's data + // Rremember, it goes {postlen}{postdelta}{pad}[data]{pad} + maskedcvars.dc_source_ofs = pointer + 3; + maskedcvars.dc_texturemid = + basetexturemid - (topdelta << FRACBITS); + + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. + maskedcvars.dc_texheight = 0; // Killough + + completeColumn(); + } + pointer += length + 4; + } + + maskedcvars.dc_texturemid = basetexturemid; + } + */ + /** + * R_DrawMaskedColumn Used for sprites and masked mid textures. Masked + * means: partly transparent, i.e. stored in posts/runs of opaque + * pixels. FIXME: while it does work with "raw columns", if the initial + * post is drawn outside of the screen the rest appear screwed up. + * SOLUTION: use the version taking raw byte[] arguments. + */ + @SuppressWarnings("unchecked") + protected final void DrawMaskedColumn(column_t column) { + int topscreen; + int bottomscreen; + int basetexturemid; // fixed_t + + basetexturemid = maskedcvars.dc_texturemid; + // That's true for the whole column. + maskedcvars.dc_source = (T) column.data; + // dc_source_ofs=0; + + // for each post... + for (int i = 0; i < column.posts; i++) { + maskedcvars.dc_source_ofs = column.postofs[i]; + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale * column.postdeltas[i]; + bottomscreen = topscreen + spryscale * column.postlen[i]; + + maskedcvars.dc_yl = (topscreen + FRACUNIT - 1) >> FRACBITS; + maskedcvars.dc_yh = (bottomscreen - 1) >> FRACBITS; + + if (maskedcvars.dc_yh >= mfloorclip[p_mfloorclip + + maskedcvars.dc_x]) { + maskedcvars.dc_yh + = mfloorclip[p_mfloorclip + maskedcvars.dc_x] - 1; + } + if (maskedcvars.dc_yl <= mceilingclip[p_mceilingclip + + maskedcvars.dc_x]) { + maskedcvars.dc_yl + = mceilingclip[p_mceilingclip + maskedcvars.dc_x] + 1; + } + + // killough 3/2/98, 3/27/98: Failsafe against overflow/crash: + if (maskedcvars.dc_yl <= maskedcvars.dc_yh + && maskedcvars.dc_yh < maskedcvars.viewheight) { + + // Set pointer inside column to current post's data + // Remember, it goes {postlen}{postdelta}{pad}[data]{pad} + maskedcvars.dc_texturemid + = basetexturemid - (column.postdeltas[i] << FRACBITS); + + // Drawn by either R_DrawColumn or (SHADOW) + // R_DrawFuzzColumn. + // MAES: when something goes bad here, it means that the + // following: + // + // fracstep = dc_iscale; + // frac = dc_texturemid + (dc_yl - centery) * fracstep; + // + // results in a negative initial frac number. + // Drawn by either R_DrawColumn + // or (SHADOW) R_DrawFuzzColumn. + // FUN FACT: this was missing and fucked my shit up. + maskedcvars.dc_texheight = 0; // Killough + + completeColumn(); + + } + } + + maskedcvars.dc_texturemid = basetexturemid; + } + + /* + * R_DrawMaskedColumn + * Used for sprites and masked mid textures. + * Masked means: partly transparent, i.e. stored + * in posts/runs of opaque pixels. + * + * NOTE: this version accepts raw bytes, in case you know what you're doing. + * NOTE: this is a legacy function. Do not reactivate unless + * REALLY needed. + * + */ + /* + protected final void DrawMaskedColumn (byte[] column) + { + int topscreen; + int bottomscreen; + int basetexturemid; // fixed_t + int topdelta; + int length; + + basetexturemid = dc_texturemid; + // That's true for the whole column. + dc_source = column; + int pointer=0; + + // for each post... + while((topdelta=0xFF&column[pointer])!=0xFF) + { + // calculate unclipped screen coordinates + // for post + topscreen = sprtopscreen + spryscale*topdelta; + length=0xff&column[pointer+1]; + bottomscreen = topscreen + spryscale*length; + + dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS; + dc_yh = (bottomscreen-1)>>FRACBITS; + + if (dc_yh >= mfloorclip[p_mfloorclip+dc_x]) + dc_yh = mfloorclip[p_mfloorclip+dc_x]-1; + + if (dc_yl <= mceilingclip[p_mceilingclip+dc_x]) + dc_yl = mceilingclip[p_mceilingclip+dc_x]+1; + + // killough 3/2/98, 3/27/98: Failsafe against overflow/crash: + if (dc_yl <= dc_yh && dc_yh < viewheight) + { + // Set pointer inside column to current post's data + // Rremember, it goes {postlen}{postdelta}{pad}[data]{pad} + dc_source_ofs = pointer+3; + dc_texturemid = basetexturemid - (topdelta< { + + T GetCachedColumn(int tex, int col); + +} \ No newline at end of file diff --git a/doom/src/rr/IGetColumn.java b/doom/src/rr/IGetColumn.java new file mode 100644 index 0000000..a931b21 --- /dev/null +++ b/doom/src/rr/IGetColumn.java @@ -0,0 +1,13 @@ +package rr; + +/** An interface used to ease the use of the GetCachedColumn by part + * of parallelized renderers. + * + * @author Maes + * + */ +public interface IGetColumn { + + T GetColumn(int tex, int col); + +} \ No newline at end of file diff --git a/doom/src/rr/ILimitResettable.java b/doom/src/rr/ILimitResettable.java new file mode 100644 index 0000000..e108d93 --- /dev/null +++ b/doom/src/rr/ILimitResettable.java @@ -0,0 +1,13 @@ +package rr; + +/** Resets limit-removing stuff back to their initial values, + * either for initialization reasons or to regain memory + * e.g. playing MAP02 after nuts.wad should free up some vissprite buffers. + * + * @author admin + * + */ +public interface ILimitResettable { + + public void resetLimits(); +} \ No newline at end of file diff --git a/doom/src/rr/IMaskedDrawer.java b/doom/src/rr/IMaskedDrawer.java new file mode 100644 index 0000000..c46f8bb --- /dev/null +++ b/doom/src/rr/IMaskedDrawer.java @@ -0,0 +1,23 @@ +package rr; + +/** Draws any masked stuff -sprites, textures, or special 3D floors */ +public interface IMaskedDrawer extends IDetailAware { + + public static final int BASEYCENTER = 100; + + /** Cache the sprite manager, if possible */ + void cacheSpriteManager(ISpriteManager SM); + + void DrawMasked(); + + void setPspriteIscale(int i); + + void setPspriteScale(int i); + + /** + * For serial masked drawer, just complete the column function. For + * parallel version, store rendering instructions and execute later on. + * HINT: you need to discern between masked and non-masked draws. + */ + void completeColumn(); +} \ No newline at end of file diff --git a/doom/src/rr/ISpriteManager.java b/doom/src/rr/ISpriteManager.java new file mode 100644 index 0000000..dfc6192 --- /dev/null +++ b/doom/src/rr/ISpriteManager.java @@ -0,0 +1,53 @@ +package rr; + +/** Interface for sprite managers. Handles loading sprites, fixing + * rotations etc. and helping retrieving spritedefs when required. + * + * @author velktron. + * + */ +public interface ISpriteManager { + + /** Default known sprite names for DOOM */ + public final static String[] doomsprnames = { + "TROO", "SHTG", "PUNG", "PISG", "PISF", "SHTF", "SHT2", "CHGG", "CHGF", "MISG", + "MISF", "SAWG", "PLSG", "PLSF", "BFGG", "BFGF", "BLUD", "PUFF", "BAL1", "BAL2", + "PLSS", "PLSE", "MISL", "BFS1", "BFE1", "BFE2", "TFOG", "IFOG", "PLAY", "POSS", + "SPOS", "VILE", "FIRE", "FATB", "FBXP", "SKEL", "MANF", "FATT", "CPOS", "SARG", + "HEAD", "BAL7", "BOSS", "BOS2", "SKUL", "SPID", "BSPI", "APLS", "APBX", "CYBR", + "PAIN", "SSWV", "KEEN", "BBRN", "BOSF", "ARM1", "ARM2", "BAR1", "BEXP", "FCAN", + "BON1", "BON2", "BKEY", "RKEY", "YKEY", "BSKU", "RSKU", "YSKU", "STIM", "MEDI", + "SOUL", "PINV", "PSTR", "PINS", "MEGA", "SUIT", "PMAP", "PVIS", "CLIP", "AMMO", + "ROCK", "BROK", "CELL", "CELP", "SHEL", "SBOX", "BPAK", "BFUG", "MGUN", "CSAW", + "LAUN", "PLAS", "SHOT", "SGN2", "COLU", "SMT2", "GOR1", "POL2", "POL5", "POL4", + "POL3", "POL1", "POL6", "GOR2", "GOR3", "GOR4", "GOR5", "SMIT", "COL1", "COL2", + "COL3", "COL4", "CAND", "CBRA", "COL6", "TRE1", "TRE2", "ELEC", "CEYE", "FSKU", + "COL5", "TBLU", "TGRN", "TRED", "SMBT", "SMGT", "SMRT", "HDB1", "HDB2", "HDB3", + "HDB4", "HDB5", "HDB6", "POB1", "POB2", "BRS1", "TLMP", "TLP2" + }; + + void InitSpriteLumps(); + + int getNumSprites(); + + int getFirstSpriteLump(); + + spritedef_t[] getSprites(); + + spritedef_t getSprite(int index); + + int[] getSpriteWidth(); + + int[] getSpriteOffset(); + + int[] getSpriteTopOffset(); + + int getSpriteWidth(int index); + + int getSpriteOffset(int index); + + int getSpriteTopOffset(int index); + + void InitSprites(String[] namelist); + +} \ No newline at end of file diff --git a/doom/src/rr/IVisSpriteManagement.java b/doom/src/rr/IVisSpriteManagement.java new file mode 100644 index 0000000..d7ac922 --- /dev/null +++ b/doom/src/rr/IVisSpriteManagement.java @@ -0,0 +1,26 @@ +package rr; + +/** A sprite manager does everything but drawing the sprites. It creates lists + * of sprites-per-sector, sorts them, and stuff like that. + * that gory visibiliy + * + * @author velkton + * + * @param + */ +public interface IVisSpriteManagement extends ILimitResettable { + + void AddSprites(sector_t sec); + + /** Cache the sprite manager, if possible */ + void cacheSpriteManager(ISpriteManager SM); + + void SortVisSprites(); + + int getNumVisSprites(); + + vissprite_t[] getVisSprites(); + + void ClearSprites(); + +} \ No newline at end of file diff --git a/doom/src/rr/MultiPatchSynthesizer.java b/doom/src/rr/MultiPatchSynthesizer.java new file mode 100644 index 0000000..61939f1 --- /dev/null +++ b/doom/src/rr/MultiPatchSynthesizer.java @@ -0,0 +1,258 @@ +package rr; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utilities to synthesize patch_t format images from multiple patches + * (with transparency). + * + * + * @author velktron + * + */ +public class MultiPatchSynthesizer { + + static class PixelRange { + + public PixelRange(int start, int end) { + this.start = start; + this.end = end; + } + + public int getLength() { + return (end - start + 1); + } + + public int start; + public int end; + } + + public static patch_t synthesizePatchFromFlat(String name, byte[] flat, int width, int height) { + + byte[] expected = new byte[width * height]; + byte[][] pixels = new byte[width][height]; + boolean[][] solid = new boolean[width][height]; + + // Copy as much data as possible. + System.arraycopy(flat, 0, expected, 0, Math.min(flat.length, expected.length)); + + for (int i = 0; i < width; i++) { + Arrays.fill(solid[i], true); + for (int j = 0; j < height; j++) { + pixels[i][j] = expected[i + j * width]; + } + } + + patch_t result = new patch_t(name, width, height, 0, 0); + column_t[] columns = new column_t[width]; + + for (int x = 0; x < width; x++) { + columns[x] = getColumnStream(pixels[x], solid[x], height); + } + + result.columns = columns; + return result; + } + + public static patch_t synthesize(String name, byte[][] pixels, boolean[][] solid, int width, int height) { + + patch_t result = new patch_t(name, width, height, 0, 0); + column_t[] columns = new column_t[width]; + + for (int x = 0; x < width; x++) { + columns[x] = getColumnStream(pixels[x], solid[x], height); + } + + result.columns = columns; + return result; + } + + public static column_t getColumnStream(byte[] pixels, boolean[] solid, int height) { + + column_t result = new column_t(); + int start = -1; + int end = -1; + + List ranges = new ArrayList<>(); + + // Scan column for continuous pixel ranges + for (int i = 0; i < height; i++) { + + // Encountered solid start. + if (solid[i] && start == -1) { + start = i; // mark start + } + + // Last solid pixel + if (solid[i] && i == height - 1 && start != -1) { + end = i; + ranges.add(new PixelRange(start, end)); + start = end = -1; // reset start/end + } + + // Start defined and ending not yet detected + if (!solid[i] && start != -1 && end == -1) { + end = i - 1; // Single-pixel runs would be e.g. 1-2 -> 1-1 + } + + if (start != -1 && end != -1) { + // Range complete. + ranges.add(new PixelRange(start, end)); + start = end = -1; // reset start/end + } + } + + // There should be at least an empty post + if (ranges.isEmpty()) { + ranges.add(new PixelRange(0, -1)); + } + + // Ideal for this use, since we don't know how big the patch is going to be a-priori + ByteArrayOutputStream file = new ByteArrayOutputStream(); + int topdelta = 0; + int n = ranges.size(); + short topdeltas[] = new short[n]; + int postofs[] = new int[n]; + short postlens[] = new short[n]; + + for (int i = 0; i < n; i++) { + PixelRange pr = ranges.get(i); + topdelta = pr.start; // cumulative top delta + + // Precomputed column data + postofs[i] = (short) file.size() + 3; // Last written post +3, pointing at first pixel of data. + topdeltas[i] = (short) topdelta; + postlens[i] = (short) (pr.getLength()); // Post lengths are at net of padding + + file.write(topdeltas[i]); + file.write(postlens[i]); + file.write(0); // padding + file.write(pixels, pr.start, pr.getLength()); // data + file.write(0); // padding + } + + file.write(0xFF); // Terminator + + result.data = file.toByteArray(); + result.postdeltas = topdeltas; + result.postlen = postlens; + result.postofs = postofs; + result.posts = ranges.size(); + + // The ranges tell us where continuous posts + return result; + } + + /* + public static patch_t synthesize(byte[][] pixels, boolean[][] solid, int width, int height, int picture_top, int picture_left){ + // Ideal for this use, since we don't know how big the patch is going to be a-priori + ByteArrayOutputStream file=new ByteArrayOutputStream(); + + int offset; + + int[] columnofs=new int[width]; + + // Patch header + file.write(width); + file.write(height); + file.write(picture_top); + file.write(picture_left); + + int column_array=0; + int x=0,y; + byte dummy_value; + while (x 0 && pixel_count > 0){ + previous_offset = current post position + + seek back in memory buffer by offset - 2 + + write pixel_count to memory buffer + + seek back to previous_offset + end block + + write pixel to memory buffer + end block + + increment y by 1 + + end block + + if operator = true or y = height then + Pixel = 0 + + write Pixel to memory buffer + + rowstart = 255 + + write rowstart to memory buffer + end block + + increment x by 1 + + end block + + seek memory buffer position to 0 + + block_size = picture_width * size of dword + + allocate block_memory, filled with 0's, with block_size + + write block_memory to file, using block_size as size + + offset = current file_position + + free block_memory + + seek to position 8 in file from start + + for loop, count = 0, break on count = number of elements in column_array + column_offset = column_array[count] + offset + + write column_offset to file + end block + + write memory buffer to file + } + } */ +} \ No newline at end of file diff --git a/doom/src/rr/PlaneDrawer.java b/doom/src/rr/PlaneDrawer.java new file mode 100644 index 0000000..33954a6 --- /dev/null +++ b/doom/src/rr/PlaneDrawer.java @@ -0,0 +1,232 @@ +package rr; + +import static data.Tables.ANGLETOFINESHIFT; +import static data.Tables.BITS32; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import doom.DoomMain; +import i.IDoomSystem; +import static m.fixed_t.FixedMul; +import rr.RendererState.IPlaneDrawer; +import rr.drawfuns.SpanVars; +import v.scale.VideoScale; +import v.tables.LightsAndColors; + +public abstract class PlaneDrawer implements IPlaneDrawer { + + private static final boolean DEBUG2 = false; + + protected final boolean RANGECHECK = false; + + // + // spanstart holds the start of a plane span + // initialized to 0 at start + // + protected int[] spanstart, spanstop; + + // + // texture mapping + // + protected V[] planezlight; // The distance lighting effect you see + /** To treat as fixed_t */ + protected int planeheight; + /** To treat as fixed_t */ + protected int[] distscale; + + /** To treat as fixed_t */ + protected int[] cacheddistance, cachedxstep, cachedystep; + + protected final ViewVars view; + + protected final SegVars seg_vars; + protected final SpanVars dsvars; + /** The visplane data. Set separately. For threads, use the same for + * everyone. + */ + protected Visplanes vpvars; + protected final LightsAndColors colormap; + protected final TextureManager TexMan; + protected final IDoomSystem I; + protected final VideoScale vs; + + protected PlaneDrawer(DoomMain DOOM, SceneRenderer R) { + this.view = R.getView(); + this.vpvars = R.getVPVars(); + this.dsvars = R.getDSVars(); + this.seg_vars = R.getSegVars(); + this.colormap = R.getColorMap(); + this.TexMan = R.getTextureManager(); + this.I = R.getDoomSystem(); + this.vs = DOOM.vs; + // Pre-scale stuff. + + spanstart = new int[vs.getScreenHeight()]; + spanstop = new int[vs.getScreenHeight()]; + distscale = new int[vs.getScreenWidth()]; + cacheddistance = new int[vs.getScreenHeight()]; + cachedxstep = new int[vs.getScreenHeight()]; + cachedystep = new int[vs.getScreenHeight()]; + + // HACK: visplanes are initialized globally. + visplane_t.setVideoScale(vs); + vpvars.initVisplanes(); + } + + /** + * R_MapPlane + * + * Called only by R_MakeSpans. + * + * This is where the actual span drawing function is called. + * + * Uses global vars: planeheight ds_source -> flat data has already been + * set. basexscale -> actual drawing angle and position is computed from + * these baseyscale viewx viewy + * + * BASIC PRIMITIVE + */ + public void MapPlane(int y, int x1, int x2) { + // MAES: angle_t + int angle; + // fixed_t + int distance; + int length; + int index; + + if (RANGECHECK) { + rangeCheck(x1, x2, y); + } + + if (planeheight != vpvars.cachedheight[y]) { + vpvars.cachedheight[y] = planeheight; + distance = cacheddistance[y] = FixedMul(planeheight, vpvars.yslope[y]); + dsvars.ds_xstep = cachedxstep[y] = FixedMul(distance, vpvars.basexscale); + dsvars.ds_ystep = cachedystep[y] = FixedMul(distance, vpvars.baseyscale); + } else { + distance = cacheddistance[y]; + dsvars.ds_xstep = cachedxstep[y]; + dsvars.ds_ystep = cachedystep[y]; + } + + length = FixedMul(distance, distscale[x1]); + angle = (int) (((view.angle + view.xtoviewangle[x1]) & BITS32) >>> ANGLETOFINESHIFT); + dsvars.ds_xfrac = view.x + FixedMul(finecosine[angle], length); + dsvars.ds_yfrac = -view.y - FixedMul(finesine[angle], length); + + if (colormap.fixedcolormap != null) { + dsvars.ds_colormap = colormap.fixedcolormap; + } else { + index = distance >>> colormap.lightZShift(); + + if (index >= colormap.maxLightZ()) { + index = colormap.maxLightZ() - 1; + } + + dsvars.ds_colormap = planezlight[index]; + } + + dsvars.ds_y = y; + dsvars.ds_x1 = x1; + dsvars.ds_x2 = x2; + + // high or low detail + dsvars.spanfunc.invoke(); + } + + protected final void rangeCheck(int x1, int x2, int y) { + if (x2 < x1 || x1 < 0 || x2 >= view.width || y > view.height) { + I.Error("%s: %d, %d at %d", this.getClass().getName(), x1, x2, y); + } + } + + /** + * R_MakeSpans + * + * Called only by DrawPlanes. If you wondered where the actual + * boundaries for the visplane flood-fill are laid out, this is it. + * + * The system of coords seems to be defining a sort of cone. + * + * + * @param x + * Horizontal position + * @param t1 + * Top-left y coord? + * @param b1 + * Bottom-left y coord? + * @param t2 + * Top-right y coord ? + * @param b2 + * Bottom-right y coord ? + * + */ + protected void MakeSpans(int x, int t1, int b1, int t2, int b2) { + + // If t1 = [sentinel value] then this part won't be executed. + while (t1 < t2 && t1 <= b1) { + this.MapPlane(t1, spanstart[t1], x - 1); + t1++; + } + while (b1 > b2 && b1 >= t1) { + this.MapPlane(b1, spanstart[b1], x - 1); + b1--; + } + + // So...if t1 for some reason is < t2, we increase t2 AND store the + // current x + // at spanstart [t2] :-S + while (t2 < t1 && t2 <= b2) { + // System.out.println("Increasing t2"); + spanstart[t2] = x; + t2++; + } + + // So...if t1 for some reason b2 > b1, we decrease b2 AND store the + // current x + // at spanstart [t2] :-S + while (b2 > b1 && b2 >= t2) { + // System.out.println("Decreasing b2"); + spanstart[b2] = x; + b2--; + } + } + + /** + * R_InitPlanes Only at game startup. + */ + @Override + public void InitPlanes() { + // Doh! + } + + protected final void rangeCheckErrors() { + if (seg_vars.ds_p > seg_vars.MAXDRAWSEGS) { + I.Error("R_DrawPlanes: drawsegs overflow (%d)", seg_vars.ds_p); + } + + if (vpvars.lastvisplane > vpvars.MAXVISPLANES) { + I.Error(" R_DrawPlanes: visplane overflow (%d)", + vpvars.lastvisplane); + } + + if (vpvars.lastopening > vpvars.MAXOPENINGS) { + I.Error("R_DrawPlanes: opening overflow (%d)", vpvars.lastopening); + } + } + + /** Default implementation which DOES NOTHING. MUST OVERRIDE */ + public void DrawPlanes() { + + } + + public void sync() { + // Nothing required if serial. + } + + /////////////// VARIOUS BORING GETTERS //////////////////// + @Override + public int[] getDistScale() { + return distscale; + } + +} \ No newline at end of file diff --git a/doom/src/rr/RendererState.java b/doom/src/rr/RendererState.java new file mode 100644 index 0000000..051c64e --- /dev/null +++ b/doom/src/rr/RendererState.java @@ -0,0 +1,3113 @@ +package rr; + +import data.Defines; +import static data.Defines.ANGLETOSKYSHIFT; +import static data.Defines.NF_SUBSECTOR; +import static data.Defines.PU_CACHE; +import static data.Defines.SIL_BOTH; +import static data.Defines.SIL_BOTTOM; +import static data.Defines.SIL_TOP; +import static data.Limits.MAXHEIGHT; +import static data.Limits.MAXSEGS; +import static data.Limits.MAXWIDTH; +import data.Tables; +import static data.Tables.ANG180; +import static data.Tables.ANG270; +import static data.Tables.ANG90; +import static data.Tables.ANGLETOFINESHIFT; +import static data.Tables.BITS32; +import static data.Tables.DBITS; +import static data.Tables.FINEANGLES; +import static data.Tables.QUARTERMARK; +import static data.Tables.SlopeDiv; +import static data.Tables.addAngles; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import static data.Tables.finetangent; +import static data.Tables.tantoangle; +import doom.CommandVariable; +import doom.DoomMain; +import doom.SourceCode.R_Draw; +import static doom.SourceCode.R_Draw.R_FillBackScreen; +import doom.player_t; +import doom.thinker_t; +import i.IDoomSystem; +import java.awt.Rectangle; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import m.IDoomMenu; +import m.MenuMisc; +import m.Settings; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import mochadoom.Engine; +import mochadoom.Loggers; +import static p.ActiveStates.P_MobjThinker; +import p.mobj_t; +import rr.drawfuns.ColFuncs; +import rr.drawfuns.ColVars; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.DoomSpanFunction; +import rr.drawfuns.SpanVars; +import static rr.line_t.ML_DONTPEGBOTTOM; +import static rr.line_t.ML_DONTPEGTOP; +import static rr.line_t.ML_MAPPED; +import utils.C2JUtils; +import static utils.GenericCopy.malloc; +import static v.DoomGraphicSystem.V_NOSCALEOFFSET; +import static v.DoomGraphicSystem.V_NOSCALEPATCH; +import static v.DoomGraphicSystem.V_NOSCALESTART; +import v.graphics.Palettes; +import v.renderers.DoomScreen; +import static v.renderers.DoomScreen.BG; +import static v.renderers.DoomScreen.FG; +import v.tables.BlurryTable; +import v.tables.LightsAndColors; +import w.IWadLoader; + +/** + * Most shared -essential- status information, methods and classes related to + * the software rendering subsystem are found here, shared between the various + * implementations of the Doom's renderer. Not the cleanest or more OO way + * possible, but still a good way to avoid duplicating common code. Some stuff + * like Texture, Flat and Sprite management are also found -or at least + * implemented temporarily- here, until a cleaner split can be made. This is a + * kind of "Jack of all trades" class, but hopefully not for long. + * + * @author velktron + */ +public abstract class RendererState implements SceneRenderer, ILimitResettable { + + private static final Logger LOGGER = Loggers.getLogger(RendererState.class.getName()); + + protected static final boolean DEBUG = false; + protected static final boolean DEBUG2 = false; + + // ///////////////////// STATUS //////////////////////// + protected DoomMain DOOM; + protected ISegDrawer MySegs; + protected IDoomMenu Menu; + protected BSP MyBSP; + protected PlaneDrawer MyPlanes; + protected IMaskedDrawer MyThings; + public IVisSpriteManagement VIS; + protected TextureManager TexMan; + public ViewVars view; + public LightsAndColors colormaps; + public SegVars seg_vars; + public Visplanes vp_vars; + + // Rendering subsystems that are detailshift-aware + protected List detailaware; + + // The only reason to query scaledviewwidth from outside the renderer, is + // this. + @Override + public boolean isFullHeight() { + return (view.height == DOOM.vs.getScreenHeight()); + } + + public boolean isFullWidth() { + return (view.scaledwidth == DOOM.vs.getScreenWidth()); + } + + @Override + public boolean isFullScreen() { + return isFullWidth() && isFullHeight(); + } + + /** + * Increment every time a check is made For some reason, this needs to be + * visible even by enemies thinking :-S + */ + protected int validcount = 1; + + /** + * Who can set this? A: The Menu. + */ + protected boolean setsizeneeded; + protected int setblocks; + protected int setdetail; + + // private BSPVars bspvars; + /** + * R_SetViewSize Do not really change anything here, because it might be in + * the middle of a refresh. The change will take effect next refresh. + * + * @param blocks + * 11 is full screen, 9 default. + * @param detail + * 0= high, 1 =low. + */ + @Override + public void SetViewSize(int blocks, int detail) { + // System.out.println("SetViewSize"); + setsizeneeded = true; + setblocks = blocks; + setdetail = detail; + + detailaware.forEach((d) -> { + d.setDetail(setdetail); + }); + } + + /** + * R_SetupFrame + */ + public void SetupFrame(player_t player) { + view.player = player; + view.x = player.mo.x; + view.y = player.mo.y; + // viewangle = addAngles(player.mo.angle , viewangleoffset); + view.angle = player.mo.angle & BITS32; + // With 32 colormaps, a bump of 1 or 2 is normal. + // With more than 32, it should be obviously higher. + + int bumplight = Math.max(colormaps.lightBits() - 5, 0); + // Be a bit more generous, otherwise the effect is not + // as evident with truecolor maps. + bumplight += (bumplight > 0) ? 1 : 0; + + colormaps.extralight = player.extralight << bumplight; + + view.z = player.viewz; + view.lookdir = player.lookdir; + int tempCentery; + + // MAES: hacks based on Heretic. Weapon movement needs to be compensated + if (setblocks == 11) { + tempCentery = (view.height / 2) + (int) (view.lookdir * DOOM.vs.getScreenMul() * setblocks) / 11; + } else { + tempCentery = (view.height / 2) + (int) (view.lookdir * DOOM.vs.getScreenMul() * setblocks) / 10; + } + + if (view.centery != tempCentery) { + view.centery = tempCentery; + view.centeryfrac = view.centery << FRACBITS; + int yslope[] = vp_vars.yslope; + for (int i = 0; i < view.height; i++) { + yslope[i] = FixedDiv( + (view.width << view.detailshift) / 2 * FRACUNIT, + Math.abs(((i - view.centery) << FRACBITS) + FRACUNIT / 2) + ); + } + + skydcvars.centery = maskedcvars.centery = dcvars.centery = view.centery; + } + + view.sin = Tables.finesine(view.angle); + view.cos = Tables.finecosine(view.angle); + + sscount = 0; + + if (player.fixedcolormap != Palettes.COLORMAP_FIXED) { + colormaps.fixedcolormap = colormaps.getFixedColormap(player); + // Offset by fixedcolomap + // pfixedcolormap =player.fixedcolormap*256; + + colormaps.walllights = colormaps.scalelightfixed; + + for (int i = 0; i < colormaps.maxLightScale(); i++) { + colormaps.scalelightfixed[i] = colormaps.fixedcolormap; + } + } else { + colormaps.fixedcolormap = null; + } + + framecount++; + validcount++; + } + + /** + * R_SetupFrame for a particular actor. + */ + public void SetupFrame(mobj_t actor) { + + // viewplayer = player; + view.x = actor.x; + view.y = actor.y; + // viewangle = addAngles(player.mo.angle , viewangleoffset); + view.angle = actor.angle & BITS32; + // extralight = actor.extralight; + + view.z = actor.z + actor.height; + + view.sin = finesine(view.angle); + view.cos = finecosine(view.angle); + + sscount = 0; + + framecount++; + validcount++; + } + + public RendererState(DoomMain DOOM) { + this.DOOM = DOOM; + + // These don't change between implementations, yet. + this.MyBSP = new BSP(); + + this.view = new ViewVars(DOOM.vs); + this.seg_vars = new SegVars(); + this.dcvars = new ColVars<>(); + this.dsvars = new SpanVars<>(); + this.maskedcvars = new ColVars<>(); + this.skydcvars = new ColVars<>(); + this.colfunclow = new ColFuncs<>(); + this.colfunchi = new ColFuncs<>(); + + this.detailaware = new ArrayList<>(); + this.colormaps = new LightsAndColors<>(DOOM); + // It's better to construct this here + @SuppressWarnings("unchecked") + final TextureManager tm = (TextureManager) new SimpleTextureManager(DOOM); + this.TexMan = tm; + + // Visplane variables + this.vp_vars = new Visplanes(DOOM.vs, view, TexMan); + + // Set rendering functions only after screen sizes + // and stuff have been set. + this.MyPlanes = new Planes(DOOM, this); + this.VIS = new VisSprites<>(this); + this.MyThings = new SimpleThings<>(DOOM.vs, this); + } + + // ////////////////////////////// THINGS //////////////////////////////// + protected final class BSP extends BSPVars { + + /** + * newend is one past the last valid seg (cliprange_t) + */ + int newend; + + cliprange_t[] solidsegs; + + BSP() { + solidsegs = malloc(cliprange_t::new, cliprange_t[]::new, MAXSEGS + 1); + } + + /** + * R_ClipSolidWallSegment Does handle solid walls, single sided LineDefs + * (middle texture) that entirely block the view VERTICALLY. Handles + * "clipranges" for a solid wall, aka where it blocks the view. + * + * @param first + * starting y coord? + * @param last + * ending y coord? + */ + private void ClipSolidWallSegment(int first, int last) { + + int next; + int start; + // int maxlast=Integer.MIN_VALUE; + + start = 0; // within solidsegs + + // Find the first cliprange that touches the range. + // Actually, the first one not completely hiding it (its last must + // be lower than first. + while (solidsegs[start].last < first - 1) { + start++; + } + + // If the post begins above the lastly found cliprange... + if (first < solidsegs[start].first) { + // ..and ends above it, too (no overlapping) + if (last < solidsegs[start].first - 1) { + // ... then the post is entirely visible (above start), + // so insert a new clippost. Calling this function + // tells the renderer that there is an obstruction. + MySegs.StoreWallRange(first, last); + + // Newend should have a value of 2 if we are at the + // beginning of a new frame. + next = newend; + newend++; + + if (next >= solidsegs.length) { + ResizeSolidSegs(); + } + while (next != start) { + // *next=*(next-1); + /* + * MAES: I think this is supposed to copy the structs + * solidsegs[next] = solidsegs[next-1].clone(); OK, so + * basically the last solidseg copies its previous, and + * so on until we reach the start. This means that at + * some point, the value of the start solidseg is + * duplicated. + */ + + solidsegs[next].copy(solidsegs[next - 1]); + + next--; + } + + // At this point, next points at start. + // Therefore, start + solidsegs[next].first = first; + solidsegs[next].last = last; + return; + } + + // There is a fragment above *start. This can occur if it a + // post does start before another, but its lower edge overlaps + // (partial, upper occlusion) + MySegs.StoreWallRange(first, solidsegs[start].first - 1); + // Now adjust the clip size. + solidsegs[start].first = first; + } + + // We can reach this only if a post starts AFTER another + // Bottom contained in start? Obviously it won't be visible. + if (last <= solidsegs[start].last) { + return; + } + + next = start; + while (last >= solidsegs[(next + 1)].first - 1) { + // There is a fragment between two posts. + MySegs.StoreWallRange(solidsegs[next].last + 1, + solidsegs[next + 1].first - 1); + next++; + + if (last <= solidsegs[next].last) { + // Bottom is contained in next. + // Adjust the clip size. + solidsegs[start].last = solidsegs[next].last; + // goto crunch; + + { // crunch code + if (next == start) { + // Post just extended past the bottom of one post. + return; + } + + while (next++ != newend) { + // Remove a post. + // MAES: this is a struct copy. + if (next >= solidsegs.length) { + ResizeSolidSegs(); + } + solidsegs[++start].copy(solidsegs[next]); + } + + newend = start + 1; + return; + } + } + } + + // There is a fragment after *next. + MySegs.StoreWallRange(solidsegs[next].last + 1, last); + // Adjust the clip size. + solidsegs[start].last = last; + + // Remove start+1 to next from the clip list, + // because start now covers their area. + { // crunch code + if (next == start) { + // Post just extended past the bottom of one post. + return; + } + + while (next++ != newend) { + // Remove a post. + // MAES: this is a struct copy. + // MAES: this can overflow, breaking e.g. MAP30 of Final + // Doom. + if (next >= solidsegs.length) { + ResizeSolidSegs(); + } + solidsegs[++start].copy(solidsegs[next]); + } + + newend = start + 1; + } + } + + void ResizeSolidSegs() { + solidsegs = C2JUtils.resize(solidsegs, solidsegs.length * 2); + } + + // + // R_ClipPassWallSegment + // Clips the given range of columns, + // but does not includes it in the clip list. + // Does handle windows, + // e.g. LineDefs with upper and lower texture. + // + private void ClipPassWallSegment(int first, int last) { + + // Find the first range that touches the range + // (adjacent pixels are touching). + int start = 0; + + while (solidsegs[start].last < first - 1) { + start++; + } + + if (first < solidsegs[start].first) { + if (last < solidsegs[start].first - 1) { + // Post is entirely visible (above start). + MySegs.StoreWallRange(first, last); + return; + } + + // There is a fragment above *start. + MySegs.StoreWallRange(first, solidsegs[start].first - 1); + } + + // Bottom contained in start? + if (last <= solidsegs[start].last) { + return; + } + + // MAES: Java absolutely can't do without a sanity check here. + // if (startptr>=MAXSEGS-2) return; + while (last >= solidsegs[start + 1].first - 1) { + // There is a fragment between two posts. + MySegs.StoreWallRange(solidsegs[start].last + 1, + solidsegs[start + 1].first - 1); + start++; + // if (startptr>=MAXSEGS-2) return; + // start=solidsegs[startptr]; + + if (last <= solidsegs[start].last) { + return; + } + } + + // There is a fragment after *next. + MySegs.StoreWallRange(solidsegs[start].last + 1, last); + } + + /** + * R_ClearClipSegs Clears the clipping segs list. The list is actually + * fixed size for efficiency reasons, so it just tells Doom to use the + * first two solidsegs, which are "neutered". It's interesting to note + * how the solidsegs begin and end just "outside" the visible borders of + * the screen. + */ + public void ClearClipSegs() { + solidsegs[0].first = -0x7fffffff; + solidsegs[0].last = -1; + solidsegs[1].first = view.width; + solidsegs[1].last = 0x7fffffff; + newend = 2; // point so solidsegs[2]; + } + + /** + * R_AddLine Called after a SubSector BSP trasversal ends up in a + * "final" subsector. Clips the given segment and adds any visible + * pieces to the line list. It also determines what kind of boundary + * (line) visplane clipping should be performed. E.g. window, final + * 1-sided line, closed door etc.) CAREFUL: was the source of much + * frustration with visplanes... + */ + private void AddLine(seg_t line) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Entered AddLine for %s", String.valueOf(line))); + } + int x1; + int x2; + long angle1; + long angle2; + long span; + long tspan; + + curline = line; + + // OPTIMIZE: quickly reject orthogonal back sides. + angle1 = view.PointToAngle(line.v1x, line.v1y); + angle2 = view.PointToAngle(line.v2x, line.v2y); + + // Clip to view edges. + // OPTIMIZE: make constant out of 2*clipangle (FIELDOFVIEW). + span = addAngles(angle1, -angle2); + + // Back side? I.e. backface culling? + if (span >= ANG180) { + return; + } + + // Global angle needed by segcalc. + MySegs.setGlobalAngle(angle1); + angle1 -= view.angle; + angle2 -= view.angle; + + angle1 &= BITS32; + angle2 &= BITS32; + + tspan = addAngles(angle1, clipangle); + + if (tspan > CLIPANGLE2) { + tspan -= CLIPANGLE2; + tspan &= BITS32; + + // Totally off the left edge? + if (tspan >= span) { + return; + } + + angle1 = clipangle; + } + tspan = addAngles(clipangle, -angle2); + + if (tspan > CLIPANGLE2) { + tspan -= CLIPANGLE2; + tspan &= BITS32; + + // Totally off the left edge? + if (tspan >= span) { + return; + } + angle2 = -clipangle; + angle2 &= BITS32; + } + + // The seg is in the view range, + // but not necessarily visible. + angle1 = ((angle1 + ANG90) & BITS32) >>> ANGLETOFINESHIFT; + angle2 = ((angle2 + ANG90) & BITS32) >>> ANGLETOFINESHIFT; + x1 = viewangletox[(int) angle1]; + x2 = viewangletox[(int) angle2]; + + // Does not cross a pixel? + if (x1 == x2) { + return; + } + + backsector = line.backsector; + + // Single sided line? + if (backsector == null) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Entering ClipSolidWallSegment SS with params %d %d", x1, (x2 - 1))); + } + ClipSolidWallSegment(x1, x2 - 1); // to clipsolid + if (DEBUG) { + LOGGER.log(Level.FINE, "Exiting ClipSolidWallSegment"); + } + return; + } + + // Closed door. + if (backsector.ceilingheight <= frontsector.floorheight + || backsector.floorheight >= frontsector.ceilingheight) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Entering ClipSolidWallSegment Closed door with params %d %d", x1, (x2 - 1))); + } + ClipSolidWallSegment(x1, x2 - 1); + // to clipsolid + return; + } + + // Window. This includes same-level floors with different textures + if (backsector.ceilingheight != frontsector.ceilingheight + || backsector.floorheight != frontsector.floorheight) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Entering ClipSolidWallSegment window with params %d %d", x1, (x2 - 1))); + } + ClipPassWallSegment(x1, x2 - 1); // to clippass + return; + } + + // Reject empty lines used for triggers + // and special events. + // Identical floor and ceiling on both sides, + // identical light levels on both sides, + // and no middle texture. + if (backsector.ceilingpic == frontsector.ceilingpic + && backsector.floorpic == frontsector.floorpic + && backsector.lightlevel == frontsector.lightlevel + && curline.sidedef.midtexture == 0) { + return; + } + + // If nothing of the previous holds, then we are + // treating the case of same-level, differently + // textured floors. ACHTUNG, this caused the "bleeding floor" + // bug, which is now fixed. + // Fucking GOTOs.... + ClipPassWallSegment(x1, x2 - 1); // to clippass + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Exiting AddLine for %s", String.valueOf(line))); + } + } + + // + // R_CheckBBox + // Checks BSP node/subtree bounding box. + // Returns true + // if some part of the bbox might be visible. + // + private final int[][] checkcoord = { + {3, 0, 2, 1}, + {3, 0, 2, 0}, + {3, 1, 2, 0}, + {0}, + {2, 0, 2, 1}, + {0, 0, 0, 0}, + {3, 1, 3, 0}, + {0}, + {2, 0, 3, 1}, + {2, 1, 3, 1}, + {2, 1, 3, 0} + }; + + /** + * @param bspcoord + * (fixed_t* as bbox) + * @return + */ + public boolean CheckBBox(int[] bspcoord) { + int boxx; + int boxy; + int boxpos; + + // fixed_t + int x1; + int y1; + int x2; + int y2; + + // angle_t + long angle1; + long angle2; + long span; + long tspan; + + cliprange_t start; + + int sx1; + int sx2; + + // Find the corners of the box + // that define the edges from current viewpoint. + if (view.x <= bspcoord[BOXLEFT]) { + boxx = 0; + } else if (view.x < bspcoord[BOXRIGHT]) { + boxx = 1; + } else { + boxx = 2; + } + + if (view.y >= bspcoord[BOXTOP]) { + boxy = 0; + } else if (view.y > bspcoord[BOXBOTTOM]) { + boxy = 1; + } else { + boxy = 2; + } + + boxpos = (boxy << 2) + boxx; + if (boxpos == 5) { + return true; + } + + x1 = bspcoord[checkcoord[boxpos][0]]; + y1 = bspcoord[checkcoord[boxpos][1]]; + x2 = bspcoord[checkcoord[boxpos][2]]; + y2 = bspcoord[checkcoord[boxpos][3]]; + + // check clip list for an open space + angle1 = view.PointToAngle(x1, y1) - view.angle; + angle2 = view.PointToAngle(x2, y2) - view.angle; + + angle1 &= BITS32; + angle2 &= BITS32; + + span = angle1 - angle2; + + span &= BITS32; + + // Sitting on a line? + if (span >= ANG180) { + return true; + } + + tspan = angle1 + clipangle; + tspan &= BITS32; + + if (tspan > CLIPANGLE2) { + tspan -= CLIPANGLE2; + tspan &= BITS32; + // Totally off the left edge? + if (tspan >= span) { + return false; + } + + angle1 = clipangle; + } + tspan = (clipangle - angle2) & BITS32; + if (tspan > CLIPANGLE2) { + tspan -= CLIPANGLE2; + tspan &= BITS32; + + // Totally off the left edge? + if (tspan >= span) { + return false; + } + + angle2 = -clipangle; + angle2 &= BITS32; + } + + // Find the first clippost + // that touches the source post + // (adjacent pixels are touching). + angle1 = ((angle1 + ANG90) & BITS32) >>> ANGLETOFINESHIFT; + angle2 = ((angle2 + ANG90) & BITS32) >>> ANGLETOFINESHIFT; + sx1 = viewangletox[(int) angle1]; + sx2 = viewangletox[(int) angle2]; + + // Does not cross a pixel. + if (sx1 == sx2) { + return false; + } + sx2--; + + int pstart = 0; + start = solidsegs[pstart]; + // FIXME: possible solidseg overflow here overflows + while (start.last < sx2 && pstart < MAXSEGS) { + start = solidsegs[pstart++]; + } + + return !(sx1 >= start.first && sx2 <= start.last); + } + + /** + * R_Subsector Determine floor/ceiling planes. Add sprites of things in + * sector. Draw one or more line segments. It also alters the visplane + * list! + * + * @param num + * Subsector from subsector_t list in Lever Loader. + */ + private void Subsector(int num) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("SubSector %d to render", num)); + } + int count; + int line; // pointer into a list of segs instead of seg_t + subsector_t sub; + + if (RANGECHECK) { + if (num >= DOOM.levelLoader.numsubsectors) { + DOOM.doomSystem.Error("R_Subsector: ss %d with numss = %d", num, + DOOM.levelLoader.numsubsectors); + } + } + + sscount++; + sub = DOOM.levelLoader.subsectors[num]; + + frontsector = sub.sector; + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Frontsector to render: %s", String.valueOf(frontsector))); + } + count = sub.numlines; + // line = LL.segs[sub.firstline]; + line = sub.firstline; + + if (DEBUG) { + LOGGER.log(Level.FINE, "Trying to find an existing FLOOR visplane..."); + } + if (frontsector.floorheight < view.z) { + vp_vars.floorplane + = vp_vars.FindPlane(frontsector.floorheight, + frontsector.floorpic, frontsector.lightlevel); + } else { + // FIXME: unclear what would happen with a null visplane used + // It's never checked explicitly for either condition, just + // called straight. + vp_vars.floorplane = -1; // in lieu of NULL + } + + // System.out.println("Trying to find an existing CEILING visplane..."); + if (frontsector.ceilingheight > view.z + || frontsector.ceilingpic == TexMan.getSkyFlatNum()) { + vp_vars.ceilingplane + = vp_vars.FindPlane(frontsector.ceilingheight, + frontsector.ceilingpic, frontsector.lightlevel); + } else { + vp_vars.ceilingplane = -1; // In lieu of NULL. Will bomb if + // actually + // used. + } + + VIS.AddSprites(frontsector); + + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Enter Addline for SubSector %d count %d", num, count)); + } + while (count-- > 0) { + AddLine(DOOM.levelLoader.segs[line]); + line++; + } + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Exit Addline for SubSector %d", num)); + } + } + + /** + * RenderBSPNode Renders all subsectors below a given node, traversing + * subtree recursively. Just call with BSP root. + */ + public void RenderBSPNode(int bspnum) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Processing BSP Node %d", bspnum)); + } + + node_t bsp; + int side; + + // Found a subsector? Then further decisions are taken, in, well, + // SubSector. + if (C2JUtils.flags(bspnum, NF_SUBSECTOR)) { + if (DEBUG) { + LOGGER.log(Level.FINE, "Subsector found."); + } + if (bspnum == -1) { + Subsector(0); + } else { + Subsector(bspnum & (~NF_SUBSECTOR)); + } + return; + } + + bsp = DOOM.levelLoader.nodes[bspnum]; + + // Decide which side the view point is on. + side = bsp.PointOnSide(view.x, view.y); + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("View side: %d", side)); + } + + // Recursively divide front space. + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Enter Front space of %d", bspnum)); + } + RenderBSPNode(bsp.children[side]); + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Return Front space of %d", bspnum)); + } + + // Possibly divide back space. + if (CheckBBox(bsp.bbox[side ^ 1].bbox)) { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Enter Back space of %d", bspnum)); + } + RenderBSPNode(bsp.children[side ^ 1]); + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("Return Back space of %d", bspnum)); + } + } + } + + } + + protected abstract class SegDrawer implements ISegDrawer { + + protected static final int HEIGHTBITS = 12; + protected static final int HEIGHTUNIT = (1 << HEIGHTBITS); + protected final Visplanes vp_vars; + protected final SegVars seg_vars; + + // Fast blanking buffers. + protected short[] BLANKFLOORCLIP; + protected short[] BLANKCEILINGCLIP; + + @Override + public short[] getBLANKFLOORCLIP() { + return BLANKFLOORCLIP; + } + + @Override + public short[] getBLANKCEILINGCLIP() { + return BLANKCEILINGCLIP; + } + + /** + * fixed_t + */ + protected int pixhigh, pixlow, pixhighstep, pixlowstep, topfrac, topstep, bottomfrac, bottomstep; + protected int worldtop, worldbottom, worldhigh, worldlow; + + /** + * True if any of the segs textures might be visible. + */ + protected boolean segtextured; + + /** + * Clip values are the solid pixel bounding the range. floorclip starts + * out vs.getScreenHeight() ceilingclip starts out -1 + */ + protected short[] floorclip, ceilingclip; + + @Override + public final short[] getFloorClip() { + return floorclip; + } + + @Override + public short[] getCeilingClip() { + return ceilingclip; + } + + /** + * False if the back side is the same plane. + */ + protected boolean markfloor, markceiling; + + protected boolean maskedtexture; + + protected int toptexture; + + protected int bottomtexture; + + protected int midtexture; + + /** + * angle_t, used after adding ANG90 in StoreWallRange + */ + protected long rw_normalangle; + + /** + * angle to line origin + */ + protected long rw_angle1; + + // + // regular wall + // + protected int rw_x; + + protected int rw_stopx; + + protected long rw_centerangle; // angle_t + + /** + * fixed_t + */ + protected int rw_offset, rw_distance, rw_scale, rw_scalestep, + rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid; + + @Override + public void resetLimits() { + drawseg_t[] tmp = new drawseg_t[seg_vars.MAXDRAWSEGS]; + System.arraycopy(seg_vars.drawsegs, 0, tmp, 0, seg_vars.MAXDRAWSEGS); + + // Now, that was quite a haircut!. + seg_vars.drawsegs = tmp; + + // System.out.println("Drawseg buffer cut back to original limit of "+MAXDRAWSEGS); + } + + @Override + public void sync() { + // Nothing required if serial. + } + + /** + * R_StoreWallRange A wall segment will be drawn between start and stop + * pixels (inclusive). This is the only place where + * markceiling/markfloor can be set. Can only be called from + * ClipSolidWallSegment and ClipPassWallSegment. + * + * @throws IOException + */ + @Override + public void StoreWallRange(int start, int stop) { + + if (DEBUG2) { + LOGGER.log(Level.FINER, String.format("Storewallrange called between %d and %d", start, stop)); + } + + int hyp; // fixed_t + int sineval; // fixed_t + int distangle; + long offsetangle; // angle_t + int vtop; // fixed_t + int lightnum; + drawseg_t seg; + + // don't overflow and crash + if (seg_vars.ds_p == seg_vars.drawsegs.length) { + seg_vars.ResizeDrawsegs(); + } + + if (RANGECHECK) { + if (start >= view.width || start > stop) { + DOOM.doomSystem.Error("Bad R_RenderWallRange: %d to %d", start, stop); + } + } + + seg = seg_vars.drawsegs[seg_vars.ds_p]; + + MyBSP.sidedef = MyBSP.curline.sidedef; + MyBSP.linedef = MyBSP.curline.linedef; + + // mark the segment as visible for auto map + MyBSP.linedef.flags |= ML_MAPPED; + + // calculate rw_distance for scale calculation + rw_normalangle = addAngles(MyBSP.curline.angle, ANG90); + + /* + * MAES: ok, this is a tricky spot. angle_t's are supposed to be + * always positive 32-bit unsigned integers, so a subtraction should + * be always positive by definition, right? WRONG: this fucking spot + * caused "blind spots" at certain angles because ONLY HERE angles + * are supposed to be treated as SIGNED and result in differences + * <180 degrees -_- The only way to coerce this behavior is to cast + * both as signed ints. + */ + offsetangle = Math.abs((int) rw_normalangle - (int) rw_angle1); + + if (offsetangle > ANG90) { + offsetangle = ANG90; + } + + // It should fit even in a signed int, by now. + distangle = (int) (ANG90 - offsetangle); + hyp = PointToDist(MyBSP.curline.v1x, MyBSP.curline.v1y); + sineval = finesine(distangle); + rw_distance = FixedMul(hyp, sineval); + + seg.x1 = rw_x = start; + seg.x2 = stop; + seg.curline = MyBSP.curline; + /* + * This is the only place it's ever explicitly assigned. Therefore + * it always starts at stop+1. + */ + rw_stopx = stop + 1; + + // calculate scale at both ends and step + // this is the ONLY place where rw_scale is set. + seg.scale1 + = rw_scale + = ScaleFromGlobalAngle((view.angle + view.xtoviewangle[start])); + + if (stop > start) { + seg.scale2 + = ScaleFromGlobalAngle(view.angle + view.xtoviewangle[stop]); + seg.scalestep + = rw_scalestep = (seg.scale2 - rw_scale) / (stop - start); + } else { + // UNUSED: try to fix the stretched line bug + /* + * #if 0 if (rw_distance < FRACUNIT/2) { fixed_t trx,try; + * fixed_t gxt,gyt; trx = curline.v1.x - viewx; try = + * curline.v1.y - viewy; gxt = FixedMul(trx,viewcos); gyt = + * -FixedMul(try,viewsin); seg.scale1 = FixedDiv(projection, + * gxt-gyt)< MyBSP.backsector.floorheight) { + seg.silhouette = SIL_BOTTOM; + seg.bsilheight = MyBSP.frontsector.floorheight; + } else if (MyBSP.backsector.floorheight > view.z) { + seg.silhouette = SIL_BOTTOM; + seg.bsilheight = Integer.MAX_VALUE; + // seg.sprbottomclip = negonearray; + } + + if (MyBSP.frontsector.ceilingheight < MyBSP.backsector.ceilingheight) { + seg.silhouette |= SIL_TOP; + seg.tsilheight = MyBSP.frontsector.ceilingheight; + } else if (MyBSP.backsector.ceilingheight < view.z) { + seg.silhouette |= SIL_TOP; + seg.tsilheight = Integer.MIN_VALUE; + // seg.sprtopclip = screenheightarray; + } + + if (MyBSP.backsector.ceilingheight <= MyBSP.frontsector.floorheight) { + seg.setSprBottomClip(view.negonearray, 0); + seg.bsilheight = Integer.MAX_VALUE; + seg.silhouette |= SIL_BOTTOM; + } + + if (MyBSP.backsector.floorheight >= MyBSP.frontsector.ceilingheight) { + seg.setSprTopClip(view.screenheightarray, 0); + seg.tsilheight = Integer.MIN_VALUE; + seg.silhouette |= SIL_TOP; + } + + worldhigh = MyBSP.backsector.ceilingheight - view.z; + worldlow = MyBSP.backsector.floorheight - view.z; + + // hack to allow height changes in outdoor areas + if (MyBSP.frontsector.ceilingpic == TexMan.getSkyFlatNum() + && MyBSP.backsector.ceilingpic == TexMan + .getSkyFlatNum()) { + worldtop = worldhigh; + } + + markfloor = worldlow != worldbottom + || MyBSP.backsector.floorpic != MyBSP.frontsector.floorpic + || MyBSP.backsector.lightlevel != MyBSP.frontsector.lightlevel; // same plane on both sides + markceiling = worldhigh != worldtop + || MyBSP.backsector.ceilingpic != MyBSP.frontsector.ceilingpic + || MyBSP.backsector.lightlevel != MyBSP.frontsector.lightlevel; // same plane on both sides + + if (MyBSP.backsector.ceilingheight <= MyBSP.frontsector.floorheight + || MyBSP.backsector.floorheight >= MyBSP.frontsector.ceilingheight) { + // closed door + markceiling = markfloor = true; + } + + if (worldhigh < worldtop) { + // top texture + toptexture + = TexMan.getTextureTranslation(MyBSP.sidedef.toptexture); + if ((MyBSP.linedef.flags & ML_DONTPEGTOP) != 0) { + // top of texture at top + rw_toptexturemid = worldtop; + } else { + vtop + = MyBSP.backsector.ceilingheight + + TexMan.getTextureheight(MyBSP.sidedef.toptexture); + + // bottom of texture + rw_toptexturemid = vtop - view.z; + } + } + if (worldlow > worldbottom) { + // bottom texture + bottomtexture + = TexMan.getTextureTranslation(MyBSP.sidedef.bottomtexture); + + if ((MyBSP.linedef.flags & ML_DONTPEGBOTTOM) != 0) { + // bottom of texture at bottom + // top of texture at top + rw_bottomtexturemid = worldtop; + } else { + // top of texture at top + rw_bottomtexturemid = worldlow; + } + } + rw_toptexturemid += MyBSP.sidedef.rowoffset; + rw_bottomtexturemid += MyBSP.sidedef.rowoffset; + + // allocate space for masked texture tables + if (MyBSP.sidedef.midtexture != 0) { + // masked midtexture + maskedtexture = true; + seg_vars.maskedtexturecol = vp_vars.openings; + seg_vars.pmaskedtexturecol = vp_vars.lastopening - rw_x; + seg.setMaskedTextureCol(seg_vars.maskedtexturecol, + seg_vars.pmaskedtexturecol); + vp_vars.lastopening += rw_stopx - rw_x; + } + } + + // calculate rw_offset (only needed for textured lines) + segtextured + = (((midtexture | toptexture | bottomtexture) != 0) | maskedtexture); + + if (segtextured) { + offsetangle = addAngles(rw_normalangle, -rw_angle1); + + // Another "tricky spot": negative of an unsigned number? + if (offsetangle > ANG180) { + offsetangle = (-(int) offsetangle) & BITS32; + } + + if (offsetangle > ANG90) { + offsetangle = ANG90; + } + + sineval = finesine(offsetangle); + rw_offset = FixedMul(hyp, sineval); + + // Another bug: we CAN'T assume that the result won't wrap + // around. + // If that assumption is made, then texture alignment issues + // appear + if (((rw_normalangle - rw_angle1) & BITS32) < ANG180) { + rw_offset = -rw_offset; + } + + rw_offset += MyBSP.sidedef.textureoffset + MyBSP.curline.offset; + // This is OK, however: we can add as much shit as we want, + // as long as we trim it to the 32 LSB. Proof as to why + // this is always true is left as an exercise to the reader. + rw_centerangle = (ANG90 + view.angle - rw_normalangle) & BITS32; + + // calculate light table + // use different light tables + // for horizontal / vertical / diagonal + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + if (colormaps.fixedcolormap == null) { + lightnum + = (MyBSP.frontsector.lightlevel >> colormaps.lightSegShift()) + + colormaps.extralight; + + if (MyBSP.curline.v1y == MyBSP.curline.v2y) { + lightnum--; + } else if (MyBSP.curline.v1x == MyBSP.curline.v2x) { + lightnum++; + } + + if (lightnum < 0) { + colormaps.walllights = colormaps.scalelight[0]; + } else if (lightnum >= colormaps.lightLevels()) { + colormaps.walllights + = colormaps.scalelight[colormaps.lightLevels() - 1]; + } else { + colormaps.walllights = colormaps.scalelight[lightnum]; + } + } + } + + // if a floor / ceiling plane is on the wrong side + // of the view plane, it is definitely invisible + // and doesn't need to be marked. + if (MyBSP.frontsector.floorheight >= view.z) { + // above view plane + markfloor = false; + } + + if (MyBSP.frontsector.ceilingheight <= view.z + && MyBSP.frontsector.ceilingpic != TexMan.getSkyFlatNum()) { + // below view plane + markceiling = false; + } + + // calculate incremental stepping values for texture edges + worldtop >>= 4; + worldbottom >>= 4; + + topstep = -FixedMul(rw_scalestep, worldtop); + topfrac = (view.centeryfrac >> 4) - FixedMul(worldtop, rw_scale); + + bottomstep = -FixedMul(rw_scalestep, worldbottom); + bottomfrac + = (view.centeryfrac >> 4) - FixedMul(worldbottom, rw_scale); + + if (MyBSP.backsector != null) { + worldhigh >>= 4; + worldlow >>= 4; + + if (worldhigh < worldtop) { + pixhigh + = (view.centeryfrac >> 4) - FixedMul(worldhigh, rw_scale); + pixhighstep = -FixedMul(rw_scalestep, worldhigh); + } + + if (worldlow > worldbottom) { + pixlow + = (view.centeryfrac >> 4) - FixedMul(worldlow, rw_scale); + pixlowstep = -FixedMul(rw_scalestep, worldlow); + } + } + + // render it + if (markceiling) { + // System.out.println("Markceiling"); + vp_vars.ceilingplane + = vp_vars.CheckPlane(vp_vars.ceilingplane, rw_x, rw_stopx - 1); + } + + if (markfloor) { + // System.out.println("Markfloor"); + vp_vars.floorplane + = vp_vars.CheckPlane(vp_vars.floorplane, rw_x, rw_stopx - 1); + } + + RenderSegLoop(); + + // After rendering is actually performed, clipping is set. + // save sprite clipping info ... no top clipping? + if ((C2JUtils.flags(seg.silhouette, SIL_TOP) || maskedtexture) + && seg.nullSprTopClip()) { + + // memcpy (lastopening, ceilingclip+start, 2*(rw_stopx-start)); + System.arraycopy(ceilingclip, start, vp_vars.openings, + vp_vars.lastopening, rw_stopx - start); + + seg.setSprTopClip(vp_vars.openings, vp_vars.lastopening - start); + // seg.setSprTopClipPointer(); + vp_vars.lastopening += rw_stopx - start; + } + // no floor clipping? + if ((C2JUtils.flags(seg.silhouette, SIL_BOTTOM) || maskedtexture) + && seg.nullSprBottomClip()) { + // memcpy (lastopening, floorclip+start, 2*(rw_stopx-start)); + System.arraycopy(floorclip, start, vp_vars.openings, + vp_vars.lastopening, rw_stopx - start); + seg.setSprBottomClip(vp_vars.openings, vp_vars.lastopening + - start); + vp_vars.lastopening += rw_stopx - start; + } + + if (maskedtexture && C2JUtils.flags(seg.silhouette, SIL_TOP)) { + seg.silhouette |= SIL_TOP; + seg.tsilheight = Integer.MIN_VALUE; + } + if (maskedtexture && (seg.silhouette & SIL_BOTTOM) == 0) { + seg.silhouette |= SIL_BOTTOM; + seg.bsilheight = Integer.MAX_VALUE; + } + seg_vars.ds_p++; + } + + /** + * R_RenderSegLoop Draws zero, one, or two textures (and possibly a + * masked texture) for walls. Can draw or mark the starting pixel of + * floor and ceiling textures. Also sets the actual sprite clipping info + * (where sprites should be cut) Since rw_x ranges are non-overlapping, + * rendering all walls means completing the clipping list as well. The + * only difference between the parallel and the non-parallel version is + * that the parallel doesn't draw immediately but rather, generates + * RWIs. This can surely be unified to avoid replicating code. CALLED: + * CORE LOOPING ROUTINE. + */ + protected void RenderSegLoop() { + int angle; // angle_t + int index; + int yl; // low + int yh; // hight + int mid; + int texturecolumn = 0; // fixed_t + int top; + int bottom; + + for (; rw_x < rw_stopx; rw_x++) { + // mark floor / ceiling areas + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; + + // no space above wall? + if (yl < ceilingclip[rw_x] + 1) { + yl = ceilingclip[rw_x] + 1; + } + + if (markceiling) { + top = ceilingclip[rw_x] + 1; + bottom = yl - 1; + + if (bottom >= floorclip[rw_x]) { + bottom = floorclip[rw_x] - 1; + } + + if (top <= bottom) { + vp_vars.visplanes[vp_vars.ceilingplane].setTop(rw_x, + (char) top); + vp_vars.visplanes[vp_vars.ceilingplane].setBottom(rw_x, + (char) bottom); + } + } + + yh = bottomfrac >> HEIGHTBITS; + + if (yh >= floorclip[rw_x]) { + yh = floorclip[rw_x] - 1; + } + + // A particular seg has been identified as a floor marker. + if (markfloor) { + top = yh + 1; + bottom = floorclip[rw_x] - 1; + if (top <= ceilingclip[rw_x]) { + top = ceilingclip[rw_x] + 1; + } + if (top <= bottom) { + vp_vars.visplanes[vp_vars.floorplane].setTop(rw_x, + (char) top); + vp_vars.visplanes[vp_vars.floorplane].setBottom(rw_x, + (char) bottom); + } + } + + // texturecolumn and lighting are independent of wall tiers + if (segtextured) { + // calculate texture offset + + // CAREFUL: a VERY anomalous point in the code. Their sum is + // supposed + // to give an angle not exceeding 45 degrees (or an index of + // 0x0FFF after + // shifting). If added with pure unsigned rules, this + // doesn't hold anymore, + // not even if accounting for overflow. + angle + = Tables.toBAMIndex(rw_centerangle + + (int) view.xtoviewangle[rw_x]); + + // FIXME: We are accessing finetangent here, the code seems + // pretty confident in that angle won't exceed 4K no matter + // what. + // But xtoviewangle alone can yield 8K when shifted. + // This usually only overflows if we idclip and look at + // certain directions (probably angles get fucked up), + // however it seems rare + // enough to just "swallow" the exception. You can eliminate + // it by anding + // with 0x1FFF if you're so inclined. + // FIXED by allowing overflow. See Tables for details. + texturecolumn + = rw_offset - FixedMul(finetangent[angle], rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = rw_scale >> colormaps.lightScaleShift(); + + if (index >= colormaps.maxLightScale()) { + index = colormaps.maxLightScale() - 1; + } + + dcvars.dc_colormap = colormaps.walllights[index]; + dcvars.dc_x = rw_x; + dcvars.dc_iscale = (int) (0xffffffffL / rw_scale); + } + + // draw the wall tiers + if (midtexture != 0) { + // single sided line + dcvars.dc_yl = yl; + dcvars.dc_yh = yh; + dcvars.dc_texheight + = TexMan.getTextureheight(midtexture) >> FRACBITS; // killough + dcvars.dc_texturemid = rw_midtexturemid; + dcvars.dc_source_ofs = 0; + dcvars.dc_source + = TexMan.GetCachedColumn(midtexture, texturecolumn); + CompleteColumn(); + ceilingclip[rw_x] = (short) view.height; + floorclip[rw_x] = -1; + } else { + // two sided line + if (toptexture != 0) { + // top wall + mid = pixhigh >> HEIGHTBITS; + pixhigh += pixhighstep; + + if (mid >= floorclip[rw_x]) { + mid = floorclip[rw_x] - 1; + } + + if (mid >= yl) { + dcvars.dc_yl = yl; + dcvars.dc_yh = mid; + dcvars.dc_texturemid = rw_toptexturemid; + dcvars.dc_texheight = TexMan.getTextureheight(toptexture) >> FRACBITS; + dcvars.dc_source = TexMan.GetCachedColumn(toptexture, texturecolumn); + dcvars.dc_source_ofs = 0; + if (dcvars.dc_colormap == null) { + LOGGER.log(Level.FINE, "Two-sided"); + } + CompleteColumn(); + ceilingclip[rw_x] = (short) mid; + } else { + ceilingclip[rw_x] = (short) (yl - 1); + } + } else { + // no top wall + if (markceiling) { + ceilingclip[rw_x] = (short) (yl - 1); + } + } + + if (bottomtexture != 0) { + // bottom wall + mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x]) { + mid = ceilingclip[rw_x] + 1; + } + + if (mid <= yh) { + dcvars.dc_yl = mid; + dcvars.dc_yh = yh; + dcvars.dc_texturemid = rw_bottomtexturemid; + dcvars.dc_texheight = TexMan.getTextureheight(bottomtexture) >> FRACBITS; + dcvars.dc_source = TexMan.GetCachedColumn(bottomtexture, texturecolumn); + dcvars.dc_source_ofs = 0; + CompleteColumn(); + + floorclip[rw_x] = (short) mid; + } else { + floorclip[rw_x] = (short) (yh + 1); + } + } else { + // no bottom wall + if (markfloor) { + floorclip[rw_x] = (short) (yh + 1); + } + } + + if (maskedtexture) { + // save texturecol + // for backdrawing of masked mid texture + seg_vars.maskedtexturecol[seg_vars.pmaskedtexturecol + rw_x] = (short) texturecolumn; + } + } + + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; + } + } + + @Override + public void ClearClips() { + System.arraycopy(BLANKFLOORCLIP, 0, floorclip, 0, view.width); + System.arraycopy(BLANKCEILINGCLIP, 0, ceilingclip, 0, view.width); + } + + /** + * Called from RenderSegLoop. This should either invoke the column + * function, or store a wall rendering instruction in the parallel + * version. It's the only difference between the parallel and serial + * renderer, BTW. So override and implement accordingly. + */ + protected abstract void CompleteColumn(); + + @Override + public void ExecuteSetViewSize(int viewwidth) { + for (int i = 0; i < viewwidth; i++) { + BLANKFLOORCLIP[i] = (short) view.height; + BLANKCEILINGCLIP[i] = -1; + } + } + + @Override + public void CompleteRendering() { + // Nothing to do for serial. + } + + protected column_t col; + + public SegDrawer(SceneRenderer R) { + this.vp_vars = R.getVPVars(); + this.seg_vars = R.getSegVars(); + col = new column_t(); + seg_vars.drawsegs = malloc(drawseg_t::new, drawseg_t[]::new, seg_vars.MAXDRAWSEGS); + this.floorclip = new short[DOOM.vs.getScreenWidth()]; + this.ceilingclip = new short[DOOM.vs.getScreenWidth()]; + BLANKFLOORCLIP = new short[DOOM.vs.getScreenWidth()]; + BLANKCEILINGCLIP = new short[DOOM.vs.getScreenWidth()]; + } + + /** + * R_ScaleFromGlobalAngle Returns the texture mapping scale for the + * current line (horizontal span) at the given angle. rw_distance must + * be calculated first. + */ + protected int ScaleFromGlobalAngle(long visangle) { + int scale; // fixed_t + long anglea; + long angleb; + int sinea; + int sineb; + int num; // fixed_t + int den; + + // UNUSED + /* + * { fixed_t dist; fixed_t z; fixed_t sinv; fixed_t cosv; sinv = + * finesine[(visangle-rw_normalangle)>>ANGLETOFINESHIFT]; dist = + * FixedDiv (rw_distance, sinv); cosv = + * finecosine[(viewangle-visangle)>>ANGLETOFINESHIFT]; z = + * abs(FixedMul (dist, cosv)); scale = FixedDiv(projection, z); + * return scale; } + */ + anglea = (ANG90 + visangle - view.angle) & BITS32; + angleb = (ANG90 + visangle - rw_normalangle) & BITS32; + + // both sines are allways positive + sinea = finesine(anglea); + sineb = finesine(angleb); + num = FixedMul(view.projection, sineb) << view.detailshift; + den = FixedMul(rw_distance, sinea); + + if (den > num >> 16) { + scale = FixedDiv(num, den); + + if (scale > 64 * FRACUNIT) { + scale = 64 * FRACUNIT; + } else if (scale < 256) { + scale = 256; + } + } else { + scale = 64 * FRACUNIT; + } + + return scale; + } + + @Override + public void setGlobalAngle(long angle) { + this.rw_angle1 = angle; + } + } + + protected interface IPlaneDrawer { + + void InitPlanes(); + + void MapPlane(int y, int x1, int x2); + + void DrawPlanes(); + + int[] getDistScale(); + + /** + * Sync up in case there's concurrent planes/walls rendering + */ + void sync(); + } + + protected interface ISegDrawer extends ILimitResettable { + + void ClearClips(); + + short[] getBLANKCEILINGCLIP(); + + short[] getBLANKFLOORCLIP(); + + short[] getFloorClip(); + + short[] getCeilingClip(); + + void ExecuteSetViewSize(int viewwidth); + + void setGlobalAngle(long angle1); + + void StoreWallRange(int first, int last); + + /** + * If there is anything to do beyond the BPS traversal, + * e.g. parallel rendering + */ + void CompleteRendering(); + + /** + * Sync up in case there's concurrent planes/walls rendering + */ + void sync(); + } + + protected class Planes extends PlaneDrawer { + + Planes(DoomMain DOOM, RendererState R) { + super(DOOM, R); + } + + /** + * R_DrawPlanes At the end of each frame. This also means that visplanes + * must have been set BEFORE we called this function. Therefore, look + * for errors behind. + * + * @throws IOException + */ + @Override + public void DrawPlanes() { + if (DEBUG) { + LOGGER.log(Level.FINE, String.format("DrawPlanes: %d", vp_vars.lastvisplane)); + } + visplane_t pln; // visplane_t + int light; + int x; + int stop; + int angle; + + if (RANGECHECK) { + rangeCheckErrors(); + } + + for (int pl = 0; pl < vp_vars.lastvisplane; pl++) { + pln = vp_vars.visplanes[pl]; + if (DEBUG2) { + LOGGER.log(Level.FINER, String.valueOf(pln)); + } + + if (pln.minx > pln.maxx) { + continue; + } + // sky flat + if (pln.picnum == TexMan.getSkyFlatNum()) { + // Cache skytexture stuff here. They aren't going to change + // while + // being drawn, after all, are they? + int skytexture = TexMan.getSkyTexture(); + skydcvars.dc_texheight + = TexMan.getTextureheight(skytexture) >> FRACBITS; + skydcvars.dc_iscale + = vpvars.getSkyScale() >> view.detailshift; + + /** + * Sky is allways drawn full bright, i.e. colormaps[0] is + * used. Because of this hack, sky is not affected by INVUL + * inverse mapping. + * Settings.fixskypalette handles the fix + */ + if (DOOM.CM.equals(Settings.fix_sky_palette, Boolean.TRUE) && colormap.fixedcolormap != null) { + skydcvars.dc_colormap = colormap.fixedcolormap; + } else { + skydcvars.dc_colormap = colormap.colormaps[Palettes.COLORMAP_FIXED]; + } + skydcvars.dc_texturemid = TexMan.getSkyTextureMid(); + for (x = pln.minx; x <= pln.maxx; x++) { + + skydcvars.dc_yl = pln.getTop(x); + skydcvars.dc_yh = pln.getBottom(x); + + if (skydcvars.dc_yl <= skydcvars.dc_yh) { + angle + = (int) (addAngles(view.angle, view.xtoviewangle[x]) >>> ANGLETOSKYSHIFT); + skydcvars.dc_x = x; + // Optimized: texheight is going to be the same + // during normal skies drawing...right? + skydcvars.dc_source + = TexMan.GetCachedColumn(skytexture, angle); + colfunc.sky.invoke(); + } + } + continue; + } + + // regular flat + dsvars.ds_source = TexMan.getSafeFlat(pln.picnum); + + planeheight = Math.abs(pln.height - view.z); + light = (pln.lightlevel >> colormap.lightSegShift()) + colormap.extralight; + + if (light >= colormap.lightLevels()) { + light = colormap.lightLevels() - 1; + } + + if (light < 0) { + light = 0; + } + + planezlight = colormap.zlight[light]; + + // We set those values at the border of a plane's top to a + // "sentinel" value...ok. + pln.setTop(pln.maxx + 1, visplane_t.SENTINEL); + pln.setTop(pln.minx - 1, visplane_t.SENTINEL); + + stop = pln.maxx + 1; + + for (x = pln.minx; x <= stop; x++) { + MakeSpans(x, pln.getTop(x - 1), pln.getBottom(x - 1), pln.getTop(x), pln.getBottom(x)); + } + + // Z_ChangeTag (ds_source, PU_CACHE); + } + } + + } // End Plane class + + // /////////////////////// LIGHTS, POINTERS, COLORMAPS ETC. //////////////// + // /// FROM R_DATA, R_MAIN , R_DRAW ////////// + /** + * OK< this is supposed to "peg" into screen buffer 0. It will work AS LONG + * AS SOMEONE FUCKING ACTUALLY SETS IT !!!! + */ + protected V screen; + + protected static final boolean RANGECHECK = false; + + /** + * These are actually offsets inside screen 0 (or any screen). Therefore + * anything using them should "draw" inside screen 0 + */ + protected int[] ylookup = new int[MAXHEIGHT]; + + /** + * Columns offset to set where?! + */ + protected int[] columnofs = new int[MAXWIDTH]; + + /** + * General purpose. Used for solid walls and as an intermediary for + * threading + */ + protected ColVars dcvars; + + /** + * Used for spans + */ + protected SpanVars dsvars; + + // Used for sky drawer, to avoid clashing with shared dcvars + protected ColVars skydcvars; + + /** + * Masked drawing functions get "pegged" to this set of dcvars, passed upon + * initialization. However, multi-threaded vars are better off carrying each + * their own ones. + */ + protected ColVars maskedcvars; + + /** + * e6y: wide-res Borrowed from PrBoom+; + */ + + /* + * protected int wide_centerx, wide_ratio, wide_offsetx, wide_offset2x, + * wide_offsety, wide_offset2y; protected final base_ratio_t[] + * BaseRatioSizes = { new base_ratio_t(960, 600, 0, 48, 1.333333f), // 4:3 + * new base_ratio_t(1280, 450, 0, 48 * 3 / 4, 1.777777f), // 16:9 new + * base_ratio_t(1152, 500, 0, 48 * 5 / 6, 1.6f), // 16:10 new + * base_ratio_t(960, 600, 0, 48, 1.333333f), new base_ratio_t(960, 640, + * (int) (6.5 * FRACUNIT), 48 * 15 / 16, 1.25f) // 5:4 }; + */ + /** + * just for profiling purposes + */ + protected int framecount; + protected int sscount; + protected int linecount; + protected int loopcount; + + // + // precalculated math tables + // + protected long clipangle; + + // Set to 2*clipangle later. + protected long CLIPANGLE2; + + // The viewangletox[viewangle + FINEANGLES/4] lookup + // maps the visible view angles to screen X coordinates, + // flattening the arc to a flat projection plane. + // There will be many angles mapped to the same X. + protected final int[] viewangletox = new int[FINEANGLES / 2]; + + /** + * The xtoviewangle[] table maps a screen pixel to the lowest viewangle that + * maps back to x ranges from clipangle to -clipangle. + * + * @see view.xtoviewangle + */ + //protected long[] view.xtoviewangle;// MAES: to resize + // UNUSED. + // The finetangentgent[angle+FINEANGLES/4] table + // holds the fixed_t tangent values for view angles, + // ranging from MININT to 0 to MAXINT. + // fixed_t finetangent[FINEANGLES/2]; + // fixed_t finesine[5*FINEANGLES/4]; + // MAES: uh oh. So now all these ints must become finesines? fuck that. + // Also wtf @ this hack....this points to approx 1/4th of the finesine + // table, but what happens if I read past it? + // int[] finecosine = finesine[FINEANGLES/4]; + + /* + * MAES: what's going on with light tables here. OK...so these should be + * "unsigned bytes", since, after all, they'll be used as pointers inside an + * array to finally pick a color, so they should be expanded to shorts. + */ + // //////////// SOME UTILITY METHODS ///////////// + /** + * Assigns a point of view before calling PointToAngle CAREFUL: this isn't a + * pure function, as it alters the renderer's state! + */ + @Override + public final long PointToAngle2(int x1, int y1, int x2, int y2) { + // Careful with assignments... + view.x = x1; + view.y = y1; + + return view.PointToAngle(x2, y2); + } + + // + // R_InitPointToAngle + // + /* + * protected final void InitPointToAngle () { // UNUSED - now getting from + * tables.c if (false){ int i; long t; float f; // // slope (tangent) to + * angle lookup // for (i=0 ; i<=SLOPERANGE ; i++) { f = (float) Math.atan( + * (double)(i/SLOPERANGE )/(3.141592657*2)); t = (long) (0xffffffffL*f); + * tantoangle[i] = (int) t; } } } + */ + /** + * Public, static, stateless version of PointToAngle2. Call this one when + * "renderless" use of PointToAngle2 is required. + */ + public static long PointToAngle(int viewx, int viewy, int x, int y) { + // MAES: note how we don't use &BITS32 here. That is because + // we know that the maximum possible value of tantoangle is angle + // This way, we are actually working with vectors emanating + // from our current position. + x -= viewx; + y -= viewy; + + if ((x == 0) && (y == 0)) { + return 0; + } + + if (x >= 0) { + // x >=0 + if (y >= 0) { + // y>= 0 + + if (x > y) { + // octant 0 + return tantoangle[SlopeDiv(y, x)]; + } else { + // octant 1 + return (ANG90 - 1 - tantoangle[SlopeDiv(x, y)]); + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 8 + return (-tantoangle[SlopeDiv(y, x)]); + } else { + // octant 7 + return (ANG270 + tantoangle[SlopeDiv(x, y)]); + } + } + } else { + // x<0 + x = -x; + + if (y >= 0) { + // y>= 0 + if (x > y) { + // octant 3 + return (ANG180 - 1 - tantoangle[SlopeDiv(y, x)]); + } else { + // octant 2 + return (ANG90 + tantoangle[SlopeDiv(x, y)]); + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 4 + return (ANG180 + tantoangle[SlopeDiv(y, x)]); + } else { + // octant 5 + return (ANG270 - 1 - tantoangle[SlopeDiv(x, y)]); + } + } + } + // This is actually unreachable. + // return 0; + } + + // + // R_InitTables + // + protected void InitTables() { + // UNUSED: now getting from tables.c + /* + * int i; float a; float fv; int t; // viewangle tangent table for (i=0 + * ; i dx) { + temp = dx; + dx = dy; + dy = temp; + } + + // If one or both of the distances are *exactly* zero at this point, + // then this means that the wall is in your face anyway, plus we want to + // avoid a division by zero. So you get zero. + if (dx == 0) { + return 0; + } + + /* + * If dx is zero, this is going to bomb. Fixeddiv will return MAXINT aka + * 7FFFFFFF, >> DBITS will make it 3FFFFFF, which is more than enough to + * break tantoangle[]. In the original C code, this probably didn't + * matter: there would probably be garbage orientations thrown all + * around. However this is unacceptable in Java. OK, so the safeguard + * above prevents that. Still... this method is only called once per + * visible wall per frame, so one check more or less at this point won't + * change much. It's better to be safe than sorry. + */ + // This effectively limits the angle to + // angle = Math.max(FixedDiv(dy, dx), 2048) >> DBITS; + angle = (FixedDiv(dy, dx) & 0x1FFFF) >> DBITS; + + // Since the division will be 0xFFFF at most, DBITS will restrict + // the maximum angle index to 7FF, about 45, so adding ANG90 with + // no other safeguards is OK. + angle = (int) ((tantoangle[angle] + ANG90) >> ANGLETOFINESHIFT); + + // use as cosine + dist = FixedDiv(dx, finesine[angle]); + + return dist; + } + + // //////////// COMMON RENDERING GLOBALS //////////////// + // //////////////// COLUMN AND SPAN FUNCTIONS ////////////// + protected ColFuncs colfunc; + + // Keep two sets of functions. + protected ColFuncs colfunchi; + + protected ColFuncs colfunclow; + + protected void setHiColFuns() { + colfunchi.main = colfunchi.base = DrawColumn; + colfunchi.masked = DrawColumnMasked; + colfunchi.fuzz = DrawFuzzColumn; + colfunchi.trans = DrawTranslatedColumn; + colfunchi.glass = DrawTLColumn; + colfunchi.player = DrawColumnPlayer; + colfunchi.sky = DrawColumnSkies; + } + + protected void setLowColFuns() { + colfunclow.main = colfunclow.base = DrawColumnLow; + colfunclow.masked = DrawColumnMaskedLow; + colfunclow.fuzz = DrawFuzzColumnLow; + colfunclow.trans = DrawTranslatedColumnLow; + colfunclow.glass = DrawTLColumn; + colfunclow.player = DrawColumnMaskedLow; + colfunclow.sky = DrawColumnSkiesLow; + } + + @Override + public ColFuncs getColFuncsHi() { + return this.colfunchi; + } + + @Override + public ColFuncs getColFuncsLow() { + return this.colfunclow; + } + + @Override + public ColVars getMaskedDCVars() { + return this.maskedcvars; + } + + // These column functions are "fixed" for a given renderer, and are + // not used directly, but only after passing them to colfuncs + protected DoomColumnFunction DrawTranslatedColumn; + protected DoomColumnFunction DrawTranslatedColumnLow; + protected DoomColumnFunction DrawColumnPlayer; + protected DoomColumnFunction DrawColumnSkies; + protected DoomColumnFunction DrawColumnSkiesLow; + protected DoomColumnFunction DrawFuzzColumn; + protected DoomColumnFunction DrawFuzzColumnLow; + protected DoomColumnFunction DrawColumn; + protected DoomColumnFunction DrawColumnLow; + protected DoomColumnFunction DrawColumnMasked; + protected DoomColumnFunction DrawColumnMaskedLow; + protected DoomColumnFunction DrawTLColumn; + + /** + * to be set in UnifiedRenderer + */ + protected DoomSpanFunction DrawSpan, DrawSpanLow; + + // ////////////// r_draw methods ////////////// + /** + * R_DrawViewBorder Draws the border around the view for different size windows + * Made use of CopyRect there + * - Good Sign 2017/04/06 + */ + @Override + public void DrawViewBorder() { + if (view.scaledwidth == DOOM.vs.getScreenWidth()) { + return; + } + + final int top = ((DOOM.vs.getScreenHeight() - DOOM.statusBar.getHeight()) - view.height) / 2; + final int side = (DOOM.vs.getScreenWidth() - view.scaledwidth) / 2; + final Rectangle rect; + // copy top + rect = new Rectangle(0, 0, DOOM.vs.getScreenWidth(), top); + DOOM.graphicSystem.CopyRect(BG, rect, FG); + // copy left side + rect.setBounds(0, top, side, view.height); + DOOM.graphicSystem.CopyRect(BG, rect, FG); + // copy right side + rect.x = side + view.scaledwidth; + DOOM.graphicSystem.CopyRect(BG, rect, FG); + // copy bottom + rect.setBounds(0, top + view.height, DOOM.vs.getScreenWidth(), top); + DOOM.graphicSystem.CopyRect(BG, rect, FG); + } + + @Override + public void ExecuteSetViewSize() { + int cosadj; + int dy; + int level; + int startmap; + + setsizeneeded = false; + + // 11 Blocks means "full screen" + if (setblocks == 11) { + view.scaledwidth = DOOM.vs.getScreenWidth(); + view.height = DOOM.vs.getScreenHeight(); + } else if (DOOM.CM.equals(Settings.scale_screen_tiles, Boolean.TRUE)) { + /** + * Make it exactly as in vanilla DOOM + * - Good Sign 2017/05/08 + */ + view.scaledwidth = (setblocks * 32) * DOOM.vs.getScalingX(); + view.height = ((setblocks * 168 / 10) & ~7) * DOOM.vs.getScalingY(); + } else { // Mocha Doom formula looks better for non-scaled tiles + view.scaledwidth = setblocks * (DOOM.vs.getScreenWidth() / 10); + // Height can only be a multiple of 8. + view.height = (short) ((setblocks * (DOOM.vs.getScreenHeight() - DOOM.statusBar.getHeight()) / 10) & ~7); + } + + skydcvars.viewheight + = maskedcvars.viewheight = dcvars.viewheight = view.height; + + view.detailshift = setdetail; + view.width = view.scaledwidth >> view.detailshift; + + view.centery = view.height / 2; + view.centerx = view.width / 2; + view.centerxfrac = (view.centerx << FRACBITS); + view.centeryfrac = (view.centery << FRACBITS); + view.projection = view.centerxfrac; + + skydcvars.centery = maskedcvars.centery = dcvars.centery = view.centery; + + // High detail + if (view.detailshift == 0) { + + colfunc = colfunchi; + dsvars.spanfunc = DrawSpan; + } else { + // Low detail + colfunc = colfunclow; + dsvars.spanfunc = DrawSpanLow; + + } + + InitBuffer(view.scaledwidth, view.height); + + InitTextureMapping(); + + // psprite scales + // pspritescale = FRACUNIT*viewwidth/vs.getScreenWidth(); + // pspriteiscale = FRACUNIT*vs.getScreenWidth()/viewwidth; + MyThings.setPspriteScale((int) (FRACUNIT * (DOOM.vs.getScreenMul() * view.width) / DOOM.vs.getScreenWidth())); + MyThings.setPspriteIscale((int) (FRACUNIT * (DOOM.vs.getScreenWidth() / (view.width * DOOM.vs.getScreenMul())))); + vp_vars.setSkyScale((int) (FRACUNIT * (DOOM.vs.getScreenWidth() / (view.width * DOOM.vs.getScreenMul())))); + + view.BOBADJUST = this.DOOM.vs.getSafeScaling() << 15; + view.WEAPONADJUST = (int) ((DOOM.vs.getScreenWidth() / (2 * DOOM.vs.getScreenMul())) * FRACUNIT); + + // thing clipping + for (int i = 0; i < view.width; i++) { + view.screenheightarray[i] = (short) view.height; + } + + // planes + for (int i = 0; i < view.height; i++) { + dy = ((i - view.height / 2) << FRACBITS) + FRACUNIT / 2; + dy = Math.abs(dy); + vp_vars.yslope[i] = FixedDiv((view.width << view.detailshift) / 2 * FRACUNIT, dy); + // MyPlanes.yslopef[i] = ((viewwidth<= colormaps.numColorMaps()) { + level = colormaps.numColorMaps() - 1; + } + colormaps.scalelight[i][j] = colormaps.colormaps[level]; + } + } + + MySegs.ExecuteSetViewSize(view.width); + } + + private final Rectangle backScreenRect = new Rectangle(); + private final Rectangle tilePatchRect = new Rectangle(); + + /** + * R_FillBackScreen Fills the back screen with a pattern for variable screen + * sizes Also draws a beveled edge. This is actually stored in screen 1, and + * is only OCCASIONALLY written to screen 0 (the visible one) by calling + * R_VideoErase. + */ + @Override + @R_Draw.C(R_FillBackScreen) + public void FillBackScreen() { + final boolean scaleSetting = Engine.getConfig().equals(Settings.scale_screen_tiles, Boolean.TRUE); + flat_t src; + DoomScreen dest; + int x; + int y; + patch_t patch; + + // DOOM border patch. + String name1 = "FLOOR7_2"; + + // DOOM II border patch. + String name2 = "GRNROCK"; + + String name; + + if (view.scaledwidth == DOOM.vs.getScreenWidth()) { + return; + } + + if (DOOM.isCommercial()) { + name = name2; + } else { + name = name1; + } + + /* This is a flat we're reading here */ + src = DOOM.wadLoader.CacheLumpName(name, PU_CACHE, flat_t.class); + dest = BG; + + /** + * TODO: cache it? + * This part actually draws the border itself, without bevels + * + * MAES: + * improved drawing routine for extended bit-depth compatibility. + * + * Now supports configurable vanilla-like scaling of tiles + * - Good Sign 2017/04/09 + * + * @SourceCode.Compatible + */ + Tiling: + { + this.backScreenRect.setBounds(0, 0, DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight() - DOOM.statusBar.getHeight()); + this.tilePatchRect.setBounds(0, 0, 64, 64); + V block = DOOM.graphicSystem.convertPalettedBlock(src.data); + if (scaleSetting) { + block = DOOM.graphicSystem.ScaleBlock(block, DOOM.vs, tilePatchRect.width, tilePatchRect.height); + this.tilePatchRect.width *= DOOM.graphicSystem.getScalingX(); + this.tilePatchRect.height *= DOOM.graphicSystem.getScalingY(); + } + DOOM.graphicSystem.TileScreenArea(dest, backScreenRect, block, tilePatchRect); + } + + final int scaleFlags = V_NOSCALESTART | (scaleSetting ? 0 : V_NOSCALEOFFSET | V_NOSCALEPATCH); + final int stepX = scaleSetting ? DOOM.graphicSystem.getScalingX() << 3 : 8; + final int stepY = scaleSetting ? DOOM.graphicSystem.getScalingY() << 3 : 8; + + patch = DOOM.wadLoader.CachePatchName("BRDR_T", PU_CACHE); + for (x = 0; x < view.scaledwidth; x += stepX) { + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx + x, view.windowy - stepY, scaleFlags); + } + + patch = DOOM.wadLoader.CachePatchName("BRDR_B", PU_CACHE); + for (x = 0; x < view.scaledwidth; x += stepX) { + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx + x, view.windowy + view.height, scaleFlags); + } + + patch = DOOM.wadLoader.CachePatchName("BRDR_L", PU_CACHE); + for (y = 0; y < view.height; y += stepY) { + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx - stepX, view.windowy + y, scaleFlags); + } + + patch = DOOM.wadLoader.CachePatchName("BRDR_R", PU_CACHE); + for (y = 0; y < view.height; y += stepY) { + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx + view.scaledwidth, view.windowy + y, scaleFlags); + } + + // Draw beveled edge. Top-left + patch = DOOM.wadLoader.CachePatchName("BRDR_TL", PU_CACHE); + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx - stepX, view.windowy - stepY, scaleFlags); + + // Top-right. + patch = DOOM.wadLoader.CachePatchName("BRDR_TR", PU_CACHE); + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx + view.scaledwidth, view.windowy - stepY, scaleFlags); + + // Bottom-left + patch = DOOM.wadLoader.CachePatchName("BRDR_BL", PU_CACHE); + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx - stepX, view.windowy + view.height, scaleFlags); + + // Bottom-right. + patch = DOOM.wadLoader.CachePatchName("BRDR_BR", PU_CACHE); + DOOM.graphicSystem.DrawPatchScaled(BG, patch, DOOM.vs, view.windowx + view.width, view.windowy + view.height, scaleFlags); + } + + /** + * R_Init + */ + @Override + public void Init() { + // Any good reason for this to be here? + // drawsegs=new drawseg_t[MAXDRAWSEGS]; + // C2JUtils.initArrayOfObjects(drawsegs); + + // DON'T FORGET ABOUT MEEEEEE!!!11!!! + this.screen = this.DOOM.graphicSystem.getScreen(FG); + + LOGGER.log(Level.INFO, "R_InitData"); + InitData(); + // InitPointToAngle (); + LOGGER.log(Level.INFO, "R_InitPointToAngle"); + + // ds.DM.viewwidth / ds.viewheight / detailLevel are set by the defaults + LOGGER.log(Level.INFO, "R_InitTables"); + InitTables(); + + SetViewSize(DOOM.menu.getScreenBlocks(), DOOM.menu.getDetailLevel()); + + LOGGER.log(Level.INFO, "R_InitPlanes"); + MyPlanes.InitPlanes(); + + LOGGER.log(Level.INFO, "R_InitLightTables"); + InitLightTables(); + + int initSkyMap = TexMan.InitSkyMap(); + LOGGER.log(Level.INFO, String.format("R_InitSkyMap: %d", initSkyMap)); + + LOGGER.log(Level.INFO, "R_InitTranslationsTables"); + InitTranslationTables(); + + LOGGER.log(Level.INFO, "R_InitTranMap"); + R_InitTranMap(0); + + LOGGER.log(Level.INFO, "R_InitDrawingFunctions"); + R_InitDrawingFunctions(); + + framecount = 0; + } + + /** + * R_InitBuffer Creates lookup tables that avoid multiplies and other + * hazzles for getting the framebuffer address of a pixel to draw. MAES: + * this is "pinned" to screen[0] of a Video Renderer. We will handle this + * differently elsewhere... + */ + protected void InitBuffer(int width, int height) { + int i; + + // Handle resize, + // e.g. smaller view windows + // with border and/or status bar. + view.windowx = (DOOM.vs.getScreenWidth() - width) >> 1; + + // Column offset. For windows. + for (i = 0; i < width; i++) { + columnofs[i] = view.windowx + i; + } + + // SamE with base row offset. + if (width == DOOM.vs.getScreenWidth()) { + view.windowy = 0; + } else { + view.windowy = (DOOM.vs.getScreenHeight() - DOOM.statusBar.getHeight() - height) >> 1; + } + + // Preclaculate all row offsets. + for (i = 0; i < height; i++) { + ylookup[i] = /* screens[0] + */ (i + view.windowy) * DOOM.vs.getScreenWidth(); + } + } + + /** + * R_InitTextureMapping Not moved into the TextureManager because it's + * tighly coupled to the visuals, rather than textures. Perhaps the name is + * not the most appropriate. + */ + protected void InitTextureMapping() { + int i, x, t; + int focallength; // fixed_t + int fov = FIELDOFVIEW; + + // For widescreen displays, increase the FOV so that the middle part of + // the + // screen that would be visible on a 4:3 display has the requested FOV. + /* + * UNUSED if (wide_centerx != centerx) { // wide_centerx is what centerx + * would be // if the display was not widescreen fov = (int) + * (Math.atan((double) centerx Math.tan((double) fov * Math.PI / + * FINEANGLES) / (double) wide_centerx) FINEANGLES / Math.PI); if (fov > + * 130 * FINEANGLES / 360) fov = 130 * FINEANGLES / 360; } + */ + // Use tangent table to generate viewangletox: + // viewangletox will give the next greatest x + // after the view angle. + // + // Calc focallength + // so FIELDOFVIEW angles covers vs.getScreenWidth(). + focallength + = FixedDiv(view.centerxfrac, finetangent[QUARTERMARK + FIELDOFVIEW + / 2]); + + for (i = 0; i < FINEANGLES / 2; i++) { + if (finetangent[i] > FRACUNIT * 2) { + t = -1; + } else if (finetangent[i] < -FRACUNIT * 2) { + t = view.width + 1; + } else { + t = FixedMul(finetangent[i], focallength); + t = (view.centerxfrac - t + FRACUNIT - 1) >> FRACBITS; + + if (t < -1) { + t = -1; + } else if (t > view.width + 1) { + t = view.width + 1; + } + } + viewangletox[i] = t; + } + + // Scan viewangletox[] to generate xtoviewangle[]: + // xtoviewangle will give the smallest view angle + // that maps to x. + for (x = 0; x <= view.width; x++) { + i = 0; + while (viewangletox[i] > x) { + i++; + } + view.xtoviewangle[x] = addAngles((i << ANGLETOFINESHIFT), -ANG90); + } + + // Take out the fencepost cases from viewangletox. + for (i = 0; i < FINEANGLES / 2; i++) { + t = FixedMul(finetangent[i], focallength); + t = view.centerx - t; + + if (viewangletox[i] == -1) { + viewangletox[i] = 0; + } else if (viewangletox[i] == view.width + 1) { + viewangletox[i] = view.width; + } + } + + clipangle = view.xtoviewangle[0]; + // OPTIMIZE: assign constant for optimization. + CLIPANGLE2 = (2 * clipangle) & BITS32; + } + + // + // R_InitLightTables + // Only inits the zlight table, + // because the scalelight table changes with view size. + // + protected final static int DISTMAP = 2; + + protected void InitLightTables() { + int i; + int j; + int startmap; + int scale; + + // Calculate the light levels to use + // for each level / distance combination. + for (i = 0; i < colormaps.lightLevels(); i++) { + startmap = ((colormaps.lightLevels() - colormaps.lightBright() - i) * 2) * colormaps.numColorMaps() / colormaps.lightLevels(); + for (j = 0; j < colormaps.maxLightZ(); j++) { + // CPhipps - use 320 here instead of vs.getScreenWidth(), otherwise hires is + // brighter than normal res + + scale = FixedDiv((320 / 2 * FRACUNIT), (j + 1) << colormaps.lightZShift()); + int t, level = startmap - (scale >>= colormaps.lightScaleShift()) / DISTMAP; + + if (level < 0) { + level = 0; + } + + if (level >= colormaps.numColorMaps()) { + level = colormaps.numColorMaps() - 1; + } + + // zlight[i][j] = colormaps + level*256; + colormaps.zlight[i][j] = colormaps.colormaps[level]; + } + } + } + + protected static final int TSC = 12; + + /** + * number of fixed point digits in + * filter percent + */ + byte[] main_tranmap; + + /** + * A faster implementation of the tranmap calculations. Almost 10x faster + * than the old one! + * + * @param progress + */ + protected void R_InitTranMap(int progress) { + int lump = DOOM.wadLoader.CheckNumForName("TRANMAP"); + + long ta = System.nanoTime(); + + // PRIORITY: a map file has been specified from commandline. Try to read + // it. If OK, this trumps even those specified in lumps. + DOOM.cVarManager.with(CommandVariable.TRANMAP, 0, (String tranmap) -> { + if (C2JUtils.testReadAccess(tranmap)) { + LOGGER.log(Level.INFO, + String.format("Translucency map file %s specified in -tranmap arg. Attempting to use...", tranmap)); + main_tranmap = new byte[256 * 256]; // killough 4/11/98 + int result = MenuMisc.ReadFile(tranmap, main_tranmap); + if (result > 0) { + return; + } + + LOGGER.log(Level.SEVERE, "R_InitTranMap: translucency map failure"); + } + }); + + // Next, if a tranlucency filter map lump is present, use it + if (lump != -1) { // Set a pointer to the translucency filter maps. + LOGGER.log(Level.INFO, "Translucency map found in lump. Attempting to use..."); + // main_tranmap=new byte[256*256]; // killough 4/11/98 + main_tranmap = DOOM.wadLoader.CacheLumpNumAsRawBytes(lump, Defines.PU_STATIC); // killough + // 4/11/98 + // Tolerate 64K or more. + if (main_tranmap.length >= 0x10000) { + return; + } + LOGGER.log(Level.SEVERE, "R_InitTranMap: tranlucency filter map failure"); // Not good, try something else. + } + + // A default map file already exists. Try to read it. + if (C2JUtils.testReadAccess("tranmap.dat")) { + LOGGER.log(Level.INFO, "Translucency map found in default tranmap.dat file. Attempting to use..."); + main_tranmap = new byte[256 * 256]; // killough 4/11/98 + int result = MenuMisc.ReadFile("tranmap.dat", main_tranmap); + if (result > 0) { + return; // Something went wrong, so fuck that. + } + } + + // Nothing to do, so we must synthesize it from scratch. And, boy, is it + // slooow. + { // Compose a default transparent filter map based on PLAYPAL. + LOGGER.log(Level.INFO, "Computing translucency map from scratch...that's gonna be SLOW..."); + byte[] playpal = DOOM.wadLoader.CacheLumpNameAsRawBytes("PLAYPAL", Defines.PU_STATIC); + main_tranmap = new byte[256 * 256]; // killough 4/11/98 + int[] basepal = new int[3 * 256]; + int[] mixedpal = new int[3 * 256 * 256]; + + main_tranmap = new byte[256 * 256]; + + // Init array of base colors. + for (int i = 0; i < 256; i++) { + basepal[3 * i] = 0Xff & playpal[i * 3]; + basepal[1 + 3 * i] = 0Xff & playpal[1 + i * 3]; + basepal[2 + 3 * i] = 0Xff & playpal[2 + i * 3]; + } + + // Init array of mixed colors. These are true RGB. + // The diagonal of this array will be the original colors. + for (int i = 0; i < 256 * 3; i += 3) { + for (int j = 0; j < 256 * 3; j += 3) { + mixColors(basepal, basepal, mixedpal, i, j, j * 256 + i); + } + } + + // Init distance map. Every original palette colour has a + // certain distance from all the others. The diagonal is zero. + // The interpretation is that e.g. the mixture of color 2 and 8 will + // have a RGB value, which is closest to euclidean distance to + // e.g. original color 9. Therefore we should put "9" in the (2,8) + // and (8,2) cells of the tranmap. + final float[] tmpdist = new float[256]; + + for (int a = 0; a < 256; a++) { + for (int b = a; b < 256; b++) { + // We evaluate the mixture of a and b + // Construct distance table vs all of the ORIGINAL colors. + for (int k = 0; k < 256; k++) { + tmpdist[k] = colorDistance(mixedpal, basepal, 3 * (a + b * 256), k * 3); + } + + main_tranmap[(a << 8) | b] = (byte) findMin(tmpdist); + main_tranmap[(b << 8) | a] = main_tranmap[(a << 8) | b]; + } + } + LOGGER.log(Level.INFO, "R_InitTranMap: done"); + if (MenuMisc.WriteFile("tranmap.dat", main_tranmap, + main_tranmap.length)) { + LOGGER.log(Level.INFO, "TRANMAP.DAT saved to disk for your convenience! Next time will be faster."); + } + } + + long b = System.nanoTime(); + LOGGER.log(Level.INFO, String.format("Tranmap %d", (b - ta) / 1000000)); + } + + /** + * Mixes two RGB colors. Nuff said + */ + protected void mixColors(int[] a, int[] b, int[] c, int pa, int pb, + int pc) { + c[pc] = (a[pa] + b[pb]) / 2; + c[pc + 1] = (a[pa + 1] + b[pb + 1]) / 2; + c[pc + 2] = (a[pa + 2] + b[pb + 2]) / 2; + + } + + /** + * Returns the euclidean distance of two RGB colors. Nuff said + */ + protected float colorDistance(int[] a, int[] b, int pa, int pb) { + return (float) Math.sqrt((a[pa] - b[pb]) * (a[pa] - b[pb]) + + (a[pa + 1] - b[pb + 1]) * (a[pa + 1] - b[pb + 1]) + + (a[pa + 2] - b[pb + 2]) * (a[pa + 2] - b[pb + 2])); + } + + /** + * Stuff that is trivially initializable, even with generics, + * but is only safe to do after all constructors have completed. + */ + protected void completeInit() { + this.detailaware.add(MyThings); + } + + protected int findMin(float[] a) { + int minindex = 0; + float min = Float.POSITIVE_INFINITY; + + for (int i = 0; i < a.length; i++) { + if (a[i] < min) { + min = a[i]; + minindex = i; + } + } + + return minindex; + + } + + /** + * R_DrawMaskedColumnSinglePost. Used to handle some special cases where + * cached columns get used as "masked" middle textures. Will be treated as a + * single-run post of capped length. + */ + + /* + * protected final void DrawCompositeColumnPost(byte[] column) { int + * topscreen; int bottomscreen; int basetexturemid; // fixed_t int + * topdelta=0; // Fixed value int length; basetexturemid = dc_texturemid; // + * That's true for the whole column. dc_source = column; // for each post... + * while (topdelta==0) { // calculate unclipped screen coordinates // for + * post topscreen = sprtopscreen + spryscale * 0; length = column.length; + * bottomscreen = topscreen + spryscale * length; dc_yl = (topscreen + + * FRACUNIT - 1) >> FRACBITS; dc_yh = (bottomscreen - 1) >> FRACBITS; if + * (dc_yh >= mfloorclip[p_mfloorclip + dc_x]) dc_yh = + * mfloorclip[p_mfloorclip + dc_x] - 1; if (dc_yl <= + * mceilingclip[p_mceilingclip + dc_x]) dc_yl = mceilingclip[p_mceilingclip + * + dc_x] + 1; // killough 3/2/98, 3/27/98: Failsafe against + * overflow/crash: if (dc_yl <= dc_yh && dc_yh < viewheight) { // Set + * pointer inside column to current post's data // Rremember, it goes + * {postlen}{postdelta}{pad}[data]{pad} dc_source_ofs = 0; // pointer + 3; + * dc_texturemid = basetexturemid - (topdelta << FRACBITS); // Drawn by + * either R_DrawColumn // or (SHADOW) R_DrawFuzzColumn. dc_texheight=0; // + * Killough try { maskedcolfunc.invoke(); } catch (Exception e){ + * System.err.printf("Error rendering %d %d %d\n", dc_yl,dc_yh,dc_yh-dc_yl); + * } } topdelta--; } dc_texturemid = basetexturemid; } + */ + protected abstract void InitColormaps() throws IOException; + + // Only used by Fuzz effect + protected BlurryTable BLURRY_MAP; + + /** + * R_InitData Locates all the lumps that will be used by all views Must be + * called after W_Init. + */ + public void InitData() { + try { + LOGGER.log(Level.INFO, "Init Texture and Flat Manager"); + TexMan = this.DOOM.textureManager; + LOGGER.log(Level.INFO, "InitTextures"); + TexMan.InitTextures(); + LOGGER.log(Level.INFO, "InitFlats"); + TexMan.InitFlats(); + LOGGER.log(Level.INFO, "InitSprites"); + DOOM.spriteManager.InitSpriteLumps(); + MyThings.cacheSpriteManager(DOOM.spriteManager); + VIS.cacheSpriteManager(DOOM.spriteManager); + LOGGER.log(Level.INFO, "InitColormaps"); + InitColormaps(); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Error: InitData failure", e); + } + + } + + protected int spritememory; + + /** + * To be called right after PrecacheLevel from SetupLevel in LevelLoader. + * It's an ugly hack, in that it must communicate with the "Game map" class + * and determine what kinds of monsters are actually in the level and + * whether it should load their graphics or not. Whenever we implement it, + * it's going to be ugly and not neatly separated anyway. + * + * @return + */ + @Override + public void PreCacheThinkers() { + + boolean[] spritepresent; + thinker_t th; + spriteframe_t sf; + int lump; + + final spritedef_t[] sprites = DOOM.spriteManager.getSprites(); + final int numsprites = DOOM.spriteManager.getNumSprites(); + + spritepresent = new boolean[numsprites]; + + for (th = DOOM.actions.getThinkerCap().next; th != DOOM.actions.getThinkerCap(); th = th.next) { + if (th.thinkerFunction == P_MobjThinker) { + spritepresent[((mobj_t) th).mobj_sprite.ordinal()] = true; + } + } + + spritememory = 0; + for (int i = 0; i < numsprites; i++) { + if (!spritepresent[i]) { + continue; + } + + for (int j = 0; j < sprites[i].numframes; j++) { + sf = sprites[i].spriteframes[j]; + for (int k = 0; k < 8; k++) { + lump = DOOM.spriteManager.getFirstSpriteLump() + sf.lump[k]; + spritememory += DOOM.wadLoader.GetLumpInfo(lump).size; + DOOM.wadLoader.CacheLumpNum(lump, PU_CACHE, patch_t.class); + } + } + } + } + + /** + * R_InitTranslationTables Creates the translation tables to map the green + * color ramp to gray, brown, red. Assumes a given structure of the PLAYPAL. + * Could be read from a lump instead. + */ + protected void InitTranslationTables() { + int i; + + final int TR_COLORS = 28; + + // translationtables = Z_Malloc (256*3+255, PU_STATIC, 0); + // translationtables = (byte *)(( (int)translationtables + 255 )& ~255); + byte[][] translationtables + = colormaps.translationtables = new byte[TR_COLORS][256]; + + // translate just the 16 green colors + for (i = 0; i < 256; i++) { + translationtables[0][i] = (byte) i; + + if (i >= 0x70 && i <= 0x7f) { + // Remap green range to other ranges. + translationtables[1][i] = (byte) (0x60 + (i & 0xf)); // gray + translationtables[2][i] = (byte) (0x40 + (i & 0xf)); // brown + translationtables[3][i] = (byte) (0x20 + (i & 0xf)); // red + translationtables[4][i] = (byte) (0x10 + (i & 0xf)); // pink + translationtables[5][i] = (byte) (0x30 + (i & 0xf)); // skin + translationtables[6][i] = (byte) (0x50 + (i & 0xf)); // metal + translationtables[7][i] = (byte) (0x80 + (i & 0xf)); // copper + translationtables[8][i] = (byte) (0xB0 + (i & 0xf)); // b.red + translationtables[9][i] = (byte) (0xC0 + (i & 0xf)); // electric + // blue + translationtables[10][i] = (byte) (0xD0 + (i & 0xf)); // guantanamo + // "Halfhue" colors for which there are only 8 distinct hues + translationtables[11][i] = (byte) (0x90 + (i & 0xf) / 2); // brown2 + translationtables[12][i] = (byte) (0x98 + (i & 0xf) / 2); // gray2 + translationtables[13][i] = (byte) (0xA0 + (i & 0xf) / 2); // piss + translationtables[14][i] = (byte) (0xA8 + (i & 0xf) / 2); // gay + translationtables[15][i] = (byte) (0xE0 + (i & 0xf) / 2); // yellow + translationtables[16][i] = (byte) (0xE8 + (i & 0xf) / 2); // turd + translationtables[17][i] = (byte) (0xF0 + (i & 0xf) / 2); // compblue + translationtables[18][i] = (byte) (0xF8 + (i & 0xf) / 2); // whore + translationtables[19][i] = (byte) (0x05 + (i & 0xf) / 2); // nigga + // "Pimped up" colors, using mixed hues. + translationtables[20][i] = (byte) (0x90 + (i & 0xf)); // soldier + translationtables[21][i] = (byte) (0xA0 + (i & 0xf)); // drag + // queen + translationtables[22][i] = (byte) (0xE0 + (i & 0xf)); // shit & + // piss + translationtables[23][i] = (byte) (0xF0 + (i & 0xf)); // raver + translationtables[24][i] = (byte) (0x70 + (0xf - i & 0xf)); // inv.marine + translationtables[25][i] = (byte) (0xF0 + (0xf - i & 0xf)); // inv.raver + translationtables[26][i] = (byte) (0xE0 + (0xf - i & 0xf)); // piss + // & + // shit + translationtables[27][i] = (byte) (0xA0 + (i & 0xf)); // shitty + // gay + } else { + for (int j = 1; j < TR_COLORS; j++) { + // Keep all other colors as is. + translationtables[j][i] = (byte) i; + } + } + } + } + + // ///////////////// Generic rendering methods ///////////////////// + public IMaskedDrawer getThings() { + return this.MyThings; + } + + /** + * e6y: this is a precalculated value for more precise flats drawing (see + * R_MapPlane) "Borrowed" from PrBoom+ + */ + protected float viewfocratio; + + protected int projectiony; + + // Some more isolation methods.... + @Override + public int getValidCount() { + return validcount; + } + + @Override + public void increaseValidCount(int amount) { + validcount += amount; + } + + @Override + public boolean getSetSizeNeeded() { + return setsizeneeded; + } + + @Override + public TextureManager getTextureManager() { + return TexMan; + } + + @Override + public PlaneDrawer getPlaneDrawer() { + return this.MyPlanes; + } + + @Override + public ViewVars getView() { + return this.view; + } + + @Override + public SpanVars getDSVars() { + return this.dsvars; + } + + @Override + public LightsAndColors getColorMap() { + return this.colormaps; + } + + @Override + public IDoomSystem getDoomSystem() { + return this.DOOM.doomSystem; + } + + @Override + public Visplanes getVPVars() { + return this.vp_vars; + } + + @Override + public SegVars getSegVars() { + return this.seg_vars; + } + + @Override + public IWadLoader getWadLoader() { + return this.DOOM.wadLoader; + } + + @Override + public ISpriteManager getSpriteManager() { + return this.DOOM.spriteManager; + } + + @Override + public BSPVars getBSPVars() { + return this.MyBSP; + } + + @Override + public IVisSpriteManagement getVisSpriteManager() { + return this.VIS; + } + + /** + * Initializes the various drawing functions. They are all "pegged" to the + * same dcvars/dsvars object. Any initializations of e.g. parallel renderers + * and their supporting subsystems should occur here. + */ + protected void R_InitDrawingFunctions() { + this.setHiColFuns(); + this.setLowColFuns(); + } + + // //////////////////////////// LIMIT RESETTING ////////////////// + @Override + public void resetLimits() { + // Call it only at the beginning of new levels. + VIS.resetLimits(); + MySegs.resetLimits(); + } + + /** + * R_RenderView As you can guess, this renders the player view of a + * particular player object. In practice, it could render the view of any + * mobj too, provided you adapt the SetupFrame method (where the viewing + * variables are set). This is the "vanilla" implementation which just works + * for most cases. + */ + @Override + public void RenderPlayerView(player_t player) { + + // Viewing variables are set according to the player's mobj. Interesting + // hacks like + // free cameras or monster views can be done. + SetupFrame(player); + + // Clear buffers. + MyBSP.ClearClipSegs(); + seg_vars.ClearDrawSegs(); + vp_vars.ClearPlanes(); + MySegs.ClearClips(); + VIS.ClearSprites(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // The head node is the last node output. + MyBSP.RenderBSPNode(DOOM.levelLoader.numnodes - 1); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // FIXME: "Warped floor" fixed, now to fix same-height visplane + // bleeding. + MyPlanes.DrawPlanes(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + MyThings.DrawMasked(); + + colfunc.main = colfunc.base; + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + } +} \ No newline at end of file diff --git a/doom/src/rr/SceneRenderer.java b/doom/src/rr/SceneRenderer.java new file mode 100644 index 0000000..26302ee --- /dev/null +++ b/doom/src/rr/SceneRenderer.java @@ -0,0 +1,98 @@ +package rr; + +import static data.Tables.FINEANGLES; +import doom.SourceCode.R_Draw; +import static doom.SourceCode.R_Draw.R_FillBackScreen; +import doom.player_t; +import i.IDoomSystem; +import static m.fixed_t.FRACUNIT; +import rr.drawfuns.ColFuncs; +import rr.drawfuns.ColVars; +import rr.drawfuns.SpanVars; +import v.tables.LightsAndColors; +import w.IWadLoader; + +public interface SceneRenderer { + + /** + * Fineangles in the SCREENWIDTH wide window. + */ + public static final int FIELDOFVIEW = FINEANGLES / 4; + public static final int MINZ = (FRACUNIT * 4); + public static final int FUZZTABLE = 50; + + /** + * killough: viewangleoffset is a legacy from the pre-v1.2 days, when Doom + * had Left/Mid/Right viewing. +/-ANG90 offsets were placed here on each + * node, by d_net.c, to set up a L/M/R session. + */ + public static final long viewangleoffset = 0; + + public void Init(); + + public void RenderPlayerView(player_t player); + + public void ExecuteSetViewSize(); + + @R_Draw.C(R_FillBackScreen) + public void FillBackScreen(); + + public void DrawViewBorder(); + + public void SetViewSize(int size, int detaillevel); + + public long PointToAngle2(int x1, int y1, int x2, int y2); + + public void PreCacheThinkers(); + + public int getValidCount(); + + public void increaseValidCount(int amount); + + public boolean isFullHeight(); + + public void resetLimits(); + + public boolean getSetSizeNeeded(); + + public boolean isFullScreen(); + + // Isolation methods + public TextureManager getTextureManager(); + + public PlaneDrawer getPlaneDrawer(); + + public ViewVars getView(); + + public SpanVars getDSVars(); + + public LightsAndColors getColorMap(); + + public IDoomSystem getDoomSystem(); + + public IWadLoader getWadLoader(); + + /** + * Use this to "peg" visplane drawers (even parallel ones) to + * the same set of visplane variables. + * + * @return + */ + public Visplanes getVPVars(); + + public SegVars getSegVars(); + + public ISpriteManager getSpriteManager(); + + public BSPVars getBSPVars(); + + public IVisSpriteManagement getVisSpriteManager(); + + public ColFuncs getColFuncsHi(); + + public ColFuncs getColFuncsLow(); + + public ColVars getMaskedDCVars(); + + //public subsector_t PointInSubsector(int x, int y); +} \ No newline at end of file diff --git a/doom/src/rr/SectorAction.java b/doom/src/rr/SectorAction.java new file mode 100644 index 0000000..c45f319 --- /dev/null +++ b/doom/src/rr/SectorAction.java @@ -0,0 +1,19 @@ +package rr; + +import doom.thinker_t; + +/** Used for special sector-based function for doors, ceilings + * etc. that are treated as a thinker by the engine. The sector + * is part of the spec, so extending classes don't need to override + * it. Also, it extends thinker so futher extensions are thinkers too. + * + */ +public abstract class SectorAction extends thinker_t { + + public sector_t sector; + + /** Special, only used when (un)archiving in order to re-link stuff + * to their proper sector. + */ + public int sectorid; +} \ No newline at end of file diff --git a/doom/src/rr/SegVars.java b/doom/src/rr/SegVars.java new file mode 100644 index 0000000..3ac1158 --- /dev/null +++ b/doom/src/rr/SegVars.java @@ -0,0 +1,32 @@ +package rr; + +import data.Limits; +import utils.C2JUtils; + +public class SegVars { + // /// FROM BSP ///////// + + public int MAXDRAWSEGS = Limits.MAXDRAWSEGS; + + /** pointer to drawsegs */ + public int ds_p; + + public drawseg_t[] drawsegs; + + public short[] maskedtexturecol; + public int pmaskedtexturecol = 0; + + /** + * R_ClearDrawSegs + * + * The drawseg list is reset by pointing back at 0. + * + */ + public void ClearDrawSegs() { + ds_p = 0; + } + + public final void ResizeDrawsegs() { + drawsegs = C2JUtils.resize(drawsegs[0], drawsegs, drawsegs.length * 2); + } +} \ No newline at end of file diff --git a/doom/src/rr/SimpleTextureManager.java b/doom/src/rr/SimpleTextureManager.java new file mode 100644 index 0000000..b8a6255 --- /dev/null +++ b/doom/src/rr/SimpleTextureManager.java @@ -0,0 +1,1448 @@ +package rr; + +import static data.Defines.PU_CACHE; +import static data.Defines.PU_STATIC; +import static data.Defines.SKYFLATNAME; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.R_Data; +import static doom.SourceCode.R_Data.R_PrecacheLevel; +import i.IDoomSystem; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import mochadoom.Loggers; +import p.AbstractLevelLoader; +import w.DoomBuffer; +import w.IWadLoader; +import w.li_namespace; +import w.lumpinfo_t; + +/** An attempt to separate texture mapping functionality from + * the rest of the rendering. Seems to work like a charm, and + * it makes it clearer what needs and what doesn't need to be + * exposed. + * + * @author Maes + * + */ +public class SimpleTextureManager implements TextureManager { + + private static final Logger LOGGER = Loggers.getLogger(SimpleTextureManager.class.getName()); + + IWadLoader W; + IDoomSystem I; + AbstractLevelLoader LL; + DoomMain DM; + + // + // Graphics. + // DOOM graphics for walls and sprites + // is stored in vertical runs of opaque pixels (posts). + // A column is composed of zero or more posts, + // a patch or sprite is composed of zero or more columns. + // + protected int firstflat; + protected int lastflat; + protected int numflats; + /** HACK */ + protected flat_t[] flats; + + //protected int firstpatch; + //protected int lastpatch; + protected int numpatches; + + protected int numtextures; + + /** The unchached textures themselves, stored just as patch lists and various properties */ + protected texture_t[] textures; + + /** Width per texture? */ + protected int[] texturewidthmask; + /** fixed_t[] needed for texture pegging */ + + /** How tall each composite texture is supposed to be */ + protected int[] textureheight; + + /** How large each composite texture is supposed to be */ + protected int[] texturecompositesize; + /** Tells us which patch lump covers which column of which texture */ + protected short[][] texturecolumnlump; + + /** This is supposed to store indexes into a patch_t lump which point to the columns themselves + * Instead, we're going to return indexes to columns inside a particular patch. + * In the case of patches inside a non-cached multi-patch texture (e.g. those made of non-overlapping + * patches), we're storing indexes INSIDE A PARTICULAR PATCH. E.g. for STARTAN1, which is made of two + * 32-px wide patches, it should go something like 0, 1,2 ,3...31, 0,1,2,....31. + * + * */ + protected char[][] texturecolumnofs; + + /** couple with texturecomposite */ + protected char texturecoloffset; + //short[][] texturecolumnindexes; + /** Stores [textures][columns][data]. */ + protected byte[][][] texturecomposite; + + /** HACK to store "composite masked textures", a Boomism. */ + protected patch_t[] patchcomposite; + + /** for global animation. Storage stores actual lumps, translation is a relative -> relative map */ + protected int[] flattranslation, flatstorage, texturetranslation; + + // This is also in DM, but one is enough, really. + protected int skytexture, skytexturemid, skyflatnum; + + public SimpleTextureManager(DoomMain DC) { + this.DM = DC; + this.W = DM.wadLoader; + this.I = DM.doomSystem; + this.LL = DM.levelLoader; + FlatPatchCache = new Hashtable<>(); + } + + /** Hash table used for matching flat lump to flat num */ + Hashtable FlatCache; + + Hashtable FlatPatchCache; + + /** + * R_CheckTextureNumForName Check whether texture is available. Filter out + * NoTexture indicator. Can be sped up with a hash table, but it's pointless. + */ + @Override + public int CheckTextureNumForName(String name) { + Integer i; + // "NoTexture" marker. + if (name.charAt(0) == '-') { + return 0; + } + + i = TextureCache.get(name); + if (i == null) { + return -1; + } else { + return i; + } + + /* for (i = 0; i < numtextures; i++) + if (textures[i].name.compareToIgnoreCase(name) == 0) + return i; + + return -1; */ + } + + /** Hash table used for fast texture lookup */ + Hashtable TextureCache; + + /** + * R_TextureNumForName + * Calls R_CheckTextureNumForName, + * aborts with error message. + */ + public int TextureNumForName(String name) { + int i; + + i = CheckTextureNumForName(name); + + if (i == -1) { + I.Error("R_TextureNumForName: %s not found", name); + } + return i; + } + + /** + * R_InitTextures + * Initializes the texture list + * with the textures from the world map. + */ + public void InitTextures() throws IOException { + // This drives the rest + maptexture_t mtexture = new maptexture_t(); + texture_t texture; + mappatch_t[] mpatch; + texpatch_t[] patch; + ByteBuffer[] maptex = new ByteBuffer[texturelumps.length]; + int[] patchlookup; + int totalwidth; + int offset; + int[] maxoff = new int[texturelumps.length]; + int[] _numtextures = new int[texturelumps.length]; + int directory = 1; + int texset = TEXTURE1; + // Load the patch names from pnames.lmp. + //name[8] = 0; + patchlookup = loadPatchNames("PNAMES"); + + // Load the map texture definitions from textures.lmp. + // The data is contained in one or two lumps, + // TEXTURE1 for shareware, plus TEXTURE2 for commercial. + for (int i = 0; i < texturelumps.length; i++) { + String TEXTUREx = texturelumps[i]; + if (W.CheckNumForName(TEXTUREx) != -1) { + maptex[i] = W.CacheLumpName(TEXTUREx, PU_STATIC).getBuffer(); + maptex[i].rewind(); + maptex[i].order(ByteOrder.LITTLE_ENDIAN); + _numtextures[i] = maptex[i].getInt(); + maxoff[i] = W.LumpLength(W.GetNumForName(TEXTUREx)); + } + } + + // Total number of textures. + numtextures = _numtextures[0] + _numtextures[1]; + + textures = new texture_t[numtextures]; + // MAES: Texture hashtable. + TextureCache = new Hashtable<>(numtextures); + + texturecolumnlump = new short[numtextures][]; + texturecolumnofs = new char[numtextures][]; + patchcomposite = new patch_t[numtextures]; + texturecomposite = new byte[numtextures][][]; + texturecompositesize = new int[numtextures]; + texturewidthmask = new int[numtextures]; + textureheight = new int[numtextures]; + + totalwidth = 0; + + LOGGER.log(Level.INFO, String.format("Textures: %d", numtextures)); + for (int i = 0; i < numtextures; i++, directory++) { + if (i == _numtextures[TEXTURE1]) { + // Start looking in second texture file. + texset = TEXTURE2; + directory = 1; // offset "1" inside maptex buffer + //System.err.print("Starting looking into TEXTURE2\n"); + } + + offset = maptex[texset].getInt(directory << 2); + + if (offset > maxoff[texset]) { + I.Error("R_InitTextures: bad texture directory"); + } + + maptex[texset].position(offset); + // Read "maptexture", which is the on-disk form. + mtexture.unpack(maptex[texset]); + + // MAES: the HashTable only needs to know the correct names. + TextureCache.put(mtexture.name.toUpperCase(), Integer.valueOf(i)); + + // We don't need to manually copy trivial fields. + textures[i] = new texture_t(); + textures[i].copyFromMapTexture(mtexture); + texture = textures[i]; + + // However we do need to correct the "patch.patch" field through the patchlookup + mpatch = mtexture.patches; + patch = texture.patches; + + for (int j = 0; j < texture.patchcount; j++) { + //System.err.printf("Texture %d name %s patch %d lookup %d\n",i,mtexture.name,j,mpatch[j].patch); + patch[j].patch = patchlookup[mpatch[j].patch]; + if (patch[j].patch == -1) { + I.Error("R_InitTextures: Missing patch in texture %s", + texture.name); + } + } + + // Columns and offsets of taxture = textures[i] + texturecolumnlump[i] = new short[texture.width]; + //C2JUtils.initArrayOfObjects( texturecolumnlump[i], column_t.class); + texturecolumnofs[i] = new char[texture.width]; + + int j = 1; + while (j * 2 <= texture.width) { + j <<= 1; + } + + texturewidthmask[i] = j - 1; + textureheight[i] = texture.height << FRACBITS; + + totalwidth += texture.width; + } + + // Precalculate whatever possible. + for (int i = 0; i < numtextures; i++) { + GenerateLookup(i); + } + + // Create translation table for global animation. + texturetranslation = new int[numtextures]; + + for (int i = 0; i < numtextures; i++) { + texturetranslation[i] = i; + } + } + + /** Assigns proper lumpnum to patch names. Check whether flats and patches of the same name coexist. + * If yes, priority should go to patches. Otherwise, it's a "flats on walls" case. + * + * @param pnames + * @return + * @throws IOException + */ + private int[] loadPatchNames(String pnames) throws IOException { + int[] patchlookup; + int nummappatches; + String name; + + ByteBuffer names = W.CacheLumpName(pnames, PU_STATIC).getBuffer(); + names.order(ByteOrder.LITTLE_ENDIAN); + + // Number of patches. + names.rewind(); + nummappatches = names.getInt(); + patchlookup = new int[nummappatches]; + + for (int i = 0; i < nummappatches; i++) { + // Get a size limited string; + name = DoomBuffer.getNullTerminatedString(names, 8).toUpperCase(); + + // Resolve clashes + int[] stuff = W.CheckNumsForName(name); + + // Move backwards. + for (int k = 0; k < stuff.length; k++) { + + // Prefer non-flat, with priority + if (W.GetLumpInfo(stuff[k]).namespace != li_namespace.ns_flats) { + patchlookup[i] = stuff[k]; + break; + } + + // Suck it down :-/ + patchlookup[i] = stuff[k]; + } + } + + return patchlookup; + } + + private patch_t retrievePatchSafe(int lump) { + + // If this is a known troublesome lump, get it from the cache. + if (FlatPatchCache.containsKey(lump)) { + return FlatPatchCache.get(lump); + } + + lumpinfo_t info = W.GetLumpInfo(lump); + patch_t realpatch; + + // Patch is actually a flat or something equally nasty. Ouch. + if (info.namespace == li_namespace.ns_flats) { + byte[] flat = W.CacheLumpNumAsRawBytes(lump, PU_CACHE); + realpatch = MultiPatchSynthesizer.synthesizePatchFromFlat(info.name, flat, 64, 64); + this.FlatPatchCache.put(lump, realpatch); + W.UnlockLumpNum(lump); + } else // It's probably safe, at this point. + { + realpatch = (patch_t) W.CacheLumpNum(lump, PU_CACHE, patch_t.class); + } + + return realpatch; + } + + /** + * R_GenerateLookup + * + * Creates the lookup tables for a given texture (aka, where inside the texture cache + * is the offset for particular column... I think. + * + * @throws IOException + */ + @Override + public void GenerateLookup(int texnum) throws IOException { + texture_t texture; + short[] patchcount; //Keeps track of how many patches overlap a column. + texpatch_t[] patch; + patch_t realpatch = null; + int x; + int x1; + int x2; + + short[] collump; + char[] colofs; + + texture = textures[texnum]; + + // Composited texture not created yet. + texturecomposite[texnum] = null; + + // We don't know ho large the texture will be, yet, but it will be a multiple of its height. + texturecompositesize[texnum] = 0; + + // This is the only place where those can be actually modified. + // They are still null at this point. + collump = texturecolumnlump[texnum]; + colofs = texturecolumnofs[texnum]; + + /* Now count the number of columns that are covered by more + * than one patch. Fill in the lump / offset, so columns + * with only a single patch are all done. + */ + patchcount = new short[texture.width]; + patch = texture.patches; + + // for each patch in a texture... + for (int i = 0; i < texture.patchcount; i++) { + // Retrieve patch...if it IS a patch. + realpatch = this.retrievePatchSafe(patch[i].patch); + + x1 = patch[i].originx; + x2 = x1 + realpatch.width; + + // Where does the patch start, inside the compositetexture? + if (x1 < 0) { + x = 0; + } else { + x = x1; + } + + // Correct, starts at originx. Where does it end? + if (x2 > texture.width) { + x2 = texture.width; + } + for (; x < x2; x++) { + /* Obviously, if a patch starts at x it does cover the x-th column + * of a texture, even if transparent. + */ + patchcount[x]++; + // Column "x" of composite texture "texnum" is covered by this patch. + collump[x] = (short) patch[i].patch; + + /* This is supposed to be a raw pointer to the beginning of the column + * data, as it appears inside the PATCH. + * + * Instead, we can return the actual column index (x-x1) + * As an example, the second patch of STARTAN1 (width 64) starts + * at column 32. Therefore colofs should be something like + * 0,1,2,...,31,0,1,....31, indicating that the 32-th column of + * STARTAN1 is the 0-th column of the patch that is assigned to that column + * (the latter can be looked up in texturecolumnlump[texnum]. + * + * Any questions? + * + */ + colofs[x] = (char) (x - x1); + // This implies that colofs[x] is 0 for a void column? + + } // end column of patch. + } // end patch + + // Now check all columns again. + for (x = 0; x < texture.width; x++) { + // Can only occur if a column isn't covered by a patch at all, not even a transparent one. + if (patchcount[x] == 0) { + // TODO: somehow handle this. + LOGGER.log(Level.WARNING, String.format("R_GenerateLookup: column without a patch (%s), width: %d", + texture.name, realpatch.width)); + //return; + } + // I_Error ("R_GenerateLookup: column without a patch"); + + // Columns where more than one patch overlaps. + if (patchcount[x] > 1) { + // Use the cached block. This column won't be read from the wad system. + collump[x] = -1; + colofs[x] = (char) texturecompositesize[texnum]; + + /* Do we really mind? + if (texturecompositesize[texnum] > 0x10000-texture.height) + { + I.Error ("R_GenerateLookup: texture no %d (%s) is >64k", + texnum,textures[texnum].name); + } */ + texturecompositesize[texnum] += texture.height; + } + } + } + + /** + * R_GenerateComposite + * Using the texture definition, the composite texture is created + * from the patches and each column is cached. This method is "lazy" + * aka it's only called when a cached/composite texture is needed. + * + * @param texnum + * + */ + public void GenerateComposite(int texnum) { + byte[][] block; + texture_t texture; + texpatch_t[] patch; + patch_t realpatch = null; + int x; + int x1; + int x2; + column_t patchcol; + short[] collump; + char[] colofs; // unsigned short + // short[] colidxs; // unsigned short + + texture = textures[texnum]; + + // BOth allocate the composite texture, and assign it to block. + // texturecompositesize indicates a size in BYTES. We need a number of columns, though. + // Now block is divided into columns. We need to allocate enough data for each column + block = texturecomposite[texnum] = new byte[texture.width][texture.height]; + + // Lump where a certain column will be read from (actually, a patch) + collump = texturecolumnlump[texnum]; + + // Offset of said column into the patch. + colofs = texturecolumnofs[texnum]; + + // colidxs = texturecolumnindexes[texnum]; + // Composite the columns together. + patch = texture.patches; + + // For each patch in the texture... + for (int i = 0; i < texture.patchcount; i++) { + // Retrieve patch...if it IS a patch. + realpatch = this.retrievePatchSafe(patch[i].patch); + + x1 = patch[i].originx; + x2 = x1 + realpatch.width; + + if (x1 < 0) { + x = 0; + } else { + x = x1; + } + + if (x2 > texture.width) { + x2 = texture.width; + } + + for (; x < x2; x++) { + // Column does not have multiple patches? + if (collump[x] >= 0) { + continue; + } + + // patchcol = (column_t *)((byte *)realpatch + // + LONG(realpatch.columnofs[x-x1])); + // We can look this up cleanly in Java. Ha! + patchcol = realpatch.columns[x - x1]; + DrawColumnInCache(patchcol, + block[x], colofs[x], + patch[i].originy, + texture.height); + } + + } + } + + /** + * R_GenerateMaskedComposite + * + * Generates a "masked composite texture": the result is a MASKED texture + * (with see-thru holes), but this time multiple patches can be used to + * assemble it, unlike standard Doom where this is not allowed. + * + * Called only if a request for a texture in the general purpose GetColumn + * method (used only for masked renders) turns out not to be pointing to a standard + * cached texture, nor to a disk lump(which is the standard Doom way of indicating a + * composite single patch texture) but to a cached one which, however, is composite. + * + * Confusing, huh? + * + * Normally, this results in a disaster, as the masked rendering methods + * don't expect cached/composite textures at all, and you get all sorts of nasty + * tutti frutti and medusa effects. Not anymore ;-) + * + * @param texnum + * + */ + @Override + public void GenerateMaskedComposite(int texnum) { + byte[][] block; + boolean[][] pixmap; // Solidity map + texture_t texture; + texpatch_t[] patch; + patch_t realpatch = null; + int x; + int x1; + int x2; + column_t patchcol; + short[] collump; + char[] colofs; // unsigned short + + texture = textures[texnum]; + + // MAES: we don't want to save a solid block this time. Will only use + // it for synthesis. + block = new byte[texture.width][texture.height]; + pixmap = new boolean[texture.width][texture.height]; // True values = solid + + // Lump where a certain column will be read from (actually, a patch) + collump = texturecolumnlump[texnum]; + + // Offset of said column into the patch. + colofs = texturecolumnofs[texnum]; + + // Composite the columns together. + patch = texture.patches; + + // For each patch in the texture... + for (int i = 0; i < texture.patchcount; i++) { + + realpatch = W.CachePatchNum(patch[i].patch); + x1 = patch[i].originx; + x2 = x1 + realpatch.width; + + if (x1 < 0) { + x = 0; + } else { + x = x1; + } + + if (x2 > texture.width) { + x2 = texture.width; + } + + for (; x < x2; x++) { + // Column does not have multiple patches? + if (collump[x] >= 0) { + continue; + } + + // patchcol = (column_t *)((byte *)realpatch + // + LONG(realpatch.columnofs[x-x1])); + // We can look this up cleanly in Java. Ha! + patchcol = realpatch.columns[x - x1]; + DrawColumnInCache(patchcol, block[x], pixmap[x], colofs[x], + patch[i].originy, texture.height); + } + + } + + // Patch drawn on cache, synthesize patch_t using it. + this.patchcomposite[texnum] = MultiPatchSynthesizer.synthesize(this.CheckTextureNameForNum(texnum), block, pixmap, texture.width, texture.height); + } + + /** + * R_DrawColumnInCache + * Clip and draw a column from a patch into a cached post. + * + * This means that columns are effectively "uncompressed" into cache, here, + * and that composite textures are generally uncompressed...right? + * + * Actually: "compressed" or "masked" textures are retrieved in the same way. + * There are both "masked" and "unmasked" drawing methods. If a masked + * column is passed to a method that expects a full, dense column...well, + * it will look fugly/overflow/crash. Vanilla Doom tolerated this, + * we're probably going to have more problems. + * + * @param patch Actually it's a single column to be drawn. May overdraw existing ones or void space. + * @param cache the column cache itself. Actually it's the third level [texture][column]->data. + * @param offset an offset inside the column cache (UNUSED) + * @param originy vertical offset. Caution with masked stuff! + * @param cacheheight the maximum height it's supposed to reach when drawing? + * + */ + public void DrawColumnInCache(column_t patch, byte[] cache, int offset, + int originy, int cacheheight) { + int count; + int position; + int source = 0; // treat as pointers + + /* + * Iterate inside column. This is starkly different from the C code, + * because post positions AND offsets are already precomputed at load + * time + */ + for (int i = 0; i < patch.posts; i++) { + + // This should position us at the beginning of the next post + source = patch.postofs[i]; + + count = patch.postlen[i]; // length of this particular post + position = originy + patch.postdeltas[i]; // Position to draw inside + // cache. + + // Post starts outside of texture's bounds. Adjust offset. + if (position < 0) { + count += position; // Consider that we have a "drawing debt". + position = 0; + } + + // Post will go too far outside. + if (position + count > cacheheight) { + count = cacheheight - position; + } + + if (count > 0) // Draw this post. Won't draw posts that start + // "outside" + // Will start at post's start, but will only draw enough pixels + // not to overdraw. + { + System.arraycopy(patch.data, source, cache, position, count); + } + + } + } + + // Version also drawing on a supplied transparency map + public void DrawColumnInCache(column_t patch, byte[] cache, + boolean[] pixmap, int offset, int originy, int cacheheight) { + int count; + int position; + int source = 0; // treat as pointers + + /* + * Iterate inside column. This is starkly different from the C code, + * because post positions AND offsets are already precomputed at load + * time + */ + for (int i = 0; i < patch.posts; i++) { + + // This should position us at the beginning of the next post + source = patch.postofs[i]; + + count = patch.postlen[i]; // length of this particular post + position = originy + patch.postdeltas[i]; // Position to draw inside + // cache. + + // Post starts outside of texture's bounds. Adjust offset. + if (position < 0) { + count += position; // Consider that we have a "drawing debt". + position = 0; + } + + // Post will go too far outside. + if (position + count > cacheheight) { + count = cacheheight - position; + } + + if (count > 0) { + // Draw post, AND fill solidity map + System.arraycopy(patch.data, source, cache, position, count); + Arrays.fill(pixmap, position, position + count, true); + } + // Repeat for next post(s), if any. + } + } + + /** + * R_InitFlats + * + * Scans WADs for F_START/F_END lumps, and also any additional + * F1_ and F2_ pairs. + * + * Correct behavior would be to detect F_START/F_END lumps, + * and skip any marker lumps sandwiched in between. If F_START and F_END are external, + * use external override. + * + * Also, in the presence of external FF_START lumps, merge their contents + * with those previously read. + * + * The method is COMPATIBLE with resource pre-coalesing, however it's not + * trivial to change back to the naive code because of the "translationless" + * system used (all flats are assumed to lie in a linear space). This + * speeds up lookups. + * + */ + @Override + public final void InitFlats() { + numflats = 0; + int extendedflatstart = -1; + firstflat = W.GetNumForName(LUMPSTART); // This is the start of normal lumps. + if (FlatCache == null) { + FlatCache = new Hashtable<>(); + } else { + FlatCache.clear(); + } + Hashtable FlatNames = new Hashtable<>(); // Store names here. + + // Normally, if we don't use Boom features, we could look for F_END and that's it. + // However we want to avoid using flat translation and instead store absolute lump numbers. + // So we need to do a clean parse. + // The rule is: we scan from the very first F_START to the very first F_END. + // We discard markers, and only assign sequential numbers to valid lumps. + // These are the vanilla flats, and will work with fully merged PWADs too. + // Normally, this marks the end of regular lumps. However, if DEUTEX extension + // are present, it will actually mark the end of the extensions due to lump + // priority, so its usefulness as an absolute end-index for regular flats + // is dodgy at best. Gotta love the inconsistent mundo hacks! + //int lastflatlump=W.GetNumForName(LUMPEND); + // + int lump = firstflat; + int seq = 0; + String name; + while (!(name = W.GetNameForNum(lump)).equalsIgnoreCase(LUMPEND)) { + if (!W.isLumpMarker(lump)) { + // Not a marker. Put in cache. + FlatCache.put(lump, seq); + // Save its name too. + FlatNames.put(name, lump); + seq++; // Advance sequence + numflats++; // Total flats do increase + } + lump++; // Advance lump. + } + + extendedflatstart = W.CheckNumForName(DEUTEX_START); // This is the start of DEUTEX flats. + if (extendedflatstart > -1) { + // If extended ones are present, then Advance slowly. + lump = extendedflatstart; + + // Safeguard: FF_START without corresponding F_END (e.g. in helltest.wad) + name = W.GetNameForNum(lump); + + // The end of those extended flats is also marked by F_END or FF_END, as noted above. + // It can also be non-existent in some broken maps like helltest.wad. Jesus. + while (!(name == null || name.equalsIgnoreCase(LUMPEND) || name.equalsIgnoreCase(DEUTEX_END))) { + if (!W.isLumpMarker(lump)) { + // Not a marker. Check if it's supposed to replace something. + if (FlatNames.containsKey(name)) { + // Well, it is. Off with its name, save the lump number though. + int removed = FlatNames.remove(name); + // Put new name in list + FlatNames.put(name, lump); + // Remove old lump, but keep sequence. + int oldseq = FlatCache.remove(removed); + // Put new lump number with old sequence. + FlatCache.put(lump, oldseq); + } else { // Add normally + FlatCache.put(lump, seq); + // Save its name too. + FlatNames.put(name, lump); + seq++; // Advance sequence + numflats++; // Total flats do increase + } + } + lump++; // Advance lump. + name = W.GetNameForNum(lump); + } + } + + // So now we have a lump -> sequence number mapping. + // Create translation table for global animation. + flattranslation = new int[numflats]; + flatstorage = new int[numflats]; + + // MAJOR CHANGE: flattranslation stores absolute lump numbers. Adding + // firstlump is not necessary anymore. + // Now, we're pretty sure that we have a progressive value mapping. + Enumeration stuff = FlatCache.keys(); + while (stuff.hasMoreElements()) { + int nextlump = stuff.nextElement(); + flatstorage[FlatCache.get(nextlump)] = nextlump; + // Lump is used as the key, while the relative lump number is the value. + //FlatCache.put(j, k-1); + + } + + for (int i = 0; i < numflats; i++) { + flattranslation[i] = i; + // System.out.printf("Verification: flat[%d] is %s in lump %d\n",i,W.GetNameForNum(flattranslation[i]),flatstorage[i]); + } + } + + private final static String LUMPSTART = "F_START"; + private final static String LUMPEND = "F_END"; + private final static String DEUTEX_END = "FF_END"; + private final static String DEUTEX_START = "FF_START"; + + /** + * R_PrecacheLevel + * Preloads all relevant graphics for the level. + * + * MAES: Everything except sprites. + * A Texturemanager != sprite manager. + * So the relevant functionality has been split into + * PrecacheThinkers (found in common rendering code). + * + * + */ + int flatmemory; + int texturememory; + + @Override + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + @R_Data.C(R_PrecacheLevel) + public void PrecacheLevel() throws IOException { + + this.preCacheFlats(); + this.preCacheTextures(); + + // recache sprites. + /* MAES: this code into PrecacheThinkers + spritepresent = new boolean[numsprites]; + + + for (th = P.thinkercap.next ; th != P.thinkercap ; th=th.next) + { + if (th.function==think_t.P_MobjThinker) + spritepresent[((mobj_t )th).sprite.ordinal()] = true; + } + + spritememory = 0; + for (i=0 ; i roguePatches = new HashMap<>(); + + class TextureDirectoryEntry implements Comparable { + + /** Where an entry starts within the TEXTUREx lump */ + int offset; + /** Its implicit position as indicated by the directory's ordering */ + int entry; + /** Its MAXIMUM possible length, depending on what follows it. + * Not trivial to compute without thoroughtly examining the entire lump */ + int length; + + /** Entries are ranked according to actual offset */ + @Override + public int compareTo(TextureDirectoryEntry o) { + if (this.offset < o.offset) { + return -1; + } + if (this.offset == o.offset) { + return 0; + } + return 1; + } + } + + @Override + public byte[] getSafeFlat(int flatnum) { + byte[] flat = ((flat_t) W.CacheLumpNum(getFlatTranslation(flatnum), + PU_STATIC, flat_t.class)).data; + + if (flat.length < 4096) { + System.arraycopy(flat, 0, safepatch, 0, flat.length); + return safepatch; + } + + return flat; + } + + private final byte[] safepatch = new byte[4096]; + + // COLUMN GETTING METHODS. No idea why those had to be in the renderer... + /** + * Special version of GetColumn meant to be called concurrently by different + * (MASKED) seg rendering threads, identfiex by index. This serves to avoid stomping + * on mutual cached textures and causing crashes. + * + * Returns column_t, so in theory it could be made data-agnostic. + * + */ + public column_t GetSmpColumn(int tex, int col, int id) { + int lump, ofs; + + col &= getTexturewidthmask(tex); + lump = getTextureColumnLump(tex, col); + ofs = getTextureColumnOfs(tex, col); + + // It's always 0 for this kind of access. + // Speed-increasing trick: speed up repeated accesses to the same + // texture or patch, if they come from the same lump + if (tex == smp_lasttex[id] && lump == smp_lastlump[id]) { + if (composite) { + return smp_lastpatch[id].columns[col]; + } else { + return smp_lastpatch[id].columns[ofs]; + } + } + + // If pointing inside a non-zero, positive lump, then it's not a + // composite texture. Read it from disk. + if (lump > 0) { + // This will actually return a pointer to a patch's columns. + // That is, to the ONE column exactly.{ + // If the caller needs access to a raw column, we must point 3 bytes + // "ahead". + smp_lastpatch[id] = W.CachePatchNum(lump); + smp_lasttex[id] = tex; + smp_lastlump[id] = lump; + smp_composite[id] = false; + // If the column was a disk lump, use ofs. + return smp_lastpatch[id].columns[ofs]; + } + + // Problem. Composite texture requested as if it was masked + // but it doesn't yet exist. Create it. + if (getMaskedComposite(tex) == null) { + LOGGER.log(Level.WARNING, String.format("Forced generation of composite %s", + CheckTextureNameForNum(tex))); + GenerateMaskedComposite(tex); + LOGGER.log(Level.WARNING, String.format("Composite patch %s %d", + getMaskedComposite(tex).name, getMaskedComposite(tex).columns.length)); + } + + // Last resort. + smp_lastpatch[id] = getMaskedComposite(tex); + smp_lasttex[id] = tex; + smp_composite[id] = true; + smp_lastlump[id] = 0; + + return lastpatch.columns[col]; + } + + // False: disk-mirrored patch. True: improper "transparent composite". + protected boolean[] smp_composite;// = false; + protected int[] smp_lasttex;// = -1; + protected int[] smp_lastlump;// = -1; + protected patch_t[] smp_lastpatch;// = null; + +///////////////////////// TEXTURE MANAGEMENT ///////////////////////// + /** + * R_GetColumn original version: returns raw pointers to byte-based column + * data. Works for both masked and unmasked columns, but is not + * tutti-frutti-safe. + * + * Use GetCachedColumn instead, if rendering non-masked stuff, which is also + * faster. + * + * @throws IOException + * + * + */ + public byte[] GetColumn(int tex, int col) { + int lump, ofs; + + col &= getTexturewidthmask(tex); + lump = getTextureColumnLump(tex, col); + ofs = getTextureColumnOfs(tex, col); + + // It's always 0 for this kind of access. + // Speed-increasing trick: speed up repeated accesses to the same + // texture or patch, if they come from the same lump + if (tex == lasttex && lump == lastlump) { + if (composite) { + return lastpatch.columns[col].data; + } else { + return lastpatch.columns[ofs].data; + } + } + + // If pointing inside a non-zero, positive lump, then it's not a + // composite texture. Read it from disk. + if (lump > 0) { + // This will actually return a pointer to a patch's columns. + // That is, to the ONE column exactly.{ + // If the caller needs access to a raw column, we must point 3 bytes + // "ahead". + lastpatch = W.CachePatchNum(lump); + lasttex = tex; + lastlump = lump; + composite = false; + // If the column was a disk lump, use ofs. + return lastpatch.columns[ofs].data; + } + + // Problem. Composite texture requested as if it was masked + // but it doesn't yet exist. Create it. + if (getMaskedComposite(tex) == null) { + LOGGER.log(Level.WARNING, + String.format("Forced generation of composite %s", CheckTextureNameForNum(tex))); + GenerateMaskedComposite(tex); + LOGGER.log(Level.WARNING, + String.format("Composite patch %s %d", getMaskedComposite(tex).name, getMaskedComposite(tex).columns.length)); + } + + // Last resort. + lastpatch = getMaskedComposite(tex); + lasttex = tex; + composite = true; + lastlump = 0; + + return lastpatch.columns[col].data; + } + + /** + * R_GetColumnStruct: returns actual pointers to columns. + * Agnostic of the underlying type. + * + * Works for both masked and unmasked columns, but is not + * tutti-frutti-safe. + * + * Use GetCachedColumn instead, if rendering non-masked stuff, which is also + * faster. + * + * @throws IOException + * + * + */ + @Override + public column_t GetColumnStruct(int tex, int col) { + int lump, ofs; + + col &= getTexturewidthmask(tex); + lump = getTextureColumnLump(tex, col); + ofs = getTextureColumnOfs(tex, col); + + // Speed-increasing trick: speed up repeated accesses to the same + // texture or patch, if they come from the same lump + if (tex == lasttex && lump == lastlump) { + if (composite) { + return lastpatch.columns[col]; + } else { + return lastpatch.columns[ofs]; + } + } + + // If pointing inside a non-zero, positive lump, then it's not a + // composite texture. Read it from disk. + if (lump > 0) { + // This will actually return a pointer to a patch's columns. + // That is, to the ONE column exactly.{ + // If the caller needs access to a raw column, we must point 3 bytes + // "ahead". + lastpatch = W.CachePatchNum(lump); + lasttex = tex; + lastlump = lump; + composite = false; + // If the column was a disk lump, use ofs. + return lastpatch.columns[ofs]; + } + + // Problem. Composite texture requested as if it was masked + // but it doesn't yet exist. Create it. + if (getMaskedComposite(tex) == null) { + LOGGER.log(Level.WARNING, + String.format("Forced generation of composite %s", CheckTextureNameForNum(tex))); + GenerateMaskedComposite(tex); + LOGGER.log(Level.WARNING, + String.format("Composite patch %s %d", getMaskedComposite(tex).name, getMaskedComposite(tex).columns.length)); + } + + // Last resort. + lastpatch = getMaskedComposite(tex); + lasttex = tex; + composite = true; + lastlump = 0; + + return lastpatch.columns[col]; + } + + // False: disk-mirrored patch. True: improper "transparent composite". + private boolean composite = false; + private int lasttex = -1; + private int lastlump = -1; + private patch_t lastpatch = null; + + /** + * R_GetColumn variation which is tutti-frutti proof. It only returns cached + * columns, and even pre-caches single-patch textures intead of trashing the + * WAD manager (should be faster, in theory). + * + * Cannot be used for drawing masked textures, use classic GetColumn + * instead. + * + * + * @throws IOException + */ + @Override + public final byte[] GetCachedColumn(int tex, int col) { + int lump, ofs; + + col &= getTexturewidthmask(tex); + lump = getTextureColumnLump(tex, col); + ofs = getTextureColumnOfs(tex, col); + + // In the case of cached columns, this is always 0. + // Done externally, for now. + //dcvars.dc_source_ofs = 0; + // If pointing inside a non-zero, positive lump, then it's not a + // composite texture. + // Read from disk, and safeguard vs tutti frutti. + if (lump > 0) { + // This will actually return a pointer to a patch's columns. + return getRogueColumn(lump, ofs); + } + + // Texture should be composite, but it doesn't yet exist. Create it. + if (getTextureComposite(tex) == null) { + GenerateComposite(tex); + } + + return getTextureComposite(tex, col); + } + + @Override + public void setSMPVars(int num_threads) { + smp_composite = new boolean[num_threads];// = false; + smp_lasttex = new int[num_threads];// = -1; + smp_lastlump = new int[num_threads];// = -1; + smp_lastpatch = new patch_t[num_threads];// = null; + } + +} \ No newline at end of file diff --git a/doom/src/rr/SimpleThings.java b/doom/src/rr/SimpleThings.java new file mode 100644 index 0000000..250c1de --- /dev/null +++ b/doom/src/rr/SimpleThings.java @@ -0,0 +1,23 @@ +package rr; + +import v.scale.VideoScale; + +/** + * A very "simple" things class which just does serial rendering and uses all + * the base methods from AbstractThings. + * + * @author velktron + * @param + * @param + */ +public final class SimpleThings extends AbstractThings { + + public SimpleThings(VideoScale vs, SceneRenderer R) { + super(vs, R); + } + + @Override + public void completeColumn() { + colfunc.invoke(); + } +} \ No newline at end of file diff --git a/doom/src/rr/SpriteManager.java b/doom/src/rr/SpriteManager.java new file mode 100644 index 0000000..75c7679 --- /dev/null +++ b/doom/src/rr/SpriteManager.java @@ -0,0 +1,593 @@ +package rr; + +import static data.Defines.PU_CACHE; +import doom.DoomMain; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import mochadoom.Loggers; +import static utils.C2JUtils.memset; +import static utils.GenericCopy.malloc; +import w.lumpinfo_t; + +/** An stand-alone sprite loader. Surprisingly, it is quite a + * separate concern from the renderer, and only needs to communicate + * occasionally through its getters with the rest of the stuff. + * + * Helped lighten up the rendering code a lot, too. + * + * @author Maes + * + */ +public class SpriteManager implements ISpriteManager { + + private static final Logger LOGGER = Loggers.getLogger(SpriteManager.class.getName()); + + /** There seems to be an arbitrary limit of 29 distinct frames per THING */ + public static final int MAX_SPRITE_FRAMES = 29; + + public SpriteManager(DoomMain DOOM) { + sprtemp = malloc(spriteframe_t::new, spriteframe_t[]::new, MAX_SPRITE_FRAMES); + this.DOOM = DOOM; + } + + private final DoomMain DOOM; + + // Temporarily contains the frames of a given sprite before they are + // registered with the rendering system. Apparently, a maximum of 29 frames + // per sprite is allowed. + protected spriteframe_t[] sprtemp = new spriteframe_t[29]; + protected int maxframe; + protected String spritename; + + // MAES: Shit taken from things + protected int firstspritelump; + protected int lastspritelump; + protected int numspritelumps; + + // variables used to look up and range check thing_t sprites patches + // + protected spritedef_t[] sprites; + + protected int numsprites; + + /** needed for pre rendering (fixed_t[]) */ + protected int[] spritewidth, spriteoffset, spritetopoffset; + + // + // R_InitSpriteDefs + // Pass a null terminated list of sprite names + // (4 chars exactly) to be used. + // + // Builds the sprite rotation matrixes to account + // for horizontally flipped sprites. + // + // Will report an error if the lumps are inconsistent. + // Only called at startup. + // + // Sprite lump names are 4 characters for the actor, + // a letter for the frame, and a number for the rotation. + // + // A sprite that is flippable will have an additional + // letter/number appended. + // + // The rotation character can be 0 to signify no rotations. + // + // 1/25/98, 1/31/98 killough : Rewritten for performance + // + // Empirically verified to have excellent hash + // properties across standard Doom sprites: + protected final void InitSpriteDefs(String[] namelist) { + int numentries = lastspritelump - firstspritelump + 1; + HashMap> hash; + int i; + + if (numentries == 0 || namelist == null) { + return; + } + + // count the number of sprite names + i = namelist.length; + + numsprites = i; + + sprites = malloc(spritedef_t::new, spritedef_t[]::new, numsprites); + + // Create hash table based on just the first four letters of each + // sprite + // killough 1/31/98 + // Maes: the idea is to have a chained hastable which can handle + // multiple entries (sprites) on the same primary key (the 4 first chars of + // the sprite name) + hash = new HashMap<>(numentries); // allocate hash table + + // We have to trasverse this in the opposite order, so that later + // lumps + // trump previous ones in order. + for (i = numentries - 1; i >= 0; i--) { + int hashcode = SpriteNameHash(DOOM.wadLoader.GetLumpInfo(i + firstspritelump).name); + // Create chain list for each sprite class (e.g. TROO, POSS, + // etc.) + // + if (!hash.containsKey(hashcode)) { + hash.put(hashcode, new ArrayList<>()); + } + + // Store (yet another) lump index for this sprite. + hash.get(hashcode).add(i); + } + + // scan all the lump names for each of the names, + // noting the highest frame letter. + for (i = 0; i < numsprites; i++) { + + // We only look for entries that are known to be sprites. + // The hashtable may contain a lot of other shit, at this point + // which will be hopefully ignored. + String spritename = namelist[i]; + List list = hash.get(SpriteNameHash(spritename)); + + // Well, it may have been something else. Fuck it. + if (list != null && !list.isEmpty()) { + + // Maes: the original code actually set everything to "-1" + // here, including the + // "boolean" rotate value. The idea was to create a + // "tristate" of sorts, where -1 + // means a sprite of uncertain status. Goto + // InstallSpriteLumps for more. + for (final spriteframe_t sprtemp1 : sprtemp) { + memset(sprtemp1.flip, (byte) -1, sprtemp1.flip.length); + memset(sprtemp1.lump, (short) -1, sprtemp1.lump.length); + // This should be INDETERMINATE at this point. + sprtemp1.rotate = -1; + } + maxframe = -1; + + // What is stored in the lists are all actual lump numbers + // relative + // to e.g. TROO. In coalesced lumps, there will be overlap. + // This procedure should, in theory, trump older ones. + list.forEach((j) -> { + lumpinfo_t lump = DOOM.wadLoader.GetLumpInfo(j + firstspritelump); + // We don't know a-priori which frames exist. + // However, we do know how to interpret existing ones, + // and have an implicit maximum sequence of 29 Frames. + // A frame can also hame multiple rotations. + if (lump.name.substring(0, 4).equalsIgnoreCase( + spritename.substring(0, 4))) { + int frame = lump.name.charAt(4) - 'A'; + int rotation = lump.name.charAt(5) - '0'; + if (sprtemp[frame].rotate != -1) { + // We already encountered this sprite, but we + // may need to trump it with something else + + } + InstallSpriteLump(j + firstspritelump, frame, + rotation, false); + if (lump.name.length() >= 7) { + frame = lump.name.charAt(6) - 'A'; + rotation = lump.name.charAt(7) - '0'; + InstallSpriteLump(j + firstspritelump, frame, + rotation, true); + } + } + }); + + // check the frames that were found for completeness + if ((sprites[i].numframes = ++maxframe) != 0) // killough + // 1/31/98 + { + int frame; + for (frame = 0; frame < maxframe; frame++) { + switch (sprtemp[frame].rotate) { + case -1: + // no rotations were found for that frame at all + DOOM.doomSystem.Error("R_InitSprites: No patches found for %s frame %c", + namelist[i], frame + 'A'); + break; + + case 0: + // only the first rotation is needed + break; + + case 1: // must have all 8 frames + { + int rotation; + for (rotation = 0; rotation < 8; rotation++) { + if (sprtemp[frame].lump[rotation] == -1) { + DOOM.doomSystem.Error("R_InitSprites: Sprite %s frame %c is missing rotations", + namelist[i], frame + 'A'); + } + } + break; + } + } + } + // allocate space for the frames present and copy + // sprtemp to it + // MAES: we can do that elegantly in one line. + + sprites[i].copy(sprtemp, maxframe); + } + + } + } + + } + + /** + * R_InitSpriteLumps Finds the width and hoffset of all sprites in the wad, + * so the sprite does not need to be cached completely just for having the + * header info ready during rendering. + */ + public void InitSpriteLumps() { + int i; + patch_t patch; + + firstspritelump = DOOM.wadLoader.GetNumForName("S_START") + 1; + lastspritelump = DOOM.wadLoader.GetNumForName("S_END") - 1; + + numspritelumps = lastspritelump - firstspritelump + 1; + spritewidth = new int[numspritelumps]; + spriteoffset = new int[numspritelumps]; + spritetopoffset = new int[numspritelumps]; + + LOGGER.log(Level.INFO, String.format("Sprite lumps: %d", numspritelumps)); + for (i = 0; i < numspritelumps; i++) { + patch = DOOM.wadLoader.CacheLumpNum(firstspritelump + i, PU_CACHE, + patch_t.class); + spritewidth[i] = patch.width << FRACBITS; + spriteoffset[i] = patch.leftoffset << FRACBITS; + spritetopoffset[i] = patch.topoffset << FRACBITS; + } + } + + /** + * R_InstallSpriteLump Local function for R_InitSprites. + * + * Boom function, more suited to resource coalescing. + * + */ + public final void InstallSpriteLump(int lump, int frame, + int rotation, boolean flipped) { + if (frame >= MAX_SPRITE_FRAMES || rotation > 8) { + DOOM.doomSystem.Error("R_InstallSpriteLump: Bad frame characters in lump %d", + lump); + } + + if (frame > maxframe) { + maxframe = frame; + } + + if (rotation == 0) { // the lump should be used for all rotations + int r; + for (r = 0; r < 8; r++) { + if (sprtemp[frame].lump[r] == -1) { + sprtemp[frame].lump[r] = lump - firstspritelump; + sprtemp[frame].flip[r] = (byte) (flipped ? 1 : 0); + sprtemp[frame].rotate = 0; // jff 4/24/98 if any subbed, + // rotless + } + } + return; + } + + // the lump is only used for one rotation + if (sprtemp[frame].lump[--rotation] == -1) { + sprtemp[frame].lump[rotation] = lump - firstspritelump; + sprtemp[frame].flip[rotation] = (byte) (flipped ? 1 : 0); + sprtemp[frame].rotate = 1; // jff 4/24/98 only change if rot + // used + } + } + + /** + * R_InitSprites Called at program start. + * + */ + @Override + public void InitSprites(String[] namelist) { + InitSpriteDefs(namelist); + } + + protected final int SpriteNameHash(String ss) { + return ss.substring(0, 4).hashCode(); + } + + // GETTERS + @Override + public final int getFirstSpriteLump() { + return firstspritelump; + } + + @Override + public final int getNumSprites() { + return numsprites; + } + + @Override + public final spritedef_t[] getSprites() { + return sprites; + } + + @Override + public final spritedef_t getSprite(int index) { + return sprites[index]; + } + + @Override + public final int[] getSpriteWidth() { + return spritewidth; + } + + @Override + public final int[] getSpriteOffset() { + return spriteoffset; + } + + @Override + public final int[] getSpriteTopOffset() { + return spritetopoffset; + } + + @Override + public final int getSpriteWidth(int index) { + return spritewidth[index]; + } + + @Override + public final int getSpriteOffset(int index) { + return spriteoffset[index]; + } + + @Override + public final int getSpriteTopOffset(int index) { + return spritetopoffset[index]; + } + + // Some unused shit + /* + * R_InstallSpriteLump Local function for R_InitSprites. + * + * Older function, closer to linuxdoom. Using Boom-derived one instead. + */ + /* + protected final void InstallSpriteLump(int lump, int frame, + int rotation, boolean flipped) { + + // System.out.println("Trying to install "+spritename+" Frame "+ + // (char)('A'+frame)+" rot "+(rotation) + // +" . Should have rotations: "+sprtemp[frame].rotate); + int r; + + if (frame >= 29 || rotation > 8) + I.Error("R_InstallSpriteLump: Bad frame characters in lump %i", + lump); + + if ((int) frame > maxframe) + maxframe = frame; + + // A rotation value of 0 means that we are either checking the first + // frame of a sprite that HAS rotations, or something that has no + // rotations at all. The value of rotate doesn't really help us + // discern here, unless set to "false" a-priori...which can't happen + // ?! + + if (rotation == 0) { + + // MAES: notice how comparisons are done with strict literals + // (true and false) which are actually defined to be 0 and 1, + // rather than assuming that true is "any nonzero value". This + // happens because rotate's value could be -1 at this point (!), + // if a series of circumstances occur. Therefore it's actually a + // "tri-state", and the comparison 0==false and + // "anything else"==true was not good enough in this case. A + // value of -1 doesn't yield either true or false here. + + // the lump should be used for all rotations + if (sprtemp[frame].rotate == 0) { + + // MAES: Explanation: we stumbled upon this lump before, and + // decided that this frame should have no more rotations, + // hence we found an error and we bomb everything. + + I.Error("R_InitSprites: Sprite %s frame %c has multiple rot=0 lump", + spritename, 'A' + frame); + } + + // This should NEVER happen! + if (sprtemp[frame].rotate == 1) { + + // MAES: This can only happen if we decided that a sprite's + // frame was already decided to have rotations, but now we + // stumble upon another occurence of "rotation 0". Or if you + // use naive true/false evaluation for .rotate ( -1 is also + // an admissible value). + + I.Error("R_InitSprites: Sprite %s frame %c has rotations and a rot=0 lump", + spritename, 'A' + frame); + } + + // Rotation is acknowledged to be totally false at this point. + sprtemp[frame].rotate = 0; + for (r = 0; r < 8; r++) { + sprtemp[frame].lump[r] = (short) (lump - firstspritelump); + sprtemp[frame].flip[r] = (byte) (flipped ? 1 : 0); + } + return; + } + + // the lump is only used for one rotation + if (sprtemp[frame].rotate == 0) + I.Error("R_InitSprites: Sprite %s frame %c has rotations and a rot=0 lump", + spritename, 'A' + frame); + + sprtemp[frame].rotate = 1; + + // make 0 based + rotation--; + if (sprtemp[frame].lump[rotation] == -1) { + // FUN FACT: with resource coalesing, this is no longer an + // error. + // I.Error + // ("R_InitSprites: Sprite %s : %c : %c has two lumps mapped to it", + // spritename, 'A'+frame, '1'+rotation); + + // Everything is OK, we can bless the temporary sprite's frame's + // rotation. + sprtemp[frame].lump[rotation] = (short) (lump - firstspritelump); + sprtemp[frame].flip[rotation] = (byte) (flipped ? 1 : 0); + sprtemp[frame].rotate = 1; // jff 4/24/98 only change if rot + // used + } + } + */ + /* + * OLDER, UNUSED VERSION + * + * R_InitSpriteDefs Pass a null terminated list of sprite names (4 chars + * exactly) to be used. Builds the sprite rotation matrixes to account + * for horizontally flipped sprites. Will report an error if the lumps + * are inconsistent. Only called at startup. + * + * Sprite lump names are 4 characters for the actor, a letter for the + * frame, and a number for the rotation. A sprite that is flippable will + * have an additional letter/number appended. The rotation character can + * be 0 to signify no rotations. + */ + + /* public void InitSpriteDefs2(String[] namelist) { + + int intname; + int frame; + int rotation; + int start; + int end; + int patched; + + if (namelist == null) + return; + numsprites = namelist.length; + + if (numsprites == 0) + return; + + sprites = new spritedef_t[numsprites]; + C2JUtils.initArrayOfObjects(sprites); + + start = firstspritelump - 1; + end = lastspritelump + 1; + + // scan all the lump names for each of the names, + // noting the highest frame letter. + // Just compare 4 characters as ints + for (int i = 0; i < numsprites; i++) { + // System.out.println("Preparing sprite "+i); + spritename = namelist[i]; + + // The original code actually set everything to "-1" + // here, including the "boolean" rotate value. The idea was + // to create a "tristate" of sorts, where -1 means a + // sprite of uncertain status. Goto InstallSpriteLumps + // for more. + for (int j = 0; j < sprtemp.length; j++) { + Arrays.fill(sprtemp[j].flip, (byte) -1); + Arrays.fill(sprtemp[j].lump, (short) -1); + // This should be INDETERMINATE at this point. + sprtemp[j].rotate = -1; + } + + maxframe = -1; + intname = name8.getIntName(namelist[i].toUpperCase()); + + // scan the lumps, + // filling in the frames for whatever is found + for (int l = start + 1; l < end; l++) { + // We HOPE it has 8 characters. + char[] cname = W.GetLumpInfo(l).name.toCharArray(); + if (cname.length == 6 || cname.length == 8) // Sprite names + // must be this + // way + + // If the check is successful, we keep looking for more + // frames + // for a particular sprite e.g. TROOAx, TROOHxHy etc. + // + if (W.GetLumpInfo(l).intname == intname) { + frame = cname[4] - 'A'; + rotation = cname[5] - '0'; + + if (DM.modifiedgame) + patched = W + .GetNumForName(W.GetLumpInfo(l).name); + else + patched = l; + + InstallSpriteLump2(patched, frame, rotation, false); + + // Second set of rotations? + if (cname.length > 6 && cname[6] != 0) { + frame = cname[6] - 'A'; + rotation = cname[7] - '0'; + InstallSpriteLump2(l, frame, rotation, true); + } + } + } + + // check the frames that were found for completeness + // This can only be -1 at this point if we didn't install + // a single frame successfuly. + // + if (maxframe == -1) { + // System.out.println("Sprite "+spritename+" has no frames!"); + getSprites()[i].numframes = 0; + // We move on to the next sprite with this one. + continue; + } + + maxframe++; + + for (frame = 0; frame < maxframe; frame++) { + switch ((int) sprtemp[frame].rotate) { + case -1: + // no rotations were found for that frame at all + I.Error("R_InitSprites: No patches found for %s frame %c", + namelist[i], frame + 'A'); + break; + + case 0: + // only the first rotation is needed + break; + + case 1: + // must have all 8 frames + for (rotation = 0; rotation < 8; rotation++) + if (sprtemp[frame].lump[rotation] == -1) + I.Error("R_InitSprites: Sprite %s frame %c is missing rotations", + namelist[i], frame + 'A'); + break; + } + } + + // allocate space for the frames present and copy sprtemp to it + // MAES: we can do that elegantly in one line. + + sprites[i].copy(sprtemp, maxframe); + + // sprites[i].numframes = maxframe; + // sprites[i].spriteframes = new spriteframe_t[maxframe]; + // C2JUtils.initArrayOfObjects(sprites[i].spriteframes,spriteframe_t.class); + + // for (int j=0;j<) + // System.arraycopy(src, srcPos, dest, destPos, length) + // memcpy (sprites[i].spriteframes, sprtemp, + // maxframe*sizeof(spriteframe_t)); + } + + } + */ +} \ No newline at end of file diff --git a/doom/src/rr/TextureManager.java b/doom/src/rr/TextureManager.java new file mode 100644 index 0000000..6a90365 --- /dev/null +++ b/doom/src/rr/TextureManager.java @@ -0,0 +1,101 @@ +package rr; + +import doom.SourceCode.R_Data; +import static doom.SourceCode.R_Data.R_PrecacheLevel; +import java.io.IOException; +import rr.parallel.IGetSmpColumn; + +/** All texture, flat and sprite management operations should be handled + * by an implementing class. As of now, the renderer does both, though it's + * not really the most ideal. + * + * @author Velktron + * + */ +public interface TextureManager extends IGetColumn, IGetCachedColumn, IGetSmpColumn { + + public final static String[] texturelumps = {"TEXTURE1", "TEXTURE2"}; + public final static int NUMTEXLUMPS = texturelumps.length; + public final static int TEXTURE1 = 0; + public final static int TEXTURE2 = 1; + + int TextureNumForName(String texname); + + /**The "num" expected here is the internal flat number, + * not the absolute lump number. So impement accordingly. + * + * @param flatname + * @return + */ + int FlatNumForName(String flatname); + + @R_Data.C(R_PrecacheLevel) + void PrecacheLevel() throws IOException; + + void GenerateComposite(int tex); + + int getTextureheight(int texnum); + + int getTextureTranslation(int texnum); + + int getFlatTranslation(int flatnum); + + void setTextureTranslation(int texnum, int amount); + + void setFlatTranslation(int flatnum, int amount); + + int CheckTextureNumForName(String texnamem); + + String CheckTextureNameForNum(int texnum); + + int getTexturewidthmask(int tex); + + int getTextureColumnLump(int tex, int col); + + char getTextureColumnOfs(int tex, int col); + + T[] getTextureComposite(int tex); + + T getTextureComposite(int tex, int col); + + void InitFlats(); + + void InitTextures() throws IOException; + + //int getFirstFlat(); + int getSkyTextureMid(); + + int getSkyFlatNum(); + + int getSkyTexture(); + + void setSkyTexture(int skytexture); + + int InitSkyMap(); + + void setSkyFlatNum(int skyflatnum); + + void GenerateLookup(int texnum) + throws IOException; + + int getFlatLumpNum(int flatnum); + + T getRogueColumn(int lump, int column); + + patch_t getMaskedComposite(int tex); + + void GenerateMaskedComposite(int texnum); + + /** Return a "sanitized" patch. If data is insufficient, return + * a default patch or attempt a partial draw. + * + * @param patchnum + * @return + */ + public T getSafeFlat(int flatnum); + + column_t GetColumnStruct(int tex, int col); + + void setSMPVars(int nUMMASKEDTHREADS); + +} \ No newline at end of file diff --git a/doom/src/rr/UnifiedRenderer.java b/doom/src/rr/UnifiedRenderer.java new file mode 100644 index 0000000..63b533c --- /dev/null +++ b/doom/src/rr/UnifiedRenderer.java @@ -0,0 +1,266 @@ +package rr; + +import doom.DoomMain; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.drawfuns.R_DrawColumnBoom; +import rr.drawfuns.R_DrawColumnBoomLow; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.drawfuns.R_DrawFuzzColumn; +import rr.drawfuns.R_DrawFuzzColumnLow; +import rr.drawfuns.R_DrawSpan; +import rr.drawfuns.R_DrawSpanLow; +import rr.drawfuns.R_DrawTLColumn; +import rr.drawfuns.R_DrawTranslatedColumn; +import rr.drawfuns.R_DrawTranslatedColumnLow; + +public abstract class UnifiedRenderer extends RendererState { + + private static final Logger LOGGER = Loggers.getLogger(UnifiedRenderer.class.getName()); + + public UnifiedRenderer(DoomMain DOOM) { + super(DOOM); + this.MySegs = new Segs(this); + } + + /** + * A very simple Seg (Wall) drawer, which just completes abstract SegDrawer by calling the final column functions. + * + * TODO: move out of RendererState. + * + * @author velktron + * + */ + protected final class Segs + extends SegDrawer { + + public Segs(SceneRenderer R) { + super(R); + } + + /** + * For serial version, just complete the call + */ + @Override + protected final void CompleteColumn() { + colfunc.main.invoke(); + } + + } + + ////////////////// The actual rendering calls /////////////////////// + public static final class HiColor extends UnifiedRenderer { + + public HiColor(DoomMain DOOM) { + super(DOOM); + + // Init any video-output dependant stuff + // Init light levels + final int LIGHTLEVELS = colormaps.lightLevels(); + final int MAXLIGHTSCALE = colormaps.maxLightScale(); + final int MAXLIGHTZ = colormaps.maxLightZ(); + + colormaps.scalelight = new short[LIGHTLEVELS][MAXLIGHTSCALE][]; + colormaps.scalelightfixed = new short[MAXLIGHTSCALE][]; + colormaps.zlight = new short[LIGHTLEVELS][MAXLIGHTZ][]; + + completeInit(); + } + + /** + * R_InitColormaps This is VERY different for hicolor. + * + * @throws IOException + */ + @Override + protected void InitColormaps() throws IOException { + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS15 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + /** + * Initializes the various drawing functions. They are all "pegged" to the same dcvars/dsvars object. Any + * initializations of e.g. parallel renderers and their supporting subsystems should occur here. + */ + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpan.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTLColumn = new R_DrawTLColumn(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + + } + + public static final class Indexed extends UnifiedRenderer { + + public Indexed(DoomMain DOOM) { + super(DOOM); + + // Init light levels + final int LIGHTLEVELS = colormaps.lightLevels(); + final int MAXLIGHTSCALE = colormaps.maxLightScale(); + final int MAXLIGHTZ = colormaps.maxLightZ(); + + colormaps.scalelight = new byte[LIGHTLEVELS][MAXLIGHTSCALE][]; + colormaps.scalelightfixed = new byte[MAXLIGHTSCALE][]; + colormaps.zlight = new byte[LIGHTLEVELS][MAXLIGHTZ][]; + + completeInit(); + } + + /** + * R_InitColormaps + * + * @throws IOException + */ + @Override + protected void InitColormaps() throws IOException { + // Load in the light tables, + // 256 byte align tables. + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + // MAES: blurry effect is hardcoded to this colormap. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + // colormaps = (byte *)( ((int)colormaps + 255)&~0xff); + } + + /** + * Initializes the various drawing functions. They are all "pegged" to the same dcvars/dsvars object. Any + * initializations of e.g. parallel renderers and their supporting subsystems should occur here. + */ + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpan.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + //DrawTLColumn=new R_DrawTLColumn(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + super.R_InitDrawingFunctions(); + } + + } + + public static final class TrueColor extends UnifiedRenderer { + + public TrueColor(DoomMain DOOM) { + super(DOOM); + + // Init light levels + final int LIGHTLEVELS = colormaps.lightLevels(); + final int MAXLIGHTSCALE = colormaps.maxLightScale(); + final int MAXLIGHTZ = colormaps.maxLightZ(); + + colormaps.scalelight = new int[LIGHTLEVELS][MAXLIGHTSCALE][]; + colormaps.scalelightfixed = new int[MAXLIGHTSCALE][]; + colormaps.zlight = new int[LIGHTLEVELS][MAXLIGHTZ][]; + + completeInit(); + } + + /** + * R_InitColormaps This is VERY different for hicolor. + * + * @throws IOException + */ + protected void InitColormaps() throws IOException { + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS32 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + /** + * Initializes the various drawing functions. They are all "pegged" to the same dcvars/dsvars object. Any + * initializations of e.g. parallel renderers and their supporting subsystems should occur here. + */ + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpan.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + //DrawTLColumn=new R_DrawTLColumn.TrueColor(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + super.R_InitDrawingFunctions(); + } + + } +} \ No newline at end of file diff --git a/doom/src/rr/ViewVars.java b/doom/src/rr/ViewVars.java new file mode 100644 index 0000000..d55614c --- /dev/null +++ b/doom/src/rr/ViewVars.java @@ -0,0 +1,152 @@ +package rr; + +import static data.Tables.ANG180; +import static data.Tables.ANG270; +import static data.Tables.ANG90; +import static data.Tables.SlopeDiv; +import static data.Tables.tantoangle; +import doom.player_t; +import utils.C2JUtils; +import v.scale.VideoScale; + +public class ViewVars { + + public ViewVars(VideoScale vs) { + negonearray = new short[vs.getScreenWidth()]; // MAES: in scaling + screenheightarray = new short[vs.getScreenWidth()];// MAES: in scaling + xtoviewangle = new long[vs.getScreenWidth() + 1]; + C2JUtils.memset(negonearray, (short) -1, negonearray.length); + } + + // Found in draw_c. Only ever used in renderer. + public int windowx; + public int windowy; + public int width; + public int height; + + // MAES: outsiders have no business peeking into this. + // Or...well..maybe they do. It's only used to center the "pause" X + // position. + // TODO: get rid of this? + public int scaledwidth; + public int centerx; + public int centery; + + /** Used to determine the view center and projection in view units fixed_t */ + public int centerxfrac, centeryfrac, projection; + + /** fixed_t */ + public int x, y, z; + + // MAES: an exception to strict type safety. These are used only in here, + // anyway (?) and have no special functions. + // Plus I must use them as indexes. angle_t + public long angle; + + /** fixed */ + public int cos, sin; + + public player_t player; + + /** Heretic/freeview stuff? */ + public int lookdir; + + // 0 = high, 1 = low. Normally only the menu and the interface can change + // that. + public int detailshift; + + public int WEAPONADJUST; + public int BOBADJUST; + + /** + * constant arrays used for psprite clipping and initializing clipping + */ + public final short[] negonearray; // MAES: in scaling + public short[] screenheightarray;// MAES: in scaling + + /** Mirrors the one in renderer... */ + public long[] xtoviewangle; + + public final long PointToAngle(int x, int y) { + // MAES: note how we don't use &BITS32 here. That is because + // we know that the maximum possible value of tantoangle is angle + // This way, we are actually working with vectors emanating + // from our current position. + x -= this.x; + y -= this.y; + + if ((x == 0) && (y == 0)) { + return 0; + } + + if (x >= 0) { + // x >=0 + if (y >= 0) { + // y>= 0 + + if (x > y) { + // octant 0 + return tantoangle[SlopeDiv(y, x)]; + } else { + // octant 1 + return (ANG90 - 1 - tantoangle[SlopeDiv(x, y)]); + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 8 + return (-tantoangle[SlopeDiv(y, x)]); + } else { + // octant 7 + return (ANG270 + tantoangle[SlopeDiv(x, y)]); + } + } + } else { + // x<0 + x = -x; + + if (y >= 0) { + // y>= 0 + if (x > y) { + // octant 3 + return (ANG180 - 1 - tantoangle[SlopeDiv(y, x)]); + } else { + // octant 2 + return (ANG90 + tantoangle[SlopeDiv(x, y)]); + } + } else { + // y<0 + y = -y; + + if (x > y) { + // octant 4 + return (ANG180 + tantoangle[SlopeDiv(y, x)]); + } else { + // octant 5 + return (ANG270 - 1 - tantoangle[SlopeDiv(x, y)]); + } + } + } + // This is actually unreachable. + // return 0; + } + + public final int getViewWindowX() { + return windowx; + } + + public final int getViewWindowY() { + return windowy; + } + + public final int getScaledViewWidth() { + return scaledwidth; + } + + public final int getScaledViewHeight() { + return height; + } + +} \ No newline at end of file diff --git a/doom/src/rr/VisSprites.java b/doom/src/rr/VisSprites.java new file mode 100644 index 0000000..3dfa054 --- /dev/null +++ b/doom/src/rr/VisSprites.java @@ -0,0 +1,322 @@ +package rr; + +import static data.Defines.FF_FRAMEMASK; +import static data.Defines.FF_FULLBRIGHT; +import static data.Limits.MAXVISSPRITES; +import static data.Tables.ANG45; +import static data.Tables.BITS32; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedDiv; +import static m.fixed_t.FixedMul; +import mochadoom.Loggers; +import p.mobj_t; +import static p.mobj_t.MF_SHADOW; +import static rr.SceneRenderer.MINZ; +import utils.C2JUtils; +import v.graphics.Palettes; + +/** Visualized sprite manager. Depends on: SpriteManager, DoomSystem, + * Colormaps, Current View. + * + * @author velktron + * + * @param + */ +public final class VisSprites + implements IVisSpriteManagement { + + private static final Logger LOGGER = Loggers.getLogger(VisSprites.class.getName()); + + private final static boolean DEBUG = false; + + private final static boolean RANGECHECK = false; + + protected final RendererState rendererState; + + public VisSprites(RendererState rendererState) { + vissprite_t tmp = new vissprite_t<>(); + vissprites = C2JUtils.createArrayOfObjects(tmp, MAXVISSPRITES); + this.rendererState = rendererState; + } + + protected vissprite_t[] vissprites; + + protected int vissprite_p; + + protected int newvissprite; + + // UNUSED + // private final vissprite_t unsorted; + // private final vissprite_t vsprsortedhead; + // Cache those you get from the sprite manager + protected int[] spritewidth, spriteoffset, spritetopoffset; + + /** + * R_AddSprites During BSP traversal, this adds sprites by sector. + */ + @Override + public void AddSprites(sector_t sec) { + if (DEBUG) { + LOGGER.log(Level.FINER, "AddSprites"); + } + mobj_t thing; + int lightnum; + + // BSP is traversed by subsector. + // A sector might have been split into several + // subsectors during BSP building. + // Thus we check whether its already added. + if (sec.validcount == rendererState.getValidCount()) { + return; + } + + // Well, now it will be done. + sec.validcount = rendererState.getValidCount(); + + lightnum = (sec.lightlevel >> rendererState.colormaps.lightSegShift()) + rendererState.colormaps.extralight; + + if (lightnum < 0) { + rendererState.colormaps.spritelights = rendererState.colormaps.scalelight[0]; + } else if (lightnum >= rendererState.colormaps.lightLevels()) { + rendererState.colormaps.spritelights = rendererState.colormaps.scalelight[rendererState.colormaps.lightLevels() - 1]; + } else { + rendererState.colormaps.spritelights = rendererState.colormaps.scalelight[lightnum]; + } + + // Handle all things in sector. + for (thing = sec.thinglist; thing != null; thing = (mobj_t) thing.snext) { + ProjectSprite(thing); + } + } + + /** + * R_ProjectSprite Generates a vissprite for a thing if it might be visible. + * + * @param thing + */ + protected final void ProjectSprite(mobj_t thing) { + int tr_x, tr_y; + int gxt, gyt; + int tx, tz; + + int xscale, x1, x2; + + spritedef_t sprdef; + spriteframe_t sprframe; + int lump; + + int rot; + boolean flip; + + int index; + + vissprite_t vis; + + long ang; + int iscale; + + // transform the origin point + tr_x = thing.x - rendererState.view.x; + tr_y = thing.y - rendererState.view.y; + + gxt = FixedMul(tr_x, rendererState.view.cos); + gyt = -FixedMul(tr_y, rendererState.view.sin); + + tz = gxt - gyt; + + // thing is behind view plane? + if (tz < MINZ) { + return; + } + /* MAES: so projection/tz gives horizontal scale */ + xscale = FixedDiv(rendererState.view.projection, tz); + + gxt = -FixedMul(tr_x, rendererState.view.sin); + gyt = FixedMul(tr_y, rendererState.view.cos); + tx = -(gyt + gxt); + + // too far off the side? + if (Math.abs(tx) > (tz << 2)) { + return; + } + + // decide which patch to use for sprite relative to player + if (RANGECHECK) { + if (thing.mobj_sprite.ordinal() >= rendererState.DOOM.spriteManager.getNumSprites()) { + rendererState.DOOM.doomSystem.Error("R_ProjectSprite: invalid sprite number %d ", + thing.mobj_sprite); + } + } + sprdef = rendererState.DOOM.spriteManager.getSprite(thing.mobj_sprite.ordinal()); + if (RANGECHECK) { + if ((thing.mobj_frame & FF_FRAMEMASK) >= sprdef.numframes) { + rendererState.DOOM.doomSystem.Error("R_ProjectSprite: invalid sprite frame %d : %d ", + thing.mobj_sprite, thing.mobj_frame); + } + } + sprframe = sprdef.spriteframes[thing.mobj_frame & FF_FRAMEMASK]; + + if (sprframe.rotate != 0) { + // choose a different rotation based on player view + ang = rendererState.view.PointToAngle(thing.x, thing.y); + rot = (int) ((ang - thing.angle + (ANG45 * 9) / 2) & BITS32) >>> 29; + lump = sprframe.lump[rot]; + flip = (boolean) (sprframe.flip[rot] != 0); + } else { + // use single rotation for all views + lump = sprframe.lump[0]; + flip = (boolean) (sprframe.flip[0] != 0); + } + + // calculate edges of the shape + tx -= spriteoffset[lump]; + x1 = (rendererState.view.centerxfrac + FixedMul(tx, xscale)) >> FRACBITS; + + // off the right side? + if (x1 > rendererState.view.width) { + return; + } + + tx += spritewidth[lump]; + x2 = ((rendererState.view.centerxfrac + FixedMul(tx, xscale)) >> FRACBITS) - 1; + + // off the left side + if (x2 < 0) { + return; + } + + // store information in a vissprite + vis = NewVisSprite(); + vis.mobjflags = thing.flags; + vis.scale = xscale << rendererState.view.detailshift; + vis.gx = thing.x; + vis.gy = thing.y; + vis.gz = thing.z; + vis.gzt = thing.z + spritetopoffset[lump]; + vis.texturemid = vis.gzt - rendererState.view.z; + vis.x1 = x1 < 0 ? 0 : x1; + vis.x2 = x2 >= rendererState.view.width ? rendererState.view.width - 1 : x2; + /* + * This actually determines the general sprite scale) iscale = 1/xscale, + * if this was floating point. + */ + iscale = FixedDiv(FRACUNIT, xscale); + + if (flip) { + vis.startfrac = spritewidth[lump] - 1; + vis.xiscale = -iscale; + } else { + vis.startfrac = 0; + vis.xiscale = iscale; + } + + if (vis.x1 > x1) { + vis.startfrac += vis.xiscale * (vis.x1 - x1); + } + vis.patch = lump; + + // get light level + if ((thing.flags & MF_SHADOW) != 0) { + // shadow draw + vis.colormap = null; + } else if (rendererState.colormaps.fixedcolormap != null) { + // fixed map + vis.colormap = (V) rendererState.colormaps.fixedcolormap; + // vis.pcolormap=0; + } else if ((thing.mobj_frame & FF_FULLBRIGHT) != 0) { + // full bright + vis.colormap = (V) rendererState.colormaps.colormaps[Palettes.COLORMAP_FIXED]; + // vis.pcolormap=0; + } else { + // diminished light + index = xscale >> (rendererState.colormaps.lightScaleShift() - rendererState.view.detailshift); + + if (index >= rendererState.colormaps.maxLightScale()) { + index = rendererState.colormaps.maxLightScale() - 1; + } + + vis.colormap = rendererState.colormaps.spritelights[index]; + // vis.pcolormap=index; + } + } + + /** + * R_NewVisSprite Returns either a "new" sprite (actually, reuses a pool), + * or a special "overflow sprite" which just gets overwritten with bogus + * data. It's a bit of dumb thing to do, since the overflow sprite is never + * rendered but we have to copy data over it anyway. Would make more sense + * to check for it specifically and avoiding copying data, which should be + * more time consuming. Fixed by making this fully limit-removing. + * + * @return + */ + protected final vissprite_t NewVisSprite() { + if (vissprite_p == (vissprites.length - 1)) { + ResizeSprites(); + } + // return overflowsprite; + + vissprite_p++; + return vissprites[vissprite_p - 1]; + } + + @Override + public void cacheSpriteManager(ISpriteManager SM) { + this.spritewidth = SM.getSpriteWidth(); + this.spriteoffset = SM.getSpriteOffset(); + this.spritetopoffset = SM.getSpriteTopOffset(); + } + + /** + * R_ClearSprites Called at frame start. + */ + @Override + public void ClearSprites() { + // vissprite_p = vissprites; + vissprite_p = 0; + } + + // UNUSED private final vissprite_t overflowsprite = new vissprite_t(); + protected final void ResizeSprites() { + vissprites + = C2JUtils.resize(vissprites[0], vissprites, vissprites.length * 2); // Bye + // bye, + // old + // vissprites. + } + + /** + * R_SortVisSprites UNUSED more efficient Comparable sorting + built-in + * Arrays.sort function used. + */ + @Override + public final void SortVisSprites() { + Arrays.sort(vissprites, 0, vissprite_p); + + // Maes: got rid of old vissprite sorting code. Java's is better + // Hell, almost anything was better than that. + } + + @Override + public int getNumVisSprites() { + return vissprite_p; + } + + @Override + public vissprite_t[] getVisSprites() { + return vissprites; + } + + public void resetLimits() { + vissprite_t[] tmp + = C2JUtils.createArrayOfObjects(vissprites[0], MAXVISSPRITES); + System.arraycopy(vissprites, 0, tmp, 0, MAXVISSPRITES); + + // Now, that was quite a haircut!. + vissprites = tmp; + } +} \ No newline at end of file diff --git a/doom/src/rr/Visplanes.java b/doom/src/rr/Visplanes.java new file mode 100644 index 0000000..725d63f --- /dev/null +++ b/doom/src/rr/Visplanes.java @@ -0,0 +1,392 @@ +package rr; + +import data.Limits; +import data.Tables; +import static data.Tables.ANG90; +import static data.Tables.finecosine; +import static data.Tables.finesine; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FixedDiv; +import mochadoom.Loggers; +import utils.C2JUtils; +import v.scale.VideoScale; + +/** Actual visplane data and methods are isolate here. + * This allows more encapsulation and some neat hacks like sharing + * visplane data among parallel renderers, without duplicating them. + */ +public class Visplanes { + + private static final Logger LOGGER = Loggers.getLogger(Visplanes.class.getName()); + + private static final boolean DEBUG2 = false; + + protected final ViewVars view; + protected final TextureManager TexMan; + protected final VideoScale vs; + + public Visplanes(VideoScale vs, ViewVars view, TextureManager TexMan) { + this.vs = vs; + this.view = view; + this.TexMan = TexMan; + MAXOPENINGS = vs.getScreenWidth() * 64; + openings = new short[MAXOPENINGS]; + BLANKCACHEDHEIGHT = new int[vs.getScreenHeight()]; + yslope = new int[vs.getScreenHeight()]; + } + + // HACK: An all zeroes array used for fast clearing of certain visplanes. + public int[] cachedheight, BLANKCACHEDHEIGHT; + + /** To treat as fixed_t */ + public int basexscale, baseyscale; + + /** To treat at fixed_t */ + protected int[] yslope; + + // initially. + public int MAXVISPLANES = Limits.MAXVISPLANES; + public int MAXOPENINGS; + + /** visplane_t*, treat as indexes into visplanes */ + public int lastvisplane, floorplane, ceilingplane; + public visplane_t[] visplanes = new visplane_t[MAXVISPLANES]; + + /** + * openings is supposed to show where "openings" in visplanes start and end + * e.g. due to sprites, windows etc. + */ + public short[] openings; + /** Maes: this is supposed to be a pointer inside openings */ + public int lastopening; + + protected int skyscale; + + /** + * Call only after visplanes have been properly resized for resolution. + * In case of dynamic resolution changes, the old ones should just be + * discarded, as they would be nonsensical. + */ + public void initVisplanes() { + cachedheight = new int[vs.getScreenHeight()]; + Arrays.setAll(visplanes, j -> new visplane_t()); + } + + public int getBaseXScale() { + return basexscale; + } + + public int getBaseYScale() { + return baseyscale; + } + + public int getSkyScale() { + return skyscale; + } + + public void setSkyScale(int i) { + skyscale = i; + } + + public int getLength() { + return visplanes.length; + } + + /** Return the last of visplanes, allocating a new one if needed */ + public visplane_t allocate() { + if (lastvisplane == visplanes.length) { + // visplane overflows could occur at this point. + resizeVisplanes(); + } + + return visplanes[lastvisplane++]; + } + + public final void resizeVisplanes() { + // Bye bye, old visplanes. + visplanes = C2JUtils.resize(visplanes[0], visplanes, visplanes.length * 2); + } + + /** + * R_FindPlane + * + * Checks whether a visplane with the specified height, picnum and light + * level exists among those already created. This looks like a half-assed + * attempt at reusing already existing visplanes, rather than creating new + * ones. The tricky part is understanding what happens if one DOESN'T exist. + * Called only from within R_Subsector (so while we're still trasversing + * stuff). + * + * @param height + * (fixed_t) + * @param picnum + * @param lightlevel + * @return was visplane_t*, returns index into visplanes[] + */ + public final int FindPlane(int height, int picnum, int lightlevel) { + // System.out.println("\tChecking for visplane merging..."); + int check = 0; // visplane_t* + visplane_t chk = null; + + if (picnum == TexMan.getSkyFlatNum()) { + height = 0; // all skys map together + lightlevel = 0; + } + + chk = visplanes[0]; + + // Find visplane with the desired attributes + for (check = 0; check < lastvisplane; check++) { + + chk = visplanes[check]; + if (height == chk.height && picnum == chk.picnum + && lightlevel == chk.lightlevel) { + // Found a visplane with the desired specs. + break; + } + } + + if (check < lastvisplane) { + return check; + } + + // This should return the next available visplane and resize if needed, + // no need to hack with lastvisplane++ + chk = allocate(); + // Add a visplane + chk.height = height; + chk.picnum = picnum; + chk.lightlevel = lightlevel; + chk.minx = vs.getScreenWidth(); + chk.maxx = -1; + // memset (chk.top,0xff,sizeof(chk.top)); + chk.clearTop(); + + return check; + } + + /** + * R_ClearPlanes At begining of frame. + * + */ + public void ClearPlanes() { + int angle; + + /* + * View planes are cleared at the beginning of every plane, by + * setting them "just outside" the borders of the screen (-1 and + * viewheight). + */ + // Point to #1 in visplane list? OK... ?! + lastvisplane = 0; + + // We point back to the first opening of the list openings[0], + // again. + lastopening = 0; + + // texture calculation + System.arraycopy(BLANKCACHEDHEIGHT, 0, cachedheight, 0, + BLANKCACHEDHEIGHT.length); + + // left to right mapping + // FIXME: If viewangle is ever < ANG90, you're fucked. How can this + // be prevented? + // Answer: 32-bit unsigned are supposed to roll over. You can & with + // 0xFFFFFFFFL. + angle = (int) Tables.toBAMIndex(view.angle - ANG90); + + // scale will be unit scale at vs.getScreenWidth()/2 distance + basexscale = FixedDiv(finecosine[angle], view.centerxfrac); + baseyscale = -FixedDiv(finesine[angle], view.centerxfrac); + } + + /** + * R_CheckPlane + * + * Called from within StoreWallRange + * + * Presumably decides if a visplane should be split or not? + * + */ + public int CheckPlane(int index, int start, int stop) { + + if (DEBUG2) { + LOGGER.log(Level.FINER, String.format("Checkplane %d between %d and %d", index, start, stop)); + } + + // Interval ? + int intrl; + int intrh; + + // Union? + int unionl; + int unionh; + // OK, so we check out ONE particular visplane. + visplane_t pl = visplanes[index]; + + if (DEBUG2) { + LOGGER.log(Level.FINER, String.format("Checking out plane %s", String.valueOf(pl))); + } + + int x; + + // If start is smaller than the plane's min... + // + // start minx maxx stop + // | | | | + // --------PPPPPPPPPPPPPP----------- + // + // + if (start < pl.minx) { + intrl = pl.minx; + unionl = start; + // Then we will have this: + // + // unionl intrl maxx stop + // | | | | + // --------PPPPPPPPPPPPPP----------- + // + + } else { + unionl = pl.minx; + intrl = start; + + // else we will have this: + // + // union1 intrl maxx stop + // | | | | + // --------PPPPPPPPPPPPPP----------- + // + // unionl comes before intrl in any case. + // + // + } + + // Same as before, for for stop and maxx. + // This time, intrh comes before unionh. + // + if (stop > pl.maxx) { + intrh = pl.maxx; + unionh = stop; + } else { + unionh = pl.maxx; + intrh = stop; + } + + // An interval is now defined, which is entirely contained in the + // visplane. + // + // If the value FF is NOT stored ANYWWHERE inside it, we bail out + // early + for (x = intrl; x <= intrh; x++) { + if (pl.getTop(x) != Character.MAX_VALUE) { + break; + } + } + + // This can only occur if the loop above completes, + // else the visplane we were checking has non-visible/clipped + // portions within that range: we must split. + if (x > intrh) { + // Merge the visplane + pl.minx = unionl; + pl.maxx = unionh; + // System.out.println("Plane modified as follows "+pl); + // use the same one + return index; + } + + // SPLIT: make a new visplane at "last" position, copying materials + // and light. + visplane_t last = allocate(); + last.height = pl.height; + last.picnum = pl.picnum; + last.lightlevel = pl.lightlevel; + + pl = last; + pl.minx = start; + pl.maxx = stop; + + // memset (pl.top,0xff,sizeof(pl.top)); + pl.clearTop(); + + // return pl; + // System.out.println("New plane created: "+pl); + return lastvisplane - 1; + } + + /* + + /** + * A hashtable used to retrieve planes with particular attributes faster + * -hopefully-. The planes are still stored in the visplane array for + * convenience, but we can search them in the hashtable too -as a bonus, we + * can reuse previously created planes that match newer ones-. + */ + /* + Hashtable planehash = new Hashtable( + 128); + visplane_t check = new visplane_t(); + */ + + /* + protected final int FindPlane2(int height, int picnum, int lightlevel) { + // System.out.println("\tChecking for visplane merging..."); + // int check=0; // visplane_t* + visplane_t chk = null; + Integer checknum; + + if (picnum == TexMan.getSkyFlatNum()) { + height = 0; // all skys map together + lightlevel = 0; + } + + // Try and find this. + check.lightlevel = lightlevel; + check.picnum = picnum; + check.height = height; + check.updateHashCode(); + checknum = planehash.get(check); + + // Something found, get it. + + if (!(checknum == null)) { + + // Visplane exists and is within those allocated in the current tic. + if (checknum < lastvisplane) { + return checknum; + } + + // Found a visplane, but we can't add anymore. + // Resize right away. This shouldn't take too long. + if (lastvisplane == MAXVISPLANES) { + // I.Error ("R_FindPlane: no more visplanes"); + ResizeVisplanes(); + } + } + + // We found a visplane (possibly one allocated on a previous tic) + // but we can't link directly to it, we need to copy its data + // around. + + checknum = new Integer(Math.max(0, lastvisplane)); + + chk = visplanes[checknum]; + // Add a visplane + lastvisplane++; + chk.height = height; + chk.picnum = picnum; + chk.lightlevel = lightlevel; + chk.minx = vs.getScreenWidth(); + chk.maxx = -1; + chk.updateHashCode(); + planehash.put(chk, checknum); + // memset (chk.top,0xff,sizeof(chk.top)); + chk.clearTop(); + + return checknum; + } + */ +} \ No newline at end of file diff --git a/doom/src/rr/base_ratio_t.java b/doom/src/rr/base_ratio_t.java new file mode 100644 index 0000000..7ff8d2a --- /dev/null +++ b/doom/src/rr/base_ratio_t.java @@ -0,0 +1,22 @@ +package rr; + +public class base_ratio_t { + + public base_ratio_t(int base_width, int base_height, int psprite_offset, + int multiplier, float gl_ratio) { + this.base_width = base_width; + this.base_height = base_height; + this.psprite_offset = psprite_offset; + this.multiplier = multiplier; + this.gl_ratio = (float) (RMUL * gl_ratio); + } + + public int base_width; // Base width (unused) + public int base_height; // Base height (used for wall visibility multiplier) + public int psprite_offset; // Psprite offset (needed for "tallscreen" modes) + public int multiplier; // Width or height multiplier + public float gl_ratio; + + public static final double RMUL = 1.6d / 1.333333d; + +} \ No newline at end of file diff --git a/doom/src/rr/cliprange_t.java b/doom/src/rr/cliprange_t.java new file mode 100644 index 0000000..c0aff9f --- /dev/null +++ b/doom/src/rr/cliprange_t.java @@ -0,0 +1,21 @@ +package rr; + +public class cliprange_t { + + public cliprange_t(int first, int last) { + this.first = first; + this.last = last; + } + + public cliprange_t() { + + } + + public int first; + public int last; + + public void copy(cliprange_t from) { + this.first = from.first; + this.last = from.last; + } +} \ No newline at end of file diff --git a/doom/src/rr/column_t.java b/doom/src/rr/column_t.java new file mode 100644 index 0000000..6f9325d --- /dev/null +++ b/doom/src/rr/column_t.java @@ -0,0 +1,154 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import utils.C2JUtils; +import w.CacheableDoomObject; + +/** column_t is a list of 0 or more post_t, (byte)-1 terminated + * typedef post_t column_t; + * For the sake of efficiency, "column_t" will store raw data, however I added + * some stuff to make my life easier. + * + */ +public class column_t implements CacheableDoomObject { + + /** Static buffers used during I/O. + * There's ABSO-FUCKING-LUTELY no reason to manipulate them externally!!! + * I'M NOT KIDDING!!!11!! + */ + private static final int[] guesspostofs = new int[256]; + private static final short[] guesspostlens = new short[256]; + private static final short[] guesspostdeltas = new short[256]; + + // MAES: there are useless, since the renderer is using raw byte data anyway, and the per-post + // data is available in the special arrays. + // public short topdelta; // -1 is the last post in a column (actually 0xFF, since this was unsigned???) + // public short length; // length data bytes follows (actually add +2) + //public column_t[] posts; // This is quite tricky to read. + /** The RAW data (includes initial header and padding, because no post gets preferential treatment). */ + public byte[] data; + /** Actual number of posts inside this column. All guesswork is done while loading */ + public int posts; + /** Positions of posts inside the raw data (point at headers) */ + public int[] postofs; + /** Posts lengths, intended as actual drawable pixels. Add +4 to get the whole post length */ + public short[] postlen; + /** Vertical offset of each post. In theory it should be possible to quickly + * clip to the next visible post when drawing a column */ + public short[] postdeltas; + + @Override + public void unpack(ByteBuffer buf) throws IOException { + // Mark current position. + buf.mark(); + int skipped = 0; + short postlen = 0; + int colheight = 0; + int len = 0; // How long is the WHOLE column, until the final FF? + int postno = 0; // Actual number of posts. + int topdelta = 0; + int prevdelta = -1; // HACK for DeepSea tall patches. + + // Scan every byte until we encounter an 0xFF which definitively marks the end of a column. + while ((topdelta = C2JUtils.toUnsignedByte(buf.get())) != 0xFF) { + + // From the wiki: + // A column's topdelta is compared to the previous column's topdelta + // (or to -1 if there is no previous column in this row). If the new + // topdelta is lesser than the previous, it is interpreted as a tall + // patch and the two values are added together, the sum serving as the + // current column's actual offset. + int tmp = topdelta; + + if (topdelta < prevdelta) { + topdelta += prevdelta; + } + + prevdelta = tmp; + + // First byte of a post should be its "topdelta" + guesspostdeltas[postno] = (short) topdelta; + + guesspostofs[postno] = skipped + 3; // 0 for first post + + // Read one more byte...this should be the post length. + postlen = (short) C2JUtils.toUnsignedByte(buf.get()); + guesspostlens[postno++] = (short) (postlen); + + // So, we already read 2 bytes (topdelta + length) + // Two further bytes are padding so we can safely skip 2+2+postlen bytes until the next post + skipped += 4 + postlen; + buf.position(buf.position() + 2 + postlen); + + // Obviously, this adds to the height of the column, which might not be equal to the patch that + // contains it. + colheight += postlen; + } + + // Skip final padding byte ? + skipped++; + + len = finalizeStatus(skipped, colheight, postno); + + // Go back...and read the raw data. That's what will actually be used in the renderer. + buf.reset(); + buf.get(data, 0, len); + } + + /** This -almost- completes reading, by filling in the header information + * before the raw column data is read in. + * + * @param skipped + * @param colheight + * @param postno + * @return + */ + private int finalizeStatus(int skipped, int colheight, int postno) { + int len; + // That's the TOTAL length including all padding. + // This means we redundantly read some data + len = (int) (skipped); + this.data = new byte[len]; + + this.postofs = new int[postno]; + this.postlen = new short[postno]; + // this.length=(short) colheight; + this.postdeltas = new short[postno]; + + System.arraycopy(guesspostofs, 0, this.postofs, 0, postno); + System.arraycopy(guesspostlens, 0, this.postlen, 0, postno); + System.arraycopy(guesspostdeltas, 0, this.postdeltas, 0, postno); + + this.posts = postno; + return len; + } + + /** based on raw data */ + public int getTopDelta() { + return C2JUtils.toUnsignedByte(data[0]); + } + + /** based on raw data */ + public int getLength() { + return C2JUtils.toUnsignedByte(data[1]); + } + +} + +// $Log: column_t.java,v $ +// Revision 1.18 2011/10/04 14:34:08 velktron +// Removed length and topdelta members (unused). +// +// Revision 1.17 2011/07/25 14:37:20 velktron +// Added support for DeePSea tall patches. +// +// Revision 1.16 2011/07/12 16:32:05 velktron +// 4 extra padding bytes uneeded. +// +// Revision 1.15 2011/06/23 17:01:24 velktron +// column_t no longer needs to support IReadableDoomObject, since patch_t doesn't. If you really need it back, it can be done through buffering and using unpack(), without duplicate code. Also added setData() method for synthesized columns. +// +// Revision 1.14 2011/06/08 16:11:13 velktron +// IMPORTANT: postofs now skip delta,height and padding ENTIRELY (added +3). This eliminates the need to add +3 before accessing the data, saving some CPU cycles for each column. Of course, anything using column_t must take this into account. +// \ No newline at end of file diff --git a/doom/src/rr/drawfuns/ColFuncs.java b/doom/src/rr/drawfuns/ColFuncs.java new file mode 100644 index 0000000..2a39811 --- /dev/null +++ b/doom/src/rr/drawfuns/ColFuncs.java @@ -0,0 +1,20 @@ +package rr.drawfuns; + +public class ColFuncs { + + public DoomColumnFunction main; + + public DoomColumnFunction base; + + public DoomColumnFunction masked; + + public DoomColumnFunction fuzz; + + public DoomColumnFunction trans; + + public DoomColumnFunction glass; + + public DoomColumnFunction player; + + public DoomColumnFunction sky; +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/ColVars.java b/doom/src/rr/drawfuns/ColVars.java new file mode 100644 index 0000000..2dc1e70 --- /dev/null +++ b/doom/src/rr/drawfuns/ColVars.java @@ -0,0 +1,65 @@ +package rr.drawfuns; + +/** This is all the information needed to draw a particular column. Really. + * So if we store all of this crap somewhere instead of drawing, we can do the + * drawing when it's more convenient, and since they are non-overlapping we can + * parallelize them. Any questions? + * + */ +public class ColVars { + + /** when passing dc_source around, also set this */ + public T dc_source; + public int dc_source_ofs; + + public T dc_translation; + public int viewheight; + + /** Used by functions that accept transparency or other special + * remapping tables. + * + */ + public T tranmap; + + public int centery; + public int dc_iscale; + + public int dc_texturemid; + public int dc_texheight; // Boom enhancement + public int dc_x; + public int dc_yh; + public int dc_yl; + + public int dc_flags; + + /** + * MAES: this was a typedef for unsigned bytes, called "lighttable_t". It + * makes more sense to make it generic and parametrize it to an array of + * primitives since it's performance-critical in the renderer. + * Now, whether this should be made bytes or shorts or chars or even ints + * is debatable. + */ + public V dc_colormap; + + /** Copies all BUT flags */ + public final void copyFrom(ColVars dcvars) { + this.dc_source = dcvars.dc_source; + this.dc_colormap = dcvars.dc_colormap; + this.dc_source_ofs = dcvars.dc_source_ofs; + this.viewheight = dcvars.viewheight; + this.centery = dcvars.centery; + this.dc_x = dcvars.dc_x; + this.dc_yh = dcvars.dc_yh; + this.dc_yl = dcvars.dc_yl; + this.dc_texturemid = dcvars.dc_texturemid; + this.dc_iscale = dcvars.dc_iscale; + this.dc_texheight = dcvars.dc_texheight; + } + + /** Assigns specific flags */ + public void copyFrom(ColVars dcvars, int flags) { + this.copyFrom(dcvars); + this.dc_flags = flags; + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/ColumnFunction.java b/doom/src/rr/drawfuns/ColumnFunction.java new file mode 100644 index 0000000..96bb3bd --- /dev/null +++ b/doom/src/rr/drawfuns/ColumnFunction.java @@ -0,0 +1,16 @@ +package rr.drawfuns; + +/** Either draws a column or a span + * + * @author velktron + * + */ +public interface ColumnFunction { + + public void invoke(); + + public void invoke(ColVars dcvars); + + /** A set of flags that help identifying the type of function */ + public int getFlags(); +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/DcFlags.java b/doom/src/rr/drawfuns/DcFlags.java new file mode 100644 index 0000000..161941e --- /dev/null +++ b/doom/src/rr/drawfuns/DcFlags.java @@ -0,0 +1,15 @@ +package rr.drawfuns; + +/** Flags used to mark column functions according to type, + * for quick type identification. + * + * @author velktron + * + */ +public final class DcFlags { + + public static final int FUZZY = 0x1; + public static final int TRANSLATED = 0x2; + public static final int TRANSPARENT = 0x4; + public static final int LOW_DETAIL = 0x8; +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/DoomColumnFunction.java b/doom/src/rr/drawfuns/DoomColumnFunction.java new file mode 100644 index 0000000..6f63753 --- /dev/null +++ b/doom/src/rr/drawfuns/DoomColumnFunction.java @@ -0,0 +1,85 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import v.tables.BlurryTable; + +/** Prototype for + * + * @author velktron + * + * @param + */ +public abstract class DoomColumnFunction implements ColumnFunction { + + protected final boolean RANGECHECK = false; + protected final int SCREENWIDTH; + protected final int SCREENHEIGHT; + protected ColVars dcvars; + protected final V screen; + protected final IDoomSystem I; + protected final int[] ylookup; + protected final int[] columnofs; + protected BlurryTable blurryTable; + protected int flags; + + public DoomColumnFunction(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, V screen, IDoomSystem I) { + SCREENWIDTH = sCREENWIDTH; + SCREENHEIGHT = sCREENHEIGHT; + this.ylookup = ylookup; + this.columnofs = columnofs; + this.dcvars = dcvars; + this.screen = screen; + this.I = I; + this.blurryTable = null; + } + + public DoomColumnFunction(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, V screen, IDoomSystem I, BlurryTable BLURRY_MAP) { + SCREENWIDTH = sCREENWIDTH; + SCREENHEIGHT = sCREENHEIGHT; + this.ylookup = ylookup; + this.columnofs = columnofs; + this.dcvars = dcvars; + this.screen = screen; + this.I = I; + this.blurryTable = BLURRY_MAP; + } + + protected final void performRangeCheck() { + if (dcvars.dc_x >= SCREENWIDTH || dcvars.dc_yl < 0 || dcvars.dc_yh >= SCREENHEIGHT) { + I.Error("R_DrawColumn: %d to %d at %d", dcvars.dc_yl, dcvars.dc_yh, dcvars.dc_x); + } + } + + /** + * + * Use ylookup LUT to avoid multiply with ScreenWidth. + * Use columnofs LUT for subwindows? + * + * @return Framebuffer destination address. + */ + protected final int computeScreenDest() { + return ylookup[dcvars.dc_yl] + columnofs[dcvars.dc_x]; + } + + protected final int blockyDest1() { + return ylookup[dcvars.dc_yl] + columnofs[dcvars.dc_x << 1]; + } + + protected final int blockyDest2() { + return ylookup[dcvars.dc_yl] + columnofs[(dcvars.dc_x << 1) + 1]; + } + + @Override + public final void invoke(ColVars dcvars) { + this.dcvars = dcvars; + invoke(); + } + + @Override + public final int getFlags() { + return this.flags; + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/DoomSpanFunction.java b/doom/src/rr/drawfuns/DoomSpanFunction.java new file mode 100644 index 0000000..2eca23c --- /dev/null +++ b/doom/src/rr/drawfuns/DoomSpanFunction.java @@ -0,0 +1,40 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +public abstract class DoomSpanFunction implements SpanFunction { + + protected final boolean RANGECHECK = false; + protected final int SCREENWIDTH; + protected final int SCREENHEIGHT; + protected SpanVars dsvars; + protected final int[] ylookup; + protected final int[] columnofs; + protected final V screen; + protected final IDoomSystem I; + + public DoomSpanFunction(int sCREENWIDTH, int sCREENHEIGHT, + int[] ylookup, int[] columnofs, SpanVars dsvars, V screen, IDoomSystem I) { + SCREENWIDTH = sCREENWIDTH; + SCREENHEIGHT = sCREENHEIGHT; + this.ylookup = ylookup; + this.columnofs = columnofs; + this.dsvars = dsvars; + this.screen = screen; + this.I = I; + } + + protected final void doRangeCheck() { + if (dsvars.ds_x2 < dsvars.ds_x1 || dsvars.ds_x1 < 0 || dsvars.ds_x2 >= SCREENWIDTH + || dsvars.ds_y > SCREENHEIGHT) { + I.Error("R_DrawSpan: %d to %d at %d", dsvars.ds_x1, dsvars.ds_x2, dsvars.ds_y); + } + } + + @Override + public final void invoke(SpanVars dsvars) { + this.dsvars = dsvars; + invoke(); + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumn.java b/doom/src/rr/drawfuns/R_DrawColumn.java new file mode 100644 index 0000000..c45f7e7 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumn.java @@ -0,0 +1,93 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +/** + * Adapted from Killough's Boom code. There are optimized as well as + * low-detail versions of it. + * + * @author admin + * + */ +/** + * A column is a vertical slice/span from a wall texture that, given the + * DOOM style restrictions on the view orientation, will always have + * constant z depth. Thus a special case loop for very fast rendering can be + * used. It has also been used with Wolfenstein 3D. MAES: this is called + * mostly from inside Draw and from an external "Renderer" + */ +public final class R_DrawColumn extends DoomColumnFunction { + + public R_DrawColumn(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count; + // byte* dest; + int dest; // As pointer + // fixed_t + int frac, fracstep; + byte colmask = 127; + count = dcvars.dc_yh - dcvars.dc_yl; + // How much we should draw + // count = Math.min(dc_yh - dc_yl,dc_source.length-dc_source_ofs-1); + // colmask = (byte) Math.min(dc_source.length-dc_source_ofs-1,127); + + // Zero length, column does not exceed a pixel. + if (count <= 0) { + return; + } + + if (RANGECHECK) { + if (dcvars.dc_x >= SCREENWIDTH || dcvars.dc_yl < 0 || dcvars.dc_yh >= SCREENHEIGHT) { + I.Error("R_DrawColumn: %d to %d at %d", dcvars.dc_yl, dcvars.dc_yh, dcvars.dc_x); + } + } + + // Trying to draw a masked column? Then something gross will happen. + /* + * if (count>=dc_source.length-dc_source_ofs) { int + * diff=count-(dc_source.length-dc_source_ofs); + * count=dc_source.length-dc_source_ofs-1; dc_source_ofs=0; + * //dc_yl=dc_yh-count; gross=true; } + */ + dest = computeScreenDest(); + + // Determine scaling, + // which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. + do { + /* + * Re-map color indices from wall texture column using a + * lighting/special effects LUT. + * Q: Where is "*dest"supposed to be pointing? + * A: it's pointing inside screen[0] (set long before we came here) + * dc_source is a pointer to a decompressed column's data. + * Oh Woe if it points at non-pixel data. + */ + // if (gross) System.out.println(frac >> FRACBITS); + screen[dest] = dcvars.dc_colormap[0x00FF & dcvars.dc_source[(dcvars.dc_source_ofs + (frac >> FRACBITS)) + & colmask]]; + + /* + * MAES: ok, so we have (from inside out): + * + * frac is a fixed-point number representing a pointer inside a + * column. It gets shifted to an integer, and AND-ed with 128 + * (this causes vertical column tiling). + */ + dest += SCREENWIDTH; + frac += fracstep; + + } while (count-- > 0); + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnBoom.java b/doom/src/rr/drawfuns/R_DrawColumnBoom.java new file mode 100644 index 0000000..01f6ff9 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnBoom.java @@ -0,0 +1,381 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import mochadoom.Loggers; + +/** + * Adapted from Killough's Boom code. There are optimized as well as low-detail + * versions of it. + * + * @author admin + */ +public abstract class R_DrawColumnBoom + extends DoomColumnFunction { + + private static final Logger LOGGER = Loggers.getLogger(R_DrawColumnBoom.class.getName()); + + public R_DrawColumnBoom(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, V screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public static final class HiColor + extends R_DrawColumnBoom { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killoughdcvars + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + try { + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + } catch (Exception e) { + LOGGER.log(Level.WARNING, + String.format("%s %s %x %x %x", colormap, + source, dc_source_ofs, frac, heightmask)); + } + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class Indexed + extends R_DrawColumnBoom { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, byte[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final byte[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killoughdcvars + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + try { + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + } catch (Exception e) { + LOGGER.log(Level.WARNING, + String.format("%s %s %x %x %x\n", colormap, + source, dc_source_ofs, frac, heightmask)); + } + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class TrueColor + extends R_DrawColumnBoom { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, int[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final int[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killoughdcvars + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + try { + screen[dest] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + } catch (Exception e) { + LOGGER.log(Level.WARNING, + String.format("%s %s %x %x %x\n", colormap, + source, dc_source_ofs, frac, heightmask)); + } + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnBoomLow.java b/doom/src/rr/drawfuns/R_DrawColumnBoomLow.java new file mode 100644 index 0000000..02140a0 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnBoomLow.java @@ -0,0 +1,413 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +/** + * Adapted from Killough's Boom code. Low-detail variation, no DC_SOURCE + * optimization. + * + * @author admin + */ +public abstract class R_DrawColumnBoomLow + extends DoomColumnFunction { + + public R_DrawColumnBoomLow(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, V screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.LOW_DETAIL; + } + + public static final class HiColor + extends R_DrawColumnBoomLow { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep, dc_source_ofs; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + dc_source_ofs = dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class Indexed + extends R_DrawColumnBoomLow { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, byte[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep, dc_source_ofs; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + dc_source_ofs = dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final byte[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + + } + + public static final class TrueColor + extends R_DrawColumnBoomLow { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + int[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep, dc_source_ofs; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + dc_source_ofs = dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final int[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = screen[dest2] + = colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnBoomOpt.java b/doom/src/rr/drawfuns/R_DrawColumnBoomOpt.java new file mode 100644 index 0000000..9668d83 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnBoomOpt.java @@ -0,0 +1,354 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +/** + * Adapted from Killough's Boom code. Specially optimized version assuming that + * dc_source_ofs is always 0. This eliminates it from expressions. + * + * @author admin + */ +public abstract class R_DrawColumnBoomOpt + extends DoomColumnFunction { + + public R_DrawColumnBoomOpt(int sCREENWIDTH, int sCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, V screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public static final class HiColor + extends R_DrawColumnBoomOpt { + + public HiColor(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class Indexed + extends R_DrawColumnBoomOpt { + + public Indexed(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, byte[] screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final byte[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class TrueColor + extends R_DrawColumnBoomOpt { + + public TrueColor(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, int[] screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final int[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 + // -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] + = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnBoomOptLow.java b/doom/src/rr/drawfuns/R_DrawColumnBoomOptLow.java new file mode 100644 index 0000000..5b6d459 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnBoomOptLow.java @@ -0,0 +1,349 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +/** + * Adapted from Killough's Boom code. Low-detail variation, with DC SOURCE + * optimization. + * + * @author admin + * + */ +public abstract class R_DrawColumnBoomOptLow extends DoomColumnFunction { + + public R_DrawColumnBoomOptLow(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + V screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.LOW_DETAIL; + } + + public static final class HiColor extends R_DrawColumnBoomOptLow { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + // Assumed to be always zero for optimized draws. + //dc_source_ofs=dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class Indexed extends R_DrawColumnBoomOptLow { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + byte[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + // Assumed to be always zero for optimized draws. + //dc_source_ofs=dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final byte[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + + public static final class TrueColor extends R_DrawColumnBoomOptLow { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + int[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count; + int dest, dest2; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + // Assumed to be always zero for optimized draws. + //dc_source_ofs=dcvars.dc_source_ofs; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final int[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS))]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] = screen[dest2] = colormap[0x00FF & source[((frac >> FRACBITS) & heightmask)]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnBoomSuperOpt.java b/doom/src/rr/drawfuns/R_DrawColumnBoomSuperOpt.java new file mode 100644 index 0000000..a1a3d04 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnBoomSuperOpt.java @@ -0,0 +1,113 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +/** + * Adapted from Killough's Boom code. Specially super-optimized version assuming + * that dc_source_ofs is always 0, AND that frac>>FRACBITS can be eliminated by + * doing fracstep>>FRACBITS a-priori. Experimental/untested. + * + * @author admin + * + */ +public final class R_DrawColumnBoomSuperOpt extends DoomColumnFunction { + + public R_DrawColumnBoomSuperOpt(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale >> FRACBITS; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + frac >>= FRACBITS; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + + // heightmask is the Tutti-Frutti fix -- killough + screen[dest] = colormap[0x00FF & source[frac]]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while (count >= 4) // texture height is a power of 2 -- + // killough + { + // System.err.println(dest); + screen[dest] = colormap[0x00FF & source[frac & heightmask]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = colormap[0x00FF & source[frac & heightmask]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = colormap[0x00FF & source[frac & heightmask]]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = colormap[0x00FF & source[frac & heightmask]]; + dest += SCREENWIDTH; + frac += fracstep; + count -= 4; + } + + while (count > 0) { + screen[dest] = colormap[0x00FF & source[frac & heightmask]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnLow.java b/doom/src/rr/drawfuns/R_DrawColumnLow.java new file mode 100644 index 0000000..cc6ca3d --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnLow.java @@ -0,0 +1,56 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +public final class R_DrawColumnLow extends DoomColumnFunction { + + public R_DrawColumnLow(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.LOW_DETAIL; + } + + public void invoke() { + int count; + // MAES: were pointers. Of course... + int dest, dest2; + final byte[] dc_source = dcvars.dc_source; + final short[] dc_colormap = dcvars.dc_colormap; + final int dc_source_ofs = dcvars.dc_source_ofs; + // Maes: fixed_t never used as such. + int frac; + final int fracstep; + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + // int spot=(frac >>> FRACBITS) & 127; + do { + + // Hack. Does not work correctly. + // MAES: that's good to know. + screen[dest] = screen[dest2] = dc_colormap[0x00FF & dc_source[dc_source_ofs + + ((frac >>> FRACBITS) & 127)]]; + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + } while (count-- != 0); + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawColumnUnrolled.java b/doom/src/rr/drawfuns/R_DrawColumnUnrolled.java new file mode 100644 index 0000000..c7964c6 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawColumnUnrolled.java @@ -0,0 +1,86 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +/** + * EI VITTU, this gives a clean 25% boost. Da fack... + * + * + * @author admin + * + */ +public final class R_DrawColumnUnrolled extends DoomColumnFunction { + + /* + * That's shit, doesn't help. private final int + * SCREENWIDTH2=SCREENWIDTH*2; private final int + * SCREENWIDTH3=SCREENWIDTH*3; private final int + * SCREENWIDTH4=SCREENWIDTH*4; private final int + * SCREENWIDTH5=SCREENWIDTH*5; private final int + * SCREENWIDTH6=SCREENWIDTH*6; private final int + * SCREENWIDTH7=SCREENWIDTH*7; private final int + * SCREENWIDTH8=SCREENWIDTH*8; + */ + public R_DrawColumnUnrolled(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + } + + public void invoke() { + int count, dest; + final byte[] source; + final short[] colormap; + final int dc_source_ofs = dcvars.dc_source_ofs; + + // These are all "unsigned". Watch out for bit shifts! + int frac; + final int fracstep, fracstep2, fracstep3, fracstep4; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + source = dcvars.dc_source; + // dc_source_ofs+=15; // ???? WHY + colormap = dcvars.dc_colormap; + dest = computeScreenDest(); + + fracstep = dcvars.dc_iscale << 9; + frac = (dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * dcvars.dc_iscale) << 9; + + fracstep2 = fracstep + fracstep; + fracstep3 = fracstep2 + fracstep; + fracstep4 = fracstep3 + fracstep; + + while (count > 8) { + screen[dest] = colormap[0x00FF & source[dc_source_ofs + frac >>> 25]]; + screen[dest + SCREENWIDTH] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep) >>> 25]]; + screen[dest + SCREENWIDTH * 2] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep2) >>> 25]]; + screen[dest + SCREENWIDTH * 3] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep3) >>> 25]]; + + frac += fracstep4; + + screen[dest + SCREENWIDTH * 4] = colormap[0x00FF & source[dc_source_ofs + + frac >>> 25]]; + screen[dest + SCREENWIDTH * 5] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep) >>> 25]]; + screen[dest + SCREENWIDTH * 6] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep2) >>> 25]]; + screen[dest + SCREENWIDTH * 7] = colormap[0x00FF & source[dc_source_ofs + + (frac + fracstep3) >>> 25]]; + + frac += fracstep4; + dest += SCREENWIDTH * 8; + count -= 8; + } + + while (count > 0) { + screen[dest] = colormap[0x00FF & source[dc_source_ofs + frac >>> 25]]; + dest += SCREENWIDTH; + frac += fracstep; + count--; + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawFuzzColumn.java b/doom/src/rr/drawfuns/R_DrawFuzzColumn.java new file mode 100644 index 0000000..312aaf0 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawFuzzColumn.java @@ -0,0 +1,362 @@ +/*----------------------------------------------------------------------------- +// +// Copyright (C) 1993-1996 Id Software, Inc. +// Copyright (C) 2017 Good Sign +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// From r_draw.c +//-----------------------------------------------------------------------------*/ +package rr.drawfuns; + +import i.IDoomSystem; +import v.tables.BlurryTable; + +/** + * fuzzMix was preserved, but moved to its own interface. + * Implemented by BlurryTable if cfg option fuzz_mix is set + * - Good Sign 2017/04/16 + * + * Framebuffer postprocessing. Creates a fuzzy image by copying pixels from + * adjacent ones to left and right. Used with an all black colormap, this + * could create the SHADOW effect, i.e. spectres and invisible players. + */ +public abstract class R_DrawFuzzColumn extends DoomColumnFunction { + + public R_DrawFuzzColumn( + int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + V screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + this(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.blurryTable = BLURRY_MAP; + } + + public R_DrawFuzzColumn( + int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + V screen, IDoomSystem I + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.FUZZY; + + FUZZOFF = SCREENWIDTH; + + // Recompute fuzz table + fuzzoffset = new int[]{FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + -FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, + -FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF}; + + FUZZTABLE = fuzzoffset.length; + } + + protected int fuzzpos; + protected final int FUZZTABLE; + + // + // Spectre/Invisibility. + // + protected final int FUZZOFF; + protected final int[] fuzzoffset; + + public static final class Indexed extends R_DrawFuzzColumn { + + public Indexed( + int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + byte[] screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + } + + @Override + public void invoke() { + int count; + int dest; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Does not work with blocky mode. + dest = computeScreenDest(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) {// MAES: unroll by 4 + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + } while (count-- > 0); + } + } + } + + public static final class HiColor extends R_DrawFuzzColumn { + + public HiColor( + int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + // TODO Auto-generated constructor stub + } + + @Override + public void invoke() { + int count; + int dest; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // Does not work with blocky mode. + dest = computeScreenDest(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) {// MAES: unroll by 4 + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + } while ((count -= 4) > 4); + + if (count > 0) { + do { + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + } while (count-- > 0); + } + } + } + } + + public static final class TrueColor extends R_DrawFuzzColumn { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + int[] screen, IDoomSystem I, BlurryTable BLURRY_MAP) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + } + + @Override + public void invoke() { + int count; + int dest; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Does not work with blocky mode. + dest = computeScreenDest(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) {// MAES: unroll by 4 + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + } while (count-- > 0); + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawFuzzColumnLow.java b/doom/src/rr/drawfuns/R_DrawFuzzColumnLow.java new file mode 100644 index 0000000..962c737 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawFuzzColumnLow.java @@ -0,0 +1,390 @@ +/*----------------------------------------------------------------------------- +// +// Copyright (C) 1993-1996 Id Software, Inc. +// Copyright (C) 2017 Good Sign +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// From r_draw.c +//-----------------------------------------------------------------------------*/ +package rr.drawfuns; + +import i.IDoomSystem; +import v.tables.BlurryTable; + +/** + * fuzzMix was preserved, but moved to its own interface. + * Implemented by BlurryTable if cfg option fuzz_mix is set + * - Good Sign 2017/04/16 + * + * Low detail version. Jesus. + */ +public abstract class R_DrawFuzzColumnLow extends DoomColumnFunction { + + public R_DrawFuzzColumnLow( + int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + V screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + this(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.blurryTable = BLURRY_MAP; + } + + public R_DrawFuzzColumnLow( + int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + V screen, IDoomSystem I + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.LOW_DETAIL | DcFlags.FUZZY; + + FUZZOFF = SCREENWIDTH; + + // Recompute fuzz table + fuzzoffset = new int[]{FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, + -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, FUZZOFF, -FUZZOFF, + -FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, + FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, -FUZZOFF, + FUZZOFF, FUZZOFF, FUZZOFF, FUZZOFF, -FUZZOFF, FUZZOFF, FUZZOFF, + -FUZZOFF, FUZZOFF}; + } + + protected int fuzzpos; + + // + // Spectre/Invisibility. + // + protected final int FUZZOFF; + protected final int[] fuzzoffset; + + public static final class Indexed extends R_DrawFuzzColumn { + + public Indexed( + int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + byte[] screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + } + + @Override + public void invoke() { + int count; + int dest, dest2; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) { + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + // Ironically, "low detail" fuzziness was not really + // low-detail, + // as it normally did full-precision calculations. + // BLURRY_MAP[0x00FF & screen[dest2+ fuzzoffset[fuzzpos]]]; + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while (count-- != 0); + } + } + } + + public static final class HiColor extends R_DrawFuzzColumn { + + public HiColor( + int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + } + + @Override + public void invoke() { + int count; + int dest, dest2; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) { + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + // Ironically, "low detail" fuzziness was not really + // low-detail, + // as it normally did full-precision calculations. + // BLURRY_MAP[0x00FF & screen[dest2+ fuzzoffset[fuzzpos]]]; + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + screen[dest] = blurryTable.computePixel(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while (count-- != 0); + } + + } + } + + public static final class TrueColor extends R_DrawFuzzColumn { + + public TrueColor( + int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + int[] screen, IDoomSystem I, BlurryTable BLURRY_MAP + ) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I, BLURRY_MAP); + } + + @Override + public void invoke() { + int count; + int dest, dest2; + + // Adjust borders. Low... + if (dcvars.dc_yl == 0) { + dcvars.dc_yl = 1; + } + + // .. and high. + if (dcvars.dc_yh == dcvars.viewheight - 1) { + dcvars.dc_yh = dcvars.viewheight - 2; + } + + count = dcvars.dc_yh - dcvars.dc_yl; + + // Zero length. + if (count < 0) { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks like an attempt at dithering, + // using the colormap #6 (of 0-31, a bit + // brighter than average). + if (count > 4) { + do { + // Lookup framebuffer, and retrieve + // a pixel that is either one column + // left or right of the current one. + // Add index from colormap to index. + screen[dest] = blurryTable.computePixelFast(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + // Ironically, "low detail" fuzziness was not really + // low-detail, + // as it normally did full-precision calculations. + // BLURRY_MAP[0x00FF & screen[dest2+ fuzzoffset[fuzzpos]]]; + // Clamp table lookup index. + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixelFast(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixelFast(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + screen[dest] = blurryTable.computePixelFast(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + screen[dest] = blurryTable.computePixelFast(screen[dest + fuzzoffset[fuzzpos]]); + screen[dest2] = screen[dest]; + + if (++fuzzpos == FUZZTABLE) { + fuzzpos = 0; + } + + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + } while (count-- != 0); + } + + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawSpan.java b/doom/src/rr/drawfuns/R_DrawSpan.java new file mode 100644 index 0000000..f5b8bba --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawSpan.java @@ -0,0 +1,165 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +/** + * Draws the actual span. + * + * ds_frac, ds_yfrac, ds_x2, ds_x1, ds_xstep and ds_ystep must be set. + * + */ +public abstract class R_DrawSpan extends DoomSpanFunction { + + public R_DrawSpan(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, V screen, IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + // TODO Auto-generated constructor stub + } + + public static final class Indexed extends R_DrawSpan { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, + byte[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + public void invoke() { + + int f_xfrac; // fixed_t + int f_yfrac; // fixed_t + int dest, count, spot; + final byte[] ds_colormap = dsvars.ds_colormap; + final byte[] ds_source = dsvars.ds_source; + + // System.out.println("R_DrawSpan: "+ds_x1+" to "+ds_x2+" at "+ + // ds_y); + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + f_xfrac = dsvars.ds_xfrac; + f_yfrac = dsvars.ds_yfrac; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + // We do not check for zero spans here? + count = dsvars.ds_x2 - dsvars.ds_x1; + + do { + // Current texture index in u,v. + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + // Next step in u,v. + f_xfrac += dsvars.ds_xstep; + f_yfrac += dsvars.ds_ystep; + + } while (count-- > 0); + } + } + + public static final class HiColor extends R_DrawSpan { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + public void invoke() { + + int f_xfrac; // fixed_t + int f_yfrac; // fixed_t + int dest, count, spot; + final short[] ds_colormap = dsvars.ds_colormap; + final byte[] ds_source = dsvars.ds_source; + + // System.out.println("R_DrawSpan: "+ds_x1+" to "+ds_x2+" at "+ + // ds_y); + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + f_xfrac = dsvars.ds_xfrac; + f_yfrac = dsvars.ds_yfrac; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + // We do not check for zero spans here? + count = dsvars.ds_x2 - dsvars.ds_x1; + + do { + // Current texture index in u,v. + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + // Next step in u,v. + f_xfrac += dsvars.ds_xstep; + f_yfrac += dsvars.ds_ystep; + + } while (count-- > 0); + } + } + + public static final class TrueColor extends R_DrawSpan { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, int[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + public void invoke() { + + int f_xfrac; // fixed_t + int f_yfrac; // fixed_t + int dest, count, spot; + final int[] ds_colormap = dsvars.ds_colormap; + final byte[] ds_source = dsvars.ds_source; + + // System.out.println("R_DrawSpan: "+ds_x1+" to "+ds_x2+" at "+ + // ds_y); + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + f_xfrac = dsvars.ds_xfrac; + f_yfrac = dsvars.ds_yfrac; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + // We do not check for zero spans here? + count = dsvars.ds_x2 - dsvars.ds_x1; + + do { + // Current texture index in u,v. + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + // Next step in u,v. + f_xfrac += dsvars.ds_xstep; + f_yfrac += dsvars.ds_ystep; + + } while (count-- > 0); + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawSpanLow.java b/doom/src/rr/drawfuns/R_DrawSpanLow.java new file mode 100644 index 0000000..f00c515 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawSpanLow.java @@ -0,0 +1,173 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +public abstract class R_DrawSpanLow + extends DoomSpanFunction { + + public R_DrawSpanLow(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, V screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + } + + public static final class Indexed + extends R_DrawSpanLow { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, + byte[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + public void invoke() { + final byte[] ds_source = dsvars.ds_source; + final byte[] ds_colormap = dsvars.ds_colormap; + final int ds_xstep = dsvars.ds_xstep; + final int ds_ystep = dsvars.ds_ystep; + int f_xfrac = dsvars.ds_xfrac; + int f_yfrac = dsvars.ds_yfrac; + int dest; + int count; + int spot; + + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + // MAES: count must be performed before shifting. + count = dsvars.ds_x2 - dsvars.ds_x1; + + // Blocky mode, need to multiply by 2. + dsvars.ds_x1 <<= 1; + dsvars.ds_x2 <<= 1; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + do { + spot + = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + } while (count-- > 0); + + } + } + + public static final class HiColor + extends R_DrawSpanLow { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + @Override + public void invoke() { + final byte[] ds_source = dsvars.ds_source; + final short[] ds_colormap = dsvars.ds_colormap; + final int ds_xstep = dsvars.ds_xstep; + final int ds_ystep = dsvars.ds_ystep; + int f_xfrac = dsvars.ds_xfrac; + int f_yfrac = dsvars.ds_yfrac; + int dest; + int count; + int spot; + + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + // MAES: count must be performed before shifting. + count = dsvars.ds_x2 - dsvars.ds_x1; + + // Blocky mode, need to multiply by 2. + dsvars.ds_x1 <<= 1; + dsvars.ds_x2 <<= 1; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + do { + spot + = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + } while (count-- > 0); + + } + } + + public static final class TrueColor + extends R_DrawSpanLow { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, int[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dsvars, + screen, I); + } + + @Override + public void invoke() { + final byte[] ds_source = dsvars.ds_source; + final int[] ds_colormap = dsvars.ds_colormap; + final int ds_xstep = dsvars.ds_xstep; + final int ds_ystep = dsvars.ds_ystep; + int f_xfrac = dsvars.ds_xfrac; + int f_yfrac = dsvars.ds_yfrac; + int dest; + int count; + int spot; + + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + // MAES: count must be performed before shifting. + count = dsvars.ds_x2 - dsvars.ds_x1; + + // Blocky mode, need to multiply by 2. + dsvars.ds_x1 <<= 1; + dsvars.ds_x2 <<= 1; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + do { + spot + = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + // Lowres/blocky mode does it twice, + // while scale is adjusted appropriately. + + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + } while (count-- > 0); + + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawSpanUnrolled.java b/doom/src/rr/drawfuns/R_DrawSpanUnrolled.java new file mode 100644 index 0000000..baf5a34 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawSpanUnrolled.java @@ -0,0 +1,251 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +/** + * Drawspan loop unrolled by 4. However it has low rendering quality and bad + * distortion. However it does actually does give a small speed boost (120 + * -> 130 fps with a Mul of 3.0) + * + */ +public abstract class R_DrawSpanUnrolled extends DoomSpanFunction { + + public R_DrawSpanUnrolled(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, V screen, IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + // TODO Auto-generated constructor stub + } + + public static final class HiColor extends R_DrawSpanUnrolled { + + public HiColor(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, short[] screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + // TODO Auto-generated constructor stub + } + + public void invoke() { + int position, step; + final byte[] source; + final short[] colormap; + int dest; + int count; + int spot; + int xtemp; + int ytemp; + + position = ((dsvars.ds_xfrac << 10) & 0xffff0000) + | ((dsvars.ds_yfrac >> 6) & 0xffff); + step = ((dsvars.ds_xstep << 10) & 0xffff0000) | ((dsvars.ds_ystep >> 6) & 0xffff); + source = dsvars.ds_source; + colormap = dsvars.ds_colormap; + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + count = dsvars.ds_x2 - dsvars.ds_x1 + 1; + //int rolls = 0; + while (count >= 4) { + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 1] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 2] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 3] = colormap[0x00FF & source[spot]]; + count -= 4; + dest += 4; + + // Half-assed attempt to fix precision by forced periodic + // realignment. + + /* + * if ((rolls++)%64==0){ position = + * ((((rolls*4)*ds_xstep+ds_xfrac) << 10) & 0xffff0000) | + * ((((rolls*4)*ds_ystep+ds_yfrac) >> 6) & 0xffff); } + */ + } + + while (count > 0) { + ytemp = position >> 4; + ytemp = ytemp & 4032; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest++] = colormap[0x00FF & source[spot]]; + count--; + } + } + + } + + public static final class Indexed extends R_DrawSpanUnrolled { + + public Indexed(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, byte[] screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + // TODO Auto-generated constructor stub + } + + public void invoke() { + int position, step; + final byte[] source; + final byte[] colormap; + int dest; + int count; + int spot; + int xtemp; + int ytemp; + + position = ((dsvars.ds_xfrac << 10) & 0xffff0000) + | ((dsvars.ds_yfrac >> 6) & 0xffff); + step = ((dsvars.ds_xstep << 10) & 0xffff0000) | ((dsvars.ds_ystep >> 6) & 0xffff); + source = dsvars.ds_source; + colormap = dsvars.ds_colormap; + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + count = dsvars.ds_x2 - dsvars.ds_x1 + 1; + //int rolls = 0; + while (count >= 4) { + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 1] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 2] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 3] = colormap[0x00FF & source[spot]]; + count -= 4; + dest += 4; + + // Half-assed attempt to fix precision by forced periodic + // realignment. + + /* + * if ((rolls++)%64==0){ position = + * ((((rolls*4)*ds_xstep+ds_xfrac) << 10) & 0xffff0000) | + * ((((rolls*4)*ds_ystep+ds_yfrac) >> 6) & 0xffff); } + */ + } + + while (count > 0) { + ytemp = position >> 4; + ytemp = ytemp & 4032; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest++] = colormap[0x00FF & source[spot]]; + count--; + } + } + + } + + public static final class TrueColor extends R_DrawSpanUnrolled { + + public TrueColor(int sCREENWIDTH, int sCREENHEIGHT, int[] ylookup, + int[] columnofs, SpanVars dsvars, int[] screen, + IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + } + + public void invoke() { + int position, step; + final byte[] source; + final int[] colormap; + int dest; + int count; + int spot; + int xtemp; + int ytemp; + + position = ((dsvars.ds_xfrac << 10) & 0xffff0000) + | ((dsvars.ds_yfrac >> 6) & 0xffff); + step = ((dsvars.ds_xstep << 10) & 0xffff0000) | ((dsvars.ds_ystep >> 6) & 0xffff); + source = dsvars.ds_source; + colormap = dsvars.ds_colormap; + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + count = dsvars.ds_x2 - dsvars.ds_x1 + 1; + //int rolls = 0; + while (count >= 4) { + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 1] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 2] = colormap[0x00FF & source[spot]]; + ytemp = position >> 4; + ytemp = ytemp & 0xfc0; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest + 3] = colormap[0x00FF & source[spot]]; + count -= 4; + dest += 4; + + // Half-assed attempt to fix precision by forced periodic + // realignment. + + /* + * if ((rolls++)%64==0){ position = + * ((((rolls*4)*ds_xstep+ds_xfrac) << 10) & 0xffff0000) | + * ((((rolls*4)*ds_ystep+ds_yfrac) >> 6) & 0xffff); } + */ + } + + while (count > 0) { + ytemp = position >> 4; + ytemp = ytemp & 4032; + xtemp = position >>> 26; + spot = xtemp | ytemp; + position += step; + screen[dest++] = colormap[0x00FF & source[spot]]; + count--; + } + } + + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawSpanUnrolled2.java b/doom/src/rr/drawfuns/R_DrawSpanUnrolled2.java new file mode 100644 index 0000000..989ee50 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawSpanUnrolled2.java @@ -0,0 +1,93 @@ +package rr.drawfuns; + +import i.IDoomSystem; + +/** An unrolled (4x) rendering loop with full quality */ +// public final int dumb=63 * 64; +public final class R_DrawSpanUnrolled2 extends DoomSpanFunction { + + public R_DrawSpanUnrolled2(int sCREENWIDTH, int sCREENHEIGHT, + int[] ylookup, int[] columnofs, SpanVars dsvars, + short[] screen, IDoomSystem I) { + super(sCREENWIDTH, sCREENHEIGHT, ylookup, columnofs, dsvars, screen, I); + // TODO Auto-generated constructor stub + } + + public void invoke() { + final byte[] ds_source = dsvars.ds_source; + final short[] ds_colormap = dsvars.ds_colormap; + final int ds_xstep = dsvars.ds_xstep; + final int ds_ystep = dsvars.ds_ystep; + int f_xfrac; // fixed_t + int f_yfrac; // fixed_t + int dest; + int count; + int spot; + + // System.out.println("R_DrawSpan: "+ds_x1+" to "+ds_x2+" at "+ + // ds_y); + if (RANGECHECK) { + doRangeCheck(); + // dscount++; + } + + f_xfrac = dsvars.ds_xfrac; + f_yfrac = dsvars.ds_yfrac; + + dest = ylookup[dsvars.ds_y] + columnofs[dsvars.ds_x1]; + + count = dsvars.ds_x2 - dsvars.ds_x1; + while (count >= 4) { + // Current texture index in u,v. + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + // Next step in u,v. + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + // UNROLL 2 + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + // UNROLL 3 + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + // UNROLL 4 + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + + count -= 4; + } + + while (count > 0) { + // Current texture index in u,v. + spot = ((f_yfrac >> (16 - 6)) & (63 * 64)) + + ((f_xfrac >> 16) & 63); + + // Lookup pixel from flat texture tile, + // re-index using light/colormap. + screen[dest++] = ds_colormap[0x00FF & ds_source[spot]]; + + // Next step in u,v. + f_xfrac += ds_xstep; + f_yfrac += ds_ystep; + count--; + } + + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawTLColumn.java b/doom/src/rr/drawfuns/R_DrawTLColumn.java new file mode 100644 index 0000000..1766011 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawTLColumn.java @@ -0,0 +1,122 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +public final class R_DrawTLColumn extends DoomColumnFunction { + + public R_DrawTLColumn(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.TRANSPARENT; + } + + public void invoke() { + int count; + int dest; // killough + int frac; // killough + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] tranmap = dcvars.tranmap; + + count = dcvars.dc_yh - dcvars.dc_yl + 1; + + if (count <= 0) // Zero length, column does not exceed a pixel. + { + return; + } + + if (RANGECHECK) { + performRangeCheck(); + } + + // Framebuffer destination address. + // Use ylookup LUT to avoid multiply with ScreenWidth. + // Use columnofs LUT for subwindows? + dest = computeScreenDest(); + + // Determine scaling, which is the only mapping to be done. + fracstep = dcvars.dc_iscale; + frac = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) * fracstep; + + // Inner loop that does the actual texture mapping, + // e.g. a DDA-lile scaling. + // This is as fast as it gets. (Yeah, right!!! -- killough) + // + // killough 2/1/98: more performance tuning + { + final byte[] source = dcvars.dc_source; + final short[] colormap = dcvars.dc_colormap; + int heightmask = dcvars.dc_texheight - 1; + if ((dcvars.dc_texheight & heightmask) != 0) // not a power of 2 -- + // killough + { + heightmask++; + heightmask <<= FRACBITS; + + if (frac < 0) { + while ((frac += heightmask) < 0) + ; + } else { + while (frac >= heightmask) { + frac -= heightmask; + } + } + + do { + // Re-map color indices from wall texture column + // using a lighting/special effects LUT. + // heightmask is the Tutti-Frutti fix -- killough + + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + dest += SCREENWIDTH; + if ((frac += fracstep) >= heightmask) { + frac -= heightmask; + } + } while (--count > 0); + } else { + while ((count -= 4) >= 0) // texture height is a power of 2 + // -- killough + { + // screen[dest] = + // main_tranmap[0xFF00&(screen[dest]<<8)|(0x00FF&colormap[0x00FF&source[dc_source_ofs+((frac>>FRACBITS) + // & heightmask)]])]; + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + dest += SCREENWIDTH; + frac += fracstep; + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + dest += SCREENWIDTH; + frac += fracstep; + } + if ((count & 1) != 0) { + screen[dest] = tranmap[0xFF00 + & (screen[dest] << 8) + | (0x00FF & colormap[0x00FF & source[dc_source_ofs + + ((frac >> FRACBITS) & heightmask)]])]; + } + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawTranslatedColumn.java b/doom/src/rr/drawfuns/R_DrawTranslatedColumn.java new file mode 100644 index 0000000..347eac6 --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawTranslatedColumn.java @@ -0,0 +1,315 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +public abstract class R_DrawTranslatedColumn + extends DoomColumnFunction { + + public R_DrawTranslatedColumn(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, V screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.TRANSLATED; + } + + public static final class HiColor + extends R_DrawTranslatedColumn { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final short[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // WATCOM VGA specific. + /* + * Keep for fixing. if (detailshift) { if (dc_x & 1) outp + * (SC_INDEX+1,12); else outp (SC_INDEX+1,3); dest = destview + + * dc_yl*80 + (dc_x>>1); } else { outp (SC_INDEX+1,1<<(dc_x&3)); + * dest = destview + dc_yl*80 + (dc_x>>2); } + */ + // FIXME. As above. + dest = computeScreenDest(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + } + + public static final class Indexed + extends R_DrawTranslatedColumn { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, byte[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final byte[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // WATCOM VGA specific. + /* + * Keep for fixing. if (detailshift) { if (dc_x & 1) outp + * (SC_INDEX+1,12); else outp (SC_INDEX+1,3); dest = destview + + * dc_yl*80 + (dc_x>>1); } else { outp (SC_INDEX+1,1<<(dc_x&3)); + * dest = destview + dc_yl*80 + (dc_x>>2); } + */ + // FIXME. As above. + dest = computeScreenDest(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + } + + public static final class TrueColor + extends R_DrawTranslatedColumn { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, int[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final int[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // WATCOM VGA specific. + /* + * Keep for fixing. if (detailshift) { if (dc_x & 1) outp + * (SC_INDEX+1,12); else outp (SC_INDEX+1,3); dest = destview + + * dc_yl*80 + (dc_x>>1); } else { outp (SC_INDEX+1,1<<(dc_x&3)); + * dest = destview + dc_yl*80 + (dc_x>>2); } + */ + // FIXME. As above. + dest = computeScreenDest(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/R_DrawTranslatedColumnLow.java b/doom/src/rr/drawfuns/R_DrawTranslatedColumnLow.java new file mode 100644 index 0000000..4056a3d --- /dev/null +++ b/doom/src/rr/drawfuns/R_DrawTranslatedColumnLow.java @@ -0,0 +1,329 @@ +package rr.drawfuns; + +import i.IDoomSystem; +import static m.fixed_t.FRACBITS; + +public abstract class R_DrawTranslatedColumnLow + extends DoomColumnFunction { + + public R_DrawTranslatedColumnLow(int SCREENWIDTH, int SCREENHEIGHT, + int[] ylookup, int[] columnofs, ColVars dcvars, V screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, screen, I); + this.flags = DcFlags.TRANSLATED | DcFlags.LOW_DETAIL; + } + + public static final class HiColor + extends R_DrawTranslatedColumnLow { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, + short[] screen, IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest, dest2; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final short[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + + } + + public static final class Indexed + extends R_DrawTranslatedColumnLow { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, byte[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest, dest2; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final byte[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + } + + public static final class TrueColor + extends R_DrawTranslatedColumnLow { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] ylookup, + int[] columnofs, ColVars dcvars, int[] screen, + IDoomSystem I) { + super(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, dcvars, + screen, I); + } + + public void invoke() { + int count; + // MAES: you know the deal by now... + int dest, dest2; + int frac; + final int fracstep; + final int dc_source_ofs = dcvars.dc_source_ofs; + final byte[] dc_source = dcvars.dc_source; + final int[] dc_colormap = dcvars.dc_colormap; + final byte[] dc_translation = dcvars.dc_translation; + + count = dcvars.dc_yh - dcvars.dc_yl; + if (count < 0) { + return; + } + + if (RANGECHECK) { + super.performRangeCheck(); + } + + // The idea is to draw more than one pixel at a time. + dest = blockyDest1(); + dest2 = blockyDest2(); + + // Looks familiar. + fracstep = dcvars.dc_iscale; + frac + = dcvars.dc_texturemid + (dcvars.dc_yl - dcvars.centery) + * fracstep; + + // Here we do an additional index re-mapping. + // Maes: Unroll by 4 + if (count >= 4) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + frac += fracstep; + + } while ((count -= 4) > 4); + } + + if (count > 0) { + do { + // Translation tables are used + // to map certain colorramps to other ones, + // used with PLAY sprites. + // Thus the "green" ramp of the player 0 sprite + // is mapped to gray, red, black/indigo. + screen[dest] + = screen[dest2] + = dc_colormap[0x00FF & dc_translation[0xFF & dc_source[dc_source_ofs + + (frac >> FRACBITS)]]]; + dest += SCREENWIDTH; + dest2 += SCREENWIDTH; + + frac += fracstep; + } while (count-- != 0); + } + } + } + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/SpanFunction.java b/doom/src/rr/drawfuns/SpanFunction.java new file mode 100644 index 0000000..bd48f69 --- /dev/null +++ b/doom/src/rr/drawfuns/SpanFunction.java @@ -0,0 +1,14 @@ +package rr.drawfuns; + +/** Either draws a column or a span + * + * @author velktron + * + */ +public interface SpanFunction { + + public void invoke(); + + public void invoke(SpanVars dsvars); + +} \ No newline at end of file diff --git a/doom/src/rr/drawfuns/SpanVars.java b/doom/src/rr/drawfuns/SpanVars.java new file mode 100644 index 0000000..6e44299 --- /dev/null +++ b/doom/src/rr/drawfuns/SpanVars.java @@ -0,0 +1,18 @@ +package rr.drawfuns; + +public class SpanVars { + + public int ds_xfrac; + public int ds_yfrac; + public int ds_xstep; + public T ds_source; + + /** DrawSpan colormap. */ + public V ds_colormap; + public int ds_y; + public int ds_x2; + public int ds_x1; + public int ds_ystep; + + public DoomSpanFunction spanfunc; +} \ No newline at end of file diff --git a/doom/src/rr/drawseg_t.java b/doom/src/rr/drawseg_t.java new file mode 100644 index 0000000..93286f4 --- /dev/null +++ b/doom/src/rr/drawseg_t.java @@ -0,0 +1,111 @@ +package rr; + +// +// ? +// +public class drawseg_t { + + public drawseg_t() { + + } + + /** MAES: was pointer. Not array? */ + public seg_t curline; + public int x1, x2; + + /** fixed_t */ + public int scale1, scale2, scalestep; + + /** 0=none, 1=bottom, 2=top, 3=both */ + public int silhouette; + + /** do not clip sprites above this (fixed_t) */ + public int bsilheight; + + /** do not clip sprites below this (fixed_t) */ + public int tsilheight; + + /** Indexes to lists for sprite clipping, + all three adjusted so [x1] is first value. */ + private int psprtopclip, psprbottomclip, pmaskedtexturecol; + + /** Pointers to the actual lists */ + private short[] sprtopclip, sprbottomclip, maskedtexturecol; + + ///////////////// Accessor methods to simulate mid-array pointers /////////// + public void setSprTopClip(short[] array, int index) { + this.sprtopclip = array; + this.psprtopclip = index; + } + + public void setSprBottomClip(short[] array, int index) { + this.sprbottomclip = array; + this.psprbottomclip = index; + } + + public void setMaskedTextureCol(short[] array, int index) { + this.maskedtexturecol = array; + this.pmaskedtexturecol = index; + } + + public short getSprTopClip(int index) { + return this.sprtopclip[this.psprtopclip + index]; + } + + public short getSprBottomClip(int index) { + return this.sprbottomclip[this.psprbottomclip + index]; + } + + public short getMaskedTextureCol(int index) { + return this.maskedtexturecol[this.pmaskedtexturecol + index]; + } + + public short[] getSprTopClipList() { + return this.sprtopclip; + } + + public short[] getSprBottomClipList() { + return this.sprbottomclip; + } + + public short[] getMaskedTextureColList() { + return this.maskedtexturecol; + } + + public int getSprTopClipPointer() { + return this.psprtopclip; + } + + public int getSprBottomClipPointer() { + return this.psprbottomclip; + } + + public int getMaskedTextureColPointer() { + return this.pmaskedtexturecol; + } + + public void setSprTopClipPointer(int index) { + this.psprtopclip = index; + } + + public void setSprBottomClipPointer(int index) { + this.psprbottomclip = index; + } + + public void setMaskedTextureColPointer(int index) { + this.pmaskedtexturecol = index; + } + + public boolean nullSprTopClip() { + return this.sprtopclip == null; + } + + public boolean nullSprBottomClip() { + return this.sprbottomclip == null; + } + + public boolean nullMaskedTextureCol() { + return this.maskedtexturecol == null; + } + +} \ No newline at end of file diff --git a/doom/src/rr/flat_t.java b/doom/src/rr/flat_t.java new file mode 100644 index 0000000..1b5dc93 --- /dev/null +++ b/doom/src/rr/flat_t.java @@ -0,0 +1,31 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import w.CacheableDoomObject; + +public class flat_t + implements CacheableDoomObject { + + public static final int FLAT_SIZE = 4096; + + public byte[] data; + + public flat_t() { + this.data = new byte[FLAT_SIZE]; + } + + public flat_t(int size) { + this.data = new byte[size]; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + + //buf.get(this.data); + this.data = buf.array(); + + } + +} \ No newline at end of file diff --git a/doom/src/rr/line_t.java b/doom/src/rr/line_t.java new file mode 100644 index 0000000..915805e --- /dev/null +++ b/doom/src/rr/line_t.java @@ -0,0 +1,357 @@ +package rr; + +import defines.slopetype_t; +import doom.SourceCode; +import doom.SourceCode.P_Spec; +import static doom.SourceCode.P_Spec.getNextSector; +import doom.thinker_t; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import static m.BBox.BOXBOTTOM; +import static m.BBox.BOXLEFT; +import static m.BBox.BOXRIGHT; +import static m.BBox.BOXTOP; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import p.Interceptable; +import p.Resettable; +import s.degenmobj_t; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.memset; +import w.DoomIO; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +/** This is the actual linedef */ +public class line_t + implements Interceptable, IReadableDoomObject, IPackableDoomObject, + Resettable { + + public static final char NO_INDEX = 0xFFFF; + + public line_t() { + sidenum = new char[2]; + bbox = new int[4]; + slopetype = slopetype_t.ST_HORIZONTAL; + } + + /** + * Vertices, from v1 to v2. NOTE: these are almost never passed as-such, nor + * linked to Maybe we can get rid of them and only use the value semantics? + */ + public vertex_t v1, v2; + + /** remapped vertex coords, for quick lookup with value semantics */ + public int v1x, v1y, v2x, v2y; + + /** (fixed_t) Precalculated v2 - v1 for side checking. */ + public int dx, dy; + + /** Animation related. */ + public short flags, special, tag; + + /** + * Visual appearance: SideDefs. sidenum[1] will be 0xFFFF if one sided + */ + public char[] sidenum; + + /** + * Neat. Another bounding box, for the extent of the LineDef. MAES: make + * this a proper bbox? fixed_t bbox[4]; + */ + public int[] bbox; + + /** To aid move clipping. */ + public slopetype_t slopetype; + + /** + * Front and back sector. Note: redundant? Can be retrieved from SideDefs. + * MAES: pointers + */ + public sector_t frontsector, backsector; + + public int frontsectorid, backsectorid; + + /** if == validcount, already checked */ + public int validcount; + + /** thinker_t for reversable actions MAES: (void*) */ + public thinker_t specialdata; + + public int specialdataid; + + public degenmobj_t soundorg; + + // From Boom + public int tranlump; + + public int id; + + /** killough 4/17/98: improves searches for tags. */ + public int firsttag, nexttag; + + /** For Boom stuff, interprets sidenum specially */ + public int getSpecialSidenum() { + return (sidenum[0] << 16) & (0x0000ffff & sidenum[1]); + } + + public void assignVertexValues() { + this.v1x = v1.x; + this.v1y = v1.y; + this.v2x = v2.x; + this.v2y = v2.y; + + } + + /** + * P_PointOnLineSide + * + * @param x + * fixed_t + * @param y + * fixed_t + * @return 0 or 1 (false, true) - (front, back) + */ + public boolean PointOnLineSide(int x, int y) { + + return (dx == 0) ? x <= this.v1x ? this.dy > 0 : this.dy < 0 + : (dy == 0) ? y <= this.v1y ? this.dx < 0 : this.dx > 0 + : FixedMul(y - this.v1y, this.dx >> FRACBITS) + >= FixedMul(this.dy >> FRACBITS, x - this.v1x); + /* + int dx, dy, left, right; + if (this.dx == 0) { + if (x <= this.v1x) + return this.dy > 0; + + return this.dy < 0; + } + if (this.dy == 0) { + if (y <= this.v1y) + return this.dx < 0; + + return this.dx > 0; + } + + dx = (x - this.v1x); + dy = (y - this.v1y); + + left = FixedMul(this.dy >> FRACBITS, dx); + right = FixedMul(dy, this.dx >> FRACBITS); + + if (right < left) + return false; // front side + return true; // back side*/ + } + + /** + * P_BoxOnLineSide Considers the line to be infinite Returns side 0 or 1, -1 + * if box crosses the line. Doubles as a convenient check for whether a + * bounding box crosses a line at all + * + * @param tmbox + * fixed_t[] + */ + public int BoxOnLineSide(int[] tmbox) { + boolean p1 = false; + boolean p2 = false; + + switch (this.slopetype) { + // Line perfectly horizontal, box floating "north" of line + case ST_HORIZONTAL: + p1 = tmbox[BOXTOP] > v1y; + p2 = tmbox[BOXBOTTOM] > v1y; + if (dx < 0) { + p1 ^= true; + p2 ^= true; + } + break; + + // Line perfectly vertical, box floating "west" of line + case ST_VERTICAL: + + p1 = tmbox[BOXRIGHT] < v1x; + p2 = tmbox[BOXLEFT] < v1x; + if (dy < 0) { + p1 ^= true; + p2 ^= true; + } + break; + + case ST_POSITIVE: + // Positive slope, both points on one side. + p1 = PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXTOP]); + p2 = PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXBOTTOM]); + break; + + case ST_NEGATIVE: + // Negative slope, both points (mirrored horizontally) on one side. + p1 = PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXTOP]); + p2 = PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXBOTTOM]); + break; + } + + if (p1 == p2) { + return p1 ? 1 : 0; + } + // Any other result means non-inclusive crossing. + return -1; + } + + /** + * Variant of P_BoxOnLineSide. Uses inclusive checks, so that even lines on + * the border of a box will be considered crossing. This is more useful for + * building blockmaps. + * + * @param tmbox + * fixed_t[] + */ + public int BoxOnLineSideInclusive(int[] tmbox) { + boolean p1 = false; + boolean p2 = false; + + switch (this.slopetype) { + // Line perfectly horizontal, box floating "north" of line + case ST_HORIZONTAL: + p1 = tmbox[BOXTOP] >= v1y; + p2 = tmbox[BOXBOTTOM] >= v1y; + if (dx < 0) { + p1 ^= true; + p2 ^= true; + } + break; + + // Line perfectly vertical, box floating "west" of line + case ST_VERTICAL: + + p1 = tmbox[BOXRIGHT] <= v1x; + p2 = tmbox[BOXLEFT] <= v1x; + if (dy < 0) { + p1 ^= true; + p2 ^= true; + } + break; + + case ST_POSITIVE: + // Positive slope, both points on one side. + p1 = PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXTOP]); + p2 = PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXBOTTOM]); + break; + + case ST_NEGATIVE: + // Negative slope, both points (mirrored horizontally) on one side. + p1 = PointOnLineSide(tmbox[BOXRIGHT], tmbox[BOXTOP]); + p2 = PointOnLineSide(tmbox[BOXLEFT], tmbox[BOXBOTTOM]); + break; + } + + if (p1 == p2) { + return p1 ? 1 : 0; + } + // Any other result means non-inclusive crossing. + return -1; + } + + /** + * getNextSector() Return sector_t * of sector next to current. NULL if not + * two-sided line + */ + @SourceCode.Compatible("getNextSector(line_t line, sector_t sec)") + @P_Spec.C(getNextSector) + public sector_t getNextSector(sector_t sec) { + if (!eval(flags & ML_TWOSIDED)) { + return null; + } + + if (frontsector == sec) { + return backsector; + } + + return frontsector; + } + + public String toString() { + return (String.format("Line %d Flags: %x Special %d Tag: %d ", this.id, this.flags, + this.special, this.tag)); + } + + @Override + public void read(DataInputStream f) + throws IOException { + + // For histerical reasons, these are the only parts of line_t that + // are archived in vanilla savegames. Go figure. + this.flags = DoomIO.readLEShort(f); + this.special = DoomIO.readLEShort(f); + this.tag = DoomIO.readLEShort(f); + } + + @Override + public void pack(ByteBuffer buffer) { + buffer.putShort(flags); + buffer.putShort(special); + buffer.putShort(tag); + // buffer.putShort((short) 0XDEAD); + // buffer.putShort((short) 0XBABE); + // buffer.putShort((short) 0XBEEF); + } + + @Override + public void reset() { + v1 = v2 = null; + v1x = v1y = v2x = v2y = 0; + dx = dy = 0; + flags = special = tag = 0; + memset(sidenum, (char) 0, sidenum.length); + Arrays.fill(bbox, 0); + slopetype = slopetype_t.ST_HORIZONTAL; + frontsector = backsector = null; + frontsectorid = backsectorid = 0; + validcount = 0; + specialdata = null; + specialdataid = 0; + soundorg = null; + tranlump = 0; + } + + /** + * LUT, motion clipping, walls/grid element // // LineDef attributes. // /** + * Solid, is an obstacle. + */ + public static final int ML_BLOCKING = 1; + + /** Blocks monsters only. */ + public static final int ML_BLOCKMONSTERS = 2; + + /** Backside will not be present at all if not two sided. */ + public static final int ML_TWOSIDED = 4; + + // If a texture is pegged, the texture will have + // the end exposed to air held constant at the + // top or bottom of the texture (stairs or pulled + // down things) and will move with a height change + // of one of the neighbor sectors. + // Unpegged textures allways have the first row of + // the texture at the top pixel of the line for both + // top and bottom textures (use next to windows). + /** upper texture unpegged */ + public static final int ML_DONTPEGTOP = 8; + + /** lower texture unpegged */ + public static final int ML_DONTPEGBOTTOM = 16; + + /** In AutoMap: don't map as two sided: IT'S A SECRET! */ + public static final int ML_SECRET = 32; + + /** Sound rendering: don't let sound cross two of these. */ + public static final int ML_SOUNDBLOCK = 64; + + /** Don't draw on the automap at all. */ + public static final int ML_DONTDRAW = 128; + + /** Set if already seen, thus drawn in automap. */ + public static final int ML_MAPPED = 256; + +} \ No newline at end of file diff --git a/doom/src/rr/mappatch_t.java b/doom/src/rr/mappatch_t.java new file mode 100644 index 0000000..d7a9496 --- /dev/null +++ b/doom/src/rr/mappatch_t.java @@ -0,0 +1,39 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +/** + * Texture definition. + * Each texture is composed of one or more patches, + * with patches being lumps stored in the WAD. + * The lumps are referenced by number, and patched + * into the rectangular texture space using origin + * and possibly other attributes. + */ +public class mappatch_t implements CacheableDoomObject { + + public short originx; + public short originy; + public short patch; + public short stepdir; + public short colormap; + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + originx = buf.getShort(); + originy = buf.getShort(); + patch = buf.getShort(); + stepdir = buf.getShort(); + colormap = buf.getShort(); + } + + public static final int size() { + return 10; + } + +}; \ No newline at end of file diff --git a/doom/src/rr/maptexture_t.java b/doom/src/rr/maptexture_t.java new file mode 100644 index 0000000..84de86e --- /dev/null +++ b/doom/src/rr/maptexture_t.java @@ -0,0 +1,45 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import static utils.GenericCopy.malloc; +import w.CacheableDoomObject; +import w.DoomBuffer; + +/** Texture definition. + * A DOOM wall texture is a list of patches which are to be combined in a predefined order. + * This is the ON-DISK structure, to be read from the TEXTURES1 and TEXTURES2 lumps. + * In memory, this becomes texture_t. + * + * @author MAES + * + */ +public class maptexture_t implements CacheableDoomObject { + + public String name; + public boolean masked; + public short width; // was signed byte + public short height; // was + //void**t columndirectory; // OBSOLETE (yeah, but we must read a dummy integer here) + public short patchcount; + public mappatch_t[] patches; + + @Override + public void unpack(ByteBuffer buf) throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + name = DoomBuffer.getNullTerminatedString(buf, 8); + masked = (buf.getInt() != 0); + width = buf.getShort(); + height = buf.getShort(); + buf.getInt(); // read a dummy integer for obsolete columndirectory. + patchcount = buf.getShort(); + + // Simple sanity check. Do not attempt reading more patches than there + // are left in the TEXTURE lump. + patchcount = (short) Math.min(patchcount, (buf.capacity() - buf.position()) / mappatch_t.size()); + + patches = malloc(mappatch_t::new, mappatch_t[]::new, patchcount); + DoomBuffer.readObjectArray(buf, patches, patchcount); + } +}; \ No newline at end of file diff --git a/doom/src/rr/maskdraw_t.java b/doom/src/rr/maskdraw_t.java new file mode 100644 index 0000000..650995a --- /dev/null +++ b/doom/src/rr/maskdraw_t.java @@ -0,0 +1,23 @@ +package rr; + +/** Purpose unknown, probably unused. + * On a closer examination, it could have been part of a system to + * "enqueue" masked draws, not much unlike the current parallel + * rendering subsystem, but discarded because of simplifications. + * In theory it could be brought back one day if parallel sprite + * drawing comes back.. just a thought ;-) + * + * + * @author Maes + * + */ +public class maskdraw_t { + + public int x1; + public int x2; + + public int column; + public int topclip; + public int bottomclip; + +} \ No newline at end of file diff --git a/doom/src/rr/node_t.java b/doom/src/rr/node_t.java new file mode 100644 index 0000000..dfee24d --- /dev/null +++ b/doom/src/rr/node_t.java @@ -0,0 +1,206 @@ +package rr; + +import doom.SourceCode; +import doom.SourceCode.R_Main; +import static doom.SourceCode.R_Main.R_PointOnSide; +import doom.SourceCode.fixed_t; +import m.BBox; +import m.ISyncLogger; +import m.Settings; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import mochadoom.Engine; +import p.Resettable; +import static utils.C2JUtils.eval; +import static utils.C2JUtils.memset; + +/** + * BSP node. + * + * @author Maes + */ +public class node_t implements Resettable { + + public node_t() { + bbox = new BBox[]{new BBox(), new BBox()}; + children = new int[2]; + } + + public node_t(int x, int y, int dx, int dy, BBox[] bbox, + int[] children) { + this.x = x; + this.y = y; + this.dx = dx; + this.dy = dy; + this.bbox = bbox; + this.children = children; + } + + /** + * Partition line. + */ + @fixed_t + public int x, y, dx, dy; + + /** + * Bounding box for each child. + */ + // Maes: make this into two proper bboxes? + @fixed_t + public BBox[] bbox; + + /** + * If NF_SUBSECTOR its a subsector. + * + * e6y: support for extented nodes + */ + public int[] children; + + /** + * R_PointOnSide + * Traverse BSP (sub) tree, + * check point against partition plane. + * Returns side 0 (front) or 1 (back). + * + * @param x fixed + * @param y fixed + * @param node + */ + @R_Main.C(R_PointOnSide) + public static int PointOnSide(@fixed_t int x, @fixed_t int y, node_t node) { + // MAES: These are used mainly as ints, no need to use fixed_t internally. + // fixed_t will only be used as a "pass type", but calculations will be done with ints, preferably. + @fixed_t + int dx, dy, left, right; + + if (node.dx == 0) { + if (x <= node.x) { + return (node.dy > 0) ? 1 : 0; + } + + return (node.dy < 0) ? 1 : 0; + } + if (node.dy == 0) { + if (y <= node.y) { + return (node.dx < 0) ? 1 : 0; + } + + return (node.dx > 0) ? 1 : 0; + } + + dx = (x - node.x); + dy = (y - node.y); + + // Try to quickly decide by looking at sign bits. + if (((node.dy ^ node.dx ^ dx ^ dy) & 0x80000000) != 0) { + if (((node.dy ^ dx) & 0x80000000) != 0) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(node.dy >> FRACBITS, dx); + right = FixedMul(dy, node.dx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; + } + + /** + * Since no context is needed, this is perfect for an instance method + * + * @param x fixed + * @param y fixed + * @return + */ + @SourceCode.Exact + @R_Main.C(R_PointOnSide) + public int PointOnSide(@fixed_t int x, @fixed_t int y) { + // MAES: These are used mainly as ints, no need to use fixed_t internally. + // fixed_t will only be used as a "pass type", but calculations will be done with ints, preferably. + @fixed_t + int lDx, lDy, left, right; + + if (this.dx == 0) { + if (x <= this.x) { + return (this.dy > 0) ? 1 : 0; + } + + return (this.dy < 0) ? 1 : 0; + } + if (this.dy == 0) { + if (y <= this.y) { + return (this.dx < 0) ? 1 : 0; + } + + return (this.dx > 0) ? 1 : 0; + } + + lDx = (x - this.x); + lDy = (y - this.y); + + // Try to quickly decide by looking at sign bits. + if (((this.dy ^ this.dx ^ lDx ^ lDy) & 0x80000000) != 0) { + if (((this.dy ^ lDx) & 0x80000000) != 0) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(this.dy >> FRACBITS, lDx); + right = FixedMul(lDy, this.dx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; + } + + /** + * P_DivlineSide + * Returns side 0 (front), 1 (back), or 2 (on). + * Clone of divline_t's method. Same contract, but working on node_t's to avoid casts. + * Boom-style code. Da fack. + * [Maes]: using it leads to very different DEMO4 UD behavior. + */ + public int DivlineSide(int x, int y) { + int left, right; + return (this.dx == 0) ? x == this.x ? 2 : x <= this.x ? eval(this.dy > 0) : eval(this.dy < 0) : (this.dy == 0) + ? (OLDDEMO ? x : y) == this.y ? 2 : y <= this.y ? eval(this.dx < 0) : eval(this.dx > 0) : (this.dy == 0) + ? y == this.y ? 2 : y <= this.y ? eval(this.dx < 0) : eval(this.dx > 0) + : (right = ((y - this.y) >> FRACBITS) * (this.dx >> FRACBITS)) + < (left = ((x - this.x) >> FRACBITS) * (this.dy >> FRACBITS)) ? 0 : right == left ? 2 : 1; + } + + private static final boolean OLDDEMO = Engine.getConfig().equals(Settings.line_of_sight, Settings.LOS.Vanilla); + + public int DivlineSide(int x, int y, ISyncLogger SL, boolean sync) { + int result = DivlineSide(x, y); + + if (sync) { + SL.sync("DLS %d\n", result); + } + + return result; + } + + @Override + public void reset() { + x = y = dx = dy = 0; + + for (int i = 0; i < 2; i++) { + bbox[i].ClearBox(); + } + + memset(children, 0, children.length); + } + +} \ No newline at end of file diff --git a/doom/src/rr/pQuickSprite.java b/doom/src/rr/pQuickSprite.java new file mode 100644 index 0000000..c6fe151 --- /dev/null +++ b/doom/src/rr/pQuickSprite.java @@ -0,0 +1,76 @@ +package rr; + +public class pQuickSprite { + + public static final void sort(vissprite_t[] c) { + int i, j, left = 0, right = c.length - 1, stack_pointer = -1; + int[] stack = new int[128]; + vissprite_t swap, temp; + while (true) { + if (right - left <= 7) { + for (j = left + 1; j <= right; j++) { + swap = c[j]; + i = j - 1; + while (i >= left && c[i].scale > swap.scale) { + c[i + 1] = c[i--]; + } + c[i + 1] = swap; + } + if (stack_pointer == -1) { + break; + } + right = stack[stack_pointer--]; + left = stack[stack_pointer--]; + } else { + int median = (left + right) >> 1; + i = left + 1; + j = right; + swap = c[median]; + c[median] = c[i]; + c[i] = swap; + /* make sure: c[left] <= c[left+1] <= c[right] */ + if (c[left].scale > c[right].scale) { + swap = c[left]; + c[left] = c[right]; + c[right] = swap; + } + if (c[i].scale > c[right].scale) { + swap = c[i]; + c[i] = c[right]; + c[right] = swap; + } + if (c[left].scale > c[i].scale) { + swap = c[left]; + c[left] = c[i]; + c[i] = swap; + } + temp = c[i]; + while (true) { + do { + i++; + } while (c[i].scale < temp.scale); + do { + j--; + } while (c[j].scale > temp.scale); + if (j < i) { + break; + } + swap = c[i]; + c[i] = c[j]; + c[j] = swap; + } + c[left + 1] = c[j]; + c[j] = temp; + if (right - i + 1 >= j - left) { + stack[++stack_pointer] = i; + stack[++stack_pointer] = right; + right = j - 1; + } else { + stack[++stack_pointer] = left; + stack[++stack_pointer] = j - 1; + left = i; + } + } + } + } +} \ No newline at end of file diff --git a/doom/src/rr/parallel/AbstractParallelRenderer.java b/doom/src/rr/parallel/AbstractParallelRenderer.java new file mode 100644 index 0000000..5d61727 --- /dev/null +++ b/doom/src/rr/parallel/AbstractParallelRenderer.java @@ -0,0 +1,557 @@ +package rr.parallel; + +import data.Tables; +import static data.Tables.finetangent; +import doom.DoomMain; +import java.io.IOException; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import mochadoom.Loggers; +import rr.PlaneDrawer; +import rr.RendererState; +import rr.SceneRenderer; +import rr.drawfuns.ColVars; +import utils.C2JUtils; + +/** + * Features and functionality which is common among parallel renderers + * + * @author velktron + */ +public abstract class AbstractParallelRenderer extends RendererState implements RWI.Init { + + private static final Logger LOGGER = Loggers.getLogger(AbstractParallelRenderer.class.getName()); + + public AbstractParallelRenderer(DoomMain DM, int wallthread, int floorthreads, int nummaskedthreads) { + super(DM); + this.NUMWALLTHREADS = wallthread; + this.NUMFLOORTHREADS = floorthreads; + this.NUMMASKEDTHREADS = nummaskedthreads; + // Prepare the barriers for MAXTHREADS + main thread. + drawsegsbarrier = new CyclicBarrier(NUMWALLTHREADS + 1); + visplanebarrier = new CyclicBarrier(NUMFLOORTHREADS + 1); + maskedbarrier = new CyclicBarrier(NUMMASKEDTHREADS + 1); + tp = Executors.newCachedThreadPool(); + } + + public AbstractParallelRenderer(DoomMain DM, int wallthread, + int floorthreads) { + super(DM); + this.NUMWALLTHREADS = wallthread; + this.NUMFLOORTHREADS = floorthreads; + this.NUMMASKEDTHREADS = 1; + // Prepare the barriers for MAXTHREADS + main thread. + drawsegsbarrier = new CyclicBarrier(NUMWALLTHREADS + 1); + visplanebarrier = new CyclicBarrier(NUMFLOORTHREADS + 1); + maskedbarrier = new CyclicBarrier(NUMMASKEDTHREADS + 1); + tp = Executors.newCachedThreadPool(); + } + + // //////// PARALLEL OBJECTS ///////////// + protected final int NUMWALLTHREADS; + + protected final int NUMMASKEDTHREADS; + + protected final int NUMFLOORTHREADS; + + protected Executor tp; + + protected Runnable[] vpw; + + protected MaskedWorker[] maskedworkers; + + protected CyclicBarrier drawsegsbarrier; + + protected CyclicBarrier visplanebarrier; + + protected CyclicBarrier maskedbarrier; + + protected static final boolean DEBUG = false; + + protected final class ParallelSegs extends SegDrawer implements RWI.Get { + + ParallelSegs(SceneRenderer R) { + super(R); + ColVars fake = new ColVars<>(); + RWI = C2JUtils.createArrayOfObjects(fake, 3 * DOOM.vs.getScreenWidth()); + } + + /** + * Parallel version. Since there's so much crap to take into account + * when rendering, the number of walls to render is unknown a-priori and + * the BSP trasversal itself is not worth parallelizing, it makes more + * sense to store "rendering instructions" as quickly as the BSP can be + * transversed, and then execute those in parallel. Also saves on having + * to duplicate way too much status. + */ + @Override + protected void CompleteColumn() { + + // Don't wait to go over + if (RWIcount >= RWI.length) { + ResizeRWIBuffer(); + } + + // A deep copy is still necessary, as dc + RWI[RWIcount].copyFrom(dcvars); + + // We only need to point to the next one in the list. + RWIcount++; + } + + /** + * Starts the RenderWallExecutors. Sync is EXTERNAL, however. + */ + @Override + public void CompleteRendering() { + + for (int i = 0; i < NUMWALLTHREADS; i++) { + + RWIExec[i].setRange((i * RWIcount) / NUMWALLTHREADS, + ((i + 1) * RWIcount) / NUMWALLTHREADS); + // RWIExec[i].setRange(i%NUMWALLTHREADS,RWIcount,NUMWALLTHREADS); + tp.execute(RWIExec[i]); + } + + // System.out.println("RWI count"+RWIcount); + RWIcount = 0; + } + + /* + * Just what are "RWIs"? Stored wall rendering instructions. They can be + * at most 3*SCREENWIDTH (if there are low, mid and high textures on + * every column of the screen) Remember to init them and set screen and + * ylookup for all of them. Their max number is static and work + * partitioning can be done in any way, as long as you keep track of how + * many there are in any given frame. This value is stored inside + * RWIcount. TODO: there are cases when more than 3*SCREENWIDTH + * instructions need to be stored. therefore we really need a resizeable + * array here, but ArrayList is way too slow for our needs. Storing a + * whole wall is not an option, as, once again, a wall may have a + * variable number of columns and an irregular height profile -> we'd + * need to look into visplanes ugh... + */ + RenderWallExecutor[] RWIExec; + + /** Array of "wall" (actually, column) instructions */ + ColVars[] RWI; + + /** + * Increment this as you submit RWIs to the "queue". Remember to reset + * to 0 when you have drawn everything! + */ + int RWIcount = 0; + + /** + * Resizes RWI buffer, updates executors. Sorry for the hackish + * implementation but ArrayList and pretty much everything in + * Collections is way too slow for what we're trying to accomplish. + */ + void ResizeRWIBuffer() { + ColVars fake = new ColVars<>(); + + // Bye bye, old RWI. + RWI = C2JUtils.resize(fake, RWI, RWI.length * 2); + + for (int i = 0; i < NUMWALLTHREADS; i++) { + RWIExec[i].updateRWI(RWI); + } + // System.err.println("RWI Buffer resized. Actual capacity " + + // RWI.length); + } + + @Override + public ColVars[] getRWI() { + return RWI; + } + + @Override + public void setExecutors(RenderWallExecutor[] RWIExec) { + this.RWIExec = RWIExec; + + } + + @Override + public void sync() { + try { + drawsegsbarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "sync failure", e); + } + // TODO Auto-generated catch block + } + } + + protected final class ParallelPlanes extends PlaneDrawer { + + protected ParallelPlanes(DoomMain DOOM, SceneRenderer R) { + super(DOOM, R); + } + + /** + * R_DrawPlanes At the end of each frame. This also means that visplanes + * must have been set BEFORE we called this function. Therefore, look + * for errors behind. + * + * @throws IOException + */ + public void DrawPlanes() { + + if (RANGECHECK) { + rangeCheckErrors(); + } + + // vpw[0].setRange(0,lastvisplane/2); + // vpw[1].setRange(lastvisplane/2,lastvisplane); + for (int i = 0; i < NUMFLOORTHREADS; i++) { + tp.execute(vpw[i]); + } + } + + } // End Plane class + + protected final class ParallelSegs2 extends SegDrawer { + + /** + * RenderSeg subsystem. Similar concept to RWI, but stores + * "Render Seg Instructions" instead. More complex to build, but + * potentially faster in some situations, as it allows distributing load + * per-wall, rather than per-screen portion. Requires careful + * concurrency considerations. + */ + RenderSegInstruction[] RSI; + + /** + * Increment this as you submit RSIs to the "queue". Remember to reset + * to 0 when you have drawn everything! + */ + int RSIcount = 0; + + RenderSegExecutor[] RSIExec; + + final AbstractParallelRenderer APR; + + ParallelSegs2(AbstractParallelRenderer APR) { + super(APR); + this.APR = APR; + } + + @Override + protected void RenderSegLoop() { + int angle; + int yl, top, bottom, yh, mid, texturecolumn = 0; + + // Generate Seg rendering instruction BEFORE the looping start + // and anything is modified. The loop will be repeated in the + // threads, but without marking ceilings/floors etc. + GenerateRSI(); + + for (; rw_x < rw_stopx; rw_x++) { + // mark floor / ceiling areas + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; + + // no space above wall? + if (yl < ceilingclip[rw_x] + 1) { + yl = ceilingclip[rw_x] + 1; + } + + if (markceiling) { + top = ceilingclip[rw_x] + 1; + bottom = yl - 1; + + if (bottom >= floorclip[rw_x]) { + bottom = floorclip[rw_x] - 1; + } + + if (top <= bottom) { + vp_vars.visplanes[vp_vars.ceilingplane].setTop(rw_x, + (char) top); + vp_vars.visplanes[vp_vars.ceilingplane].setBottom(rw_x, + (char) bottom); + } + } + + yh = bottomfrac >> HEIGHTBITS; + + if (yh >= floorclip[rw_x]) { + yh = floorclip[rw_x] - 1; + } + + // System.out.printf("Precompute: rw %d yl %d yh %d\n",rw_x,yl,yh); + // A particular seg has been identified as a floor marker. + if (markfloor) { + top = yh + 1; + bottom = floorclip[rw_x] - 1; + if (top <= ceilingclip[rw_x]) { + top = ceilingclip[rw_x] + 1; + } + if (top <= bottom) { + vp_vars.visplanes[vp_vars.floorplane].setTop(rw_x, + (char) top); + vp_vars.visplanes[vp_vars.floorplane].setBottom(rw_x, + (char) bottom); + } + } + + // texturecolumn and lighting are independent of wall tiers + if (segtextured) { + // calculate texture offset. Still important to do because + // of masked + + angle + = Tables.toBAMIndex(rw_centerangle + + (int) APR.view.xtoviewangle[rw_x]); + texturecolumn + = rw_offset - FixedMul(finetangent[angle], rw_distance); + texturecolumn >>= FRACBITS; + } + + // Don't to any drawing, only compute bounds. + if (midtexture != 0) { + + APR.dcvars.dc_source = APR.TexMan.GetCachedColumn(midtexture, texturecolumn); + // dc_m=dcvars.dc_source_ofs; + // single sided line + ceilingclip[rw_x] = (short) APR.view.height; + floorclip[rw_x] = -1; + } else { + // two sided line + if (toptexture != 0) { + // top wall + mid = pixhigh >> HEIGHTBITS; + pixhigh += pixhighstep; + + if (mid >= floorclip[rw_x]) { + mid = floorclip[rw_x] - 1; + } + + if (mid >= yl) { + APR.dcvars.dc_source = APR.TexMan.GetCachedColumn(toptexture, texturecolumn); + ceilingclip[rw_x] = (short) mid; + } else { + ceilingclip[rw_x] = (short) (yl - 1); + } + } else { + // no top wall + if (markceiling) { + ceilingclip[rw_x] = (short) (yl - 1); + } + } + + if (bottomtexture != 0) { + // bottom wall + mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x]) { + mid = ceilingclip[rw_x] + 1; + } + + if (mid <= yh) { + APR.dcvars.dc_source = APR.TexMan.GetCachedColumn(bottomtexture, texturecolumn); + floorclip[rw_x] = (short) mid; + } else { + floorclip[rw_x] = (short) (yh + 1); + } + } else { + // no bottom wall + if (markfloor) { + floorclip[rw_x] = (short) (yh + 1); + } + } + + if (maskedtexture) { + // save texturecol + // for backdrawing of masked mid texture + seg_vars.maskedtexturecol[seg_vars.pmaskedtexturecol + + rw_x] = (short) texturecolumn; + } + } + + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; + } + } + + void GenerateRSI() { + if (RSIcount >= RSI.length) { + ResizeRSIBuffer(); + } + + RenderSegInstruction rsi = RSI[RSIcount]; + rsi.centery = APR.view.centery; + rsi.bottomfrac = bottomfrac; + rsi.bottomstep = bottomstep; + rsi.bottomtexture = bottomtexture; + rsi.markceiling = markceiling; + rsi.markfloor = markfloor; + rsi.midtexture = midtexture; + rsi.pixhigh = pixhigh; + rsi.pixhighstep = pixhighstep; + rsi.pixlow = pixlow; + rsi.pixlowstep = pixlowstep; + rsi.rw_bottomtexturemid = rw_bottomtexturemid; + rsi.rw_centerangle = rw_centerangle; + rsi.rw_distance = rw_distance; + rsi.rw_midtexturemid = rw_midtexturemid; + rsi.rw_offset = rw_offset; + rsi.rw_scale = rw_scale; + rsi.rw_scalestep = rw_scalestep; + rsi.rw_stopx = rw_stopx; + rsi.rw_toptexturemid = rw_toptexturemid; + rsi.rw_x = rw_x; + rsi.segtextured = segtextured; + rsi.topfrac = topfrac; + rsi.topstep = topstep; + rsi.toptexture = toptexture; + rsi.walllights = APR.colormaps.walllights; + rsi.viewheight = APR.view.height; + // rsi.floorplane=floorplane; + // rsi.ceilingplane=ceilingplane; + RSIcount++; + } + + @Override + protected void CompleteColumn() { + // TODO Auto-generated method stub + + } + + void RenderRSIPipeline() { + for (int i = 0; i < APR.NUMWALLTHREADS; i++) { + RSIExec[i].setRSIEnd(RSIcount); + // RWIExec[i].setRange(i%NUMWALLTHREADS,RWIcount,NUMWALLTHREADS); + APR.tp.execute(RSIExec[i]); + } + + // System.out.println("RWI count"+RWIcount); + RSIcount = 0; + } + + /** + * Resizes RWI buffer, updates executors. Sorry for the hackish + * implementation but ArrayList and pretty much everything in + * Collections is way too slow for what we're trying to accomplish. + */ + void ResizeRSIBuffer() { + RenderSegInstruction fake = new RenderSegInstruction<>(); + // Bye bye, old RSI. + RSI = C2JUtils.resize(fake, RSI, RSI.length * 2); + + for (int i = 0; i < APR.NUMWALLTHREADS; i++) { + RSIExec[i].updateRSI(RSI); + } + + LOGGER.log(Level.INFO, String.format("RWI Buffer resized. Actual capacity %d", RSI.length)); + } + } + + protected final class ParallelPlanes2 extends PlaneDrawer { + + protected ParallelPlanes2(DoomMain DOOM, SceneRenderer R) { + super(DOOM, R); + } + + /** + * R_DrawPlanes At the end of each frame. This also means that visplanes + * must have been set BEFORE we called this function. Therefore, look + * for errors behind. + * + * @throws IOException + */ + @Override + public void DrawPlanes() { + + if (RANGECHECK) { + rangeCheckErrors(); + } + + // vpw[0].setRange(0,lastvisplane/2); + // vpw[1].setRange(lastvisplane/2,lastvisplane); + for (int i = 0; i < NUMFLOORTHREADS; i++) { + tp.execute(vpw[i]); + } + } + } // End Plane class + + // / RWI SUBSYSTEM + // / AKA "Render Wall Instruction": instructions that store only a single + // column + // from a wall + /** + * R_InitRSISubsystem Initialize RSIs and RSI Executors. Pegs them to the + * RSI, ylookup and screen[0]. + */ + // protected abstract void InitRSISubsystem(); + + /* + * { // CATCH: this must be executed AFTER screen is set, and // AFTER we + * initialize the RWI themselves, // before V is set (right?) //offsets=new + * int[NUMWALLTHREADS]; for (int i=0;i[] InitRWIExecutors(int num, ColVars[] RWI) { + return null; + } + + RWI.Get RWIs; +} \ No newline at end of file diff --git a/doom/src/rr/parallel/IGetSmpColumn.java b/doom/src/rr/parallel/IGetSmpColumn.java new file mode 100644 index 0000000..1a8a217 --- /dev/null +++ b/doom/src/rr/parallel/IGetSmpColumn.java @@ -0,0 +1,21 @@ +package rr.parallel; + +import rr.column_t; + +/** An interface used to ease the use of the GetCachedColumn by part + * of parallelized renderers. + * + * @author Maes + * + */ +/** + * Special version of GetColumn meant to be called concurrently by different + * seg rendering threads, identfiex by index. This serves to avoid stomping + * on mutual cached textures and causing crashes. + * + */ +public interface IGetSmpColumn { + + column_t GetSmpColumn(int tex, int col, int id); + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/MaskedWorker.java b/doom/src/rr/parallel/MaskedWorker.java new file mode 100644 index 0000000..10027b8 --- /dev/null +++ b/doom/src/rr/parallel/MaskedWorker.java @@ -0,0 +1,508 @@ +package rr.parallel; + +import static data.Defines.FF_FRAMEMASK; +import static data.Defines.FF_FULLBRIGHT; +import static data.Defines.pw_invisibility; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import static m.fixed_t.FixedMul; +import mochadoom.Loggers; +import static p.mobj_t.MF_TRANSLATION; +import p.pspdef_t; +import rr.AbstractThings; +import rr.IDetailAware; +import rr.SceneRenderer; +import rr.column_t; +import rr.drawfuns.ColFuncs; +import rr.drawfuns.ColVars; +import rr.drawfuns.R_DrawColumnBoom; +import rr.drawfuns.R_DrawColumnBoomLow; +import rr.drawfuns.R_DrawFuzzColumn; +import rr.drawfuns.R_DrawFuzzColumnLow; +import rr.drawfuns.R_DrawTranslatedColumn; +import rr.drawfuns.R_DrawTranslatedColumnLow; +import rr.drawseg_t; +import static rr.line_t.ML_DONTPEGBOTTOM; +import rr.patch_t; +import rr.spritedef_t; +import rr.spriteframe_t; +import rr.vissprite_t; +import v.graphics.Palettes; +import v.scale.VideoScale; +import v.tables.BlurryTable; + +/** A "Masked Worker" draws sprites in a split-screen strategy. Used by + * ParallelRenderer2. Each Masked Worker is essentially a complete Things + * drawer, and reuses much of the serial methods. + * + * @author velktron + * + * @param + * @param + */ +public abstract class MaskedWorker extends AbstractThings implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(MaskedWorker.class.getName()); + + private final static boolean DEBUG = false; + private final static boolean RANGECHECK = false; + + protected final CyclicBarrier barrier; + protected final int id; + protected final int numthreads; + + //protected ColVars maskedcvars; + public MaskedWorker(VideoScale vs, SceneRenderer R, int id, int numthreads, CyclicBarrier barrier) { + super(vs, R); + // Workers have their own set, not a "pegged" one. + this.colfuncshi = new ColFuncs<>(); + this.colfuncslow = new ColFuncs<>(); + this.maskedcvars = new ColVars<>(); + this.id = id; + this.numthreads = numthreads; + this.barrier = barrier; + } + + @Override + public final void completeColumn() { + // Does nothing. Shuts up inheritance + } + + public static final class HiColor extends MaskedWorker { + + public HiColor(VideoScale vs, SceneRenderer R, int id, + int[] ylookup, int[] columnofs, int numthreads, short[] screen, + CyclicBarrier barrier, BlurryTable BLURRY_MAP) { + super(vs, R, id, numthreads, barrier); + + // Non-optimized stuff for masked. + colfuncshi.base = colfuncshi.main = colfuncshi.masked = new R_DrawColumnBoom.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.masked = new R_DrawColumnBoomLow.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + // Fuzzy columns. These are also masked. + colfuncshi.fuzz = new R_DrawFuzzColumn.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + colfuncslow.fuzz = new R_DrawFuzzColumnLow.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + + // Translated columns are usually sprites-only. + colfuncshi.trans = new R_DrawTranslatedColumn.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.trans = new R_DrawTranslatedColumnLow.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + colfuncs = colfuncshi; + + } + + } + + public static final class Indexed extends MaskedWorker { + + public Indexed(VideoScale vs, SceneRenderer R, int id, + int[] ylookup, int[] columnofs, int numthreads, byte[] screen, + CyclicBarrier barrier, BlurryTable BLURRY_MAP) { + super(vs, R, id, numthreads, barrier); + colfuncshi.base = colfuncshi.main = colfuncshi.masked = new R_DrawColumnBoom.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.masked = new R_DrawColumnBoomLow.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + // Fuzzy columns. These are also masked. + colfuncshi.fuzz = new R_DrawFuzzColumn.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + colfuncslow.fuzz = new R_DrawFuzzColumnLow.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + + // Translated columns are usually sprites-only. + colfuncshi.trans = new R_DrawTranslatedColumn.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.trans = new R_DrawTranslatedColumnLow.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + colfuncs = colfuncshi; + } + + } + + public static final class TrueColor extends MaskedWorker { + + public TrueColor(VideoScale vs, SceneRenderer R, int id, + int[] ylookup, int[] columnofs, int numthreads, int[] screen, + CyclicBarrier barrier, BlurryTable BLURRY_MAP) { + super(vs, R, id, numthreads, barrier); + + // Non-optimized stuff for masked. + colfuncshi.base = colfuncshi.main = colfuncshi.masked = new R_DrawColumnBoom.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.masked = new R_DrawColumnBoomLow.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + // Fuzzy columns. These are also masked. + colfuncshi.fuzz = new R_DrawFuzzColumn.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + colfuncslow.fuzz = new R_DrawFuzzColumnLow.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I, BLURRY_MAP); + + // Translated columns are usually sprites-only. + colfuncshi.trans = new R_DrawTranslatedColumn.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + colfuncslow.trans = new R_DrawTranslatedColumnLow.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, I); + + colfuncs = colfuncshi; + + } + + } + + protected int startx, endx; + + /** + * R_DrawVisSprite mfloorclip and mceilingclip should also be set. + * + * Sprites are actually drawn here. Obviously overrides the serial + * method, and only draws a portion of the sprite. + * + * + */ + @Override + protected final void DrawVisSprite(vissprite_t vis) { + column_t column; + int texturecolumn; + int frac; // fixed_t + patch_t patch; + // The sprite may have been partially drawn on another portion of the + // screen. + int bias = startx - vis.x1; + if (bias < 0) { + bias = 0; // nope, it ain't. + } + // Trim bounds to zone NOW + int x1 = Math.max(startx, vis.x1); + int x2 = Math.min(endx, vis.x2); + + // At this point, the view angle (and patch) has already been + // chosen. Go back. + patch = W.CachePatchNum(vis.patch + SM.getFirstSpriteLump()); + + maskedcvars.dc_colormap = vis.colormap; + // colfunc=glasscolfunc; + if (maskedcvars.dc_colormap == null) { + // NULL colormap = shadow draw + colfunc = colfuncs.fuzz; + } else if ((vis.mobjflags & MF_TRANSLATION) != 0) { + colfunc = colfuncs.trans; + @SuppressWarnings("unchecked") + final T translation = (T) colormaps.getTranslationTable(vis.mobjflags); + maskedcvars.dc_translation = translation; + } + + maskedcvars.dc_iscale = Math.abs(vis.xiscale) >> view.detailshift; + maskedcvars.dc_texturemid = vis.texturemid; + // Add bias to compensate for partially drawn sprite which has not been rejected. + frac = vis.startfrac + vis.xiscale * bias; + spryscale = vis.scale; + sprtopscreen = view.centeryfrac - FixedMul(maskedcvars.dc_texturemid, spryscale); + + // A texture height of 0 means "not tiling" and holds for + // all sprite/masked renders. + maskedcvars.dc_texheight = 0; + + for (maskedcvars.dc_x = x1; maskedcvars.dc_x <= x2; maskedcvars.dc_x++, frac += vis.xiscale) { + texturecolumn = frac >> FRACBITS; + if (true) { + if (texturecolumn < 0 || texturecolumn >= patch.width) { + I.Error("R_DrawSpriteRange: bad texturecolumn %d vs %d %d %d", texturecolumn, patch.width, x1, x2); + } + } + column = patch.columns[texturecolumn]; + + if (column == null) { + LOGGER.log(Level.WARNING, String.format("Null column for texturecolumn %d", texturecolumn)); + } else { + DrawMaskedColumn(column); + } + } + + colfunc = colfuncs.masked; + } + + /** + * R_RenderMaskedSegRange + * + * @param ds + * @param x1 + * @param x2 + */ + @Override + protected final void RenderMaskedSegRange(drawseg_t ds, int x1, int x2) { + + // Trivial rejection + if (ds.x1 > endx || ds.x2 < startx) { + return; + } + + // Trim bounds to zone NOW + x1 = Math.max(startx, x1); + x2 = Math.min(endx, x2); + + int index; + + int lightnum; + int texnum; + int bias = startx - ds.x1; // Correct for starting outside + if (bias < 0) { + bias = 0; // nope, it ain't. + } + // System.out.printf("RenderMaskedSegRange from %d to %d\n",x1,x2); + + // Calculate light table. + // Use different light tables + // for horizontal / vertical / diagonal. Diagonal? + // OPTIMIZE: get rid of LIGHTSEGSHIFT globally + MyBSP.curline = ds.curline; + frontsector = MyBSP.curline.frontsector; + backsector = MyBSP.curline.backsector; + texnum = TexMan.getTextureTranslation(MyBSP.curline.sidedef.midtexture); + // System.out.print(" for texture "+textures[texnum].name+"\n:"); + lightnum = (frontsector.lightlevel >> colormaps.lightSegShift()) + colormaps.extralight; + + if (MyBSP.curline.v1y == MyBSP.curline.v2y) { + lightnum--; + } else if (MyBSP.curline.v1x == MyBSP.curline.v2x) { + lightnum++; + } + + // Killough code. + colormaps.walllights = lightnum >= colormaps.lightLevels() ? colormaps.scalelight[colormaps.lightLevels() - 1] + : lightnum < 0 ? colormaps.scalelight[0] : colormaps.scalelight[lightnum]; + + // Get the list + maskedtexturecol = ds.getMaskedTextureColList(); + // And this is the pointer. + pmaskedtexturecol = ds.getMaskedTextureColPointer(); + + rw_scalestep = ds.scalestep; + spryscale = ds.scale1 + (x1 - ds.x1) * rw_scalestep; + + // HACK to get "pointers" inside clipping lists + mfloorclip = ds.getSprBottomClipList(); + p_mfloorclip = ds.getSprBottomClipPointer(); + mceilingclip = ds.getSprTopClipList(); + p_mceilingclip = ds.getSprTopClipPointer(); + // find positioning + if ((MyBSP.curline.linedef.flags & ML_DONTPEGBOTTOM) != 0) { + maskedcvars.dc_texturemid = frontsector.floorheight > backsector.floorheight ? frontsector.floorheight + : backsector.floorheight; + maskedcvars.dc_texturemid = maskedcvars.dc_texturemid + TexMan.getTextureheight(texnum) + - view.z; + } else { + maskedcvars.dc_texturemid = frontsector.ceilingheight < backsector.ceilingheight + ? frontsector.ceilingheight + : backsector.ceilingheight; + + maskedcvars.dc_texturemid -= view.z; + } + maskedcvars.dc_texturemid += MyBSP.curline.sidedef.rowoffset; + + if (colormaps.fixedcolormap != null) { + maskedcvars.dc_colormap = colormaps.fixedcolormap; + } + + // Texture height must be set at this point. This will trigger + // tiling. For sprites, it should be set to 0. + maskedcvars.dc_texheight = TexMan.getTextureheight(texnum) >> FRACBITS; + + // draw the columns + for (maskedcvars.dc_x = x1; maskedcvars.dc_x <= x2; maskedcvars.dc_x++) { + // calculate lighting + if (maskedtexturecol[pmaskedtexturecol + maskedcvars.dc_x] != Short.MAX_VALUE) { + if (colormaps.fixedcolormap == null) { + index = spryscale >>> colormaps.lightScaleShift(); + + if (index >= colormaps.maxLightScale()) { + index = colormaps.maxLightScale() - 1; + } + + maskedcvars.dc_colormap = colormaps.walllights[index]; + } + + sprtopscreen = view.centeryfrac + - FixedMul(maskedcvars.dc_texturemid, spryscale); + maskedcvars.dc_iscale = (int) (0xffffffffL / spryscale); + + // draw the texture + column_t data = TexMan.GetSmpColumn(texnum, + maskedtexturecol[pmaskedtexturecol + maskedcvars.dc_x], id); + + DrawMaskedColumn(data); + maskedtexturecol[pmaskedtexturecol + maskedcvars.dc_x] = Short.MAX_VALUE; + } + spryscale += rw_scalestep; + } + + } + + /** + * R_DrawPSprite + * + * Draws a "player sprite" with slighly different rules than normal + * sprites. This is actually a PITA, at best :-/ + * + * Also different than normal implementation. + * + */ + @Override + protected final void DrawPSprite(pspdef_t psp) { + + int tx; + int x1; + int x2; + spritedef_t sprdef; + spriteframe_t sprframe; + vissprite_t vis; + int lump; + boolean flip; + + // + // decide which patch to use (in terms of angle?) + if (RANGECHECK) { + if (psp.state.sprite.ordinal() >= SM.getNumSprites()) { + I.Error("R_ProjectSprite: invalid sprite number %d ", psp.state.sprite); + } + } + + sprdef = SM.getSprite(psp.state.sprite.ordinal()); + + if (RANGECHECK) { + if ((psp.state.frame & FF_FRAMEMASK) >= sprdef.numframes) { + I.Error("R_ProjectSprite: invalid sprite frame %d : %d ", psp.state.sprite, psp.state.frame); + } + } + + sprframe = sprdef.spriteframes[psp.state.frame & FF_FRAMEMASK]; + + // Base frame for "angle 0" aka viewed from dead-front. + lump = sprframe.lump[0]; + // Q: where can this be set? A: at sprite loadtime. + flip = sprframe.flip[0] != 0; + + // calculate edges of the shape. tx is expressed in "view units". + tx = FixedMul(psp.sx, view.BOBADJUST) - view.WEAPONADJUST; + + tx -= spriteoffset[lump]; + + // So...centerxfrac is the center of the screen (pixel coords in + // fixed point). + x1 = (view.centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS; + + // off the right side + if (x1 > endx) { + return; + } + + tx += spritewidth[lump]; + x2 = ((view.centerxfrac + FixedMul(tx, pspritescale)) >> FRACBITS) - 1; + + // off the left side + if (x2 < startx) { + return; + } + + // store information in a vissprite ? + vis = avis; + vis.mobjflags = 0; + vis.texturemid = ((BASEYCENTER + view.lookdir) << FRACBITS) + FRACUNIT / 2 + - (psp.sy - spritetopoffset[lump]); + vis.x1 = x1 < startx ? startx : x1; + vis.x2 = x2 >= endx ? endx - 1 : x2; + vis.scale = (pspritescale) << view.detailshift; + + if (flip) { + vis.xiscale = -pspriteiscale; + vis.startfrac = spritewidth[lump] - 1; + } else { + vis.xiscale = pspriteiscale; + vis.startfrac = 0; + } + + if (vis.x1 > x1) { + vis.startfrac += vis.xiscale * (vis.x1 - x1); + } + + vis.patch = lump; + + if ((view.player.powers[pw_invisibility] > 4 * 32) + || (view.player.powers[pw_invisibility] & 8) != 0) { + // shadow draw + vis.colormap = null; + + } else if (colormaps.fixedcolormap != null) { + // fixed color + vis.colormap = colormaps.fixedcolormap; + // vis.pcolormap=0; + } else if ((psp.state.frame & FF_FULLBRIGHT) != 0) { + // full bright + vis.colormap = colormaps.colormaps[Palettes.COLORMAP_FIXED]; + // vis.pcolormap=0; + } else { + // local light + vis.colormap = colormaps.spritelights[colormaps.maxLightScale() - 1]; + } + + //System.out.printf("Weapon draw from %d to %d\n",vis.x1,vis.x2); + DrawVisSprite(vis); + } + + /** + * R_DrawMasked + * + * Sorts and draws vissprites (room for optimization in sorting func.) + * Draws masked textures. Draws player weapons and overlays (psprites). + * + * Sorting function can be swapped for almost anything, and it will work + * better, in-place and be simpler to draw, too. + * + * + */ + @Override + public void run() { + // vissprite_t spr; + int ds; + drawseg_t dss; + + // Sprites should already be sorted for distance + colfunc = colfuncs.masked; // Sprites use fully-masked capable + // function. + + // Update view height + this.maskedcvars.viewheight = view.height; + this.maskedcvars.centery = view.centery; + this.startx = ((id * view.width) / numthreads); + this.endx = (((id + 1) * view.width) / numthreads); + + // Update thread's own vissprites + final vissprite_t[] vissprites = VIS.getVisSprites(); + final int numvissprites = VIS.getNumVisSprites(); + + //System.out.printf("Sprites to render: %d\n",numvissprites); + // Try drawing all sprites that are on your side of + // the screen. Limit by x1 and x2, if you have to. + for (int i = 0; i < numvissprites; i++) { + DrawSprite(vissprites[i]); + } + + //System.out.printf("Segs to render: %d\n",ds_p); + // render any remaining masked mid textures + for (ds = seg_vars.ds_p - 1; ds >= 0; ds--) { + dss = seg_vars.drawsegs[ds]; + if (!(dss.x1 > endx || dss.x2 < startx) && !dss.nullMaskedTextureCol()) { + RenderMaskedSegRange(dss, dss.x1, dss.x2); + } + } + // draw the psprites on top of everything + // but does not draw on side views + // if (viewangleoffset==0) + + colfunc = colfuncs.player; + DrawPlayerSprites(); + colfunc = colfuncs.masked; + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "MaskedWorker run failure", e); + } + // TODO Auto-generated catch block + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/ParallelRenderer.java b/doom/src/rr/parallel/ParallelRenderer.java new file mode 100644 index 0000000..ce8eeca --- /dev/null +++ b/doom/src/rr/parallel/ParallelRenderer.java @@ -0,0 +1,467 @@ +package rr.parallel; + +import doom.DoomMain; +import doom.player_t; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.SimpleThings; +import rr.drawfuns.ColVars; +import rr.drawfuns.R_DrawColumnBoom; +import rr.drawfuns.R_DrawColumnBoomLow; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.drawfuns.R_DrawFuzzColumn; +import rr.drawfuns.R_DrawFuzzColumnLow; +import rr.drawfuns.R_DrawSpanLow; +import rr.drawfuns.R_DrawSpanUnrolled; +import rr.drawfuns.R_DrawTLColumn; +import rr.drawfuns.R_DrawTranslatedColumn; +import rr.drawfuns.R_DrawTranslatedColumnLow; + +/** + * This is Mocha Doom's famous parallel software renderer. It builds on the + * basic software renderer, but adds specialized handling for drawing segs + * (walls) and spans (floors) in parallel. There's inherent parallelism between + * walls and floor, and internal parallelism between walls and between floors. + * However, visplane limits and openings need to be pre-computed before any + * actual drawing starts, that's why rendering of walls is stored in "RWI"s or + * "Render Wall Instructions", and then rendered once they are all in place and + * the can be parallelized between rendering threads. Rendering of sprites is + * NOT parallelized yet (and probably not worth it, at this point). + * + * @author admin + */ +public abstract class ParallelRenderer extends AbstractParallelRenderer { + + private static final Logger LOGGER = Loggers.getLogger(ParallelRenderer.class.getName()); + + public ParallelRenderer(DoomMain DM, int wallthread, + int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Register parallel seg drawer with list of RWI subsystems. + ParallelSegs tmp = new ParallelSegs(this); + this.MySegs = tmp; + RWIs = tmp; + + this.MyThings = new SimpleThings<>(DM.vs, this); + //this.MyPlanes = new Planes(this);// new ParallelPlanes(DM.R); + + } + + /** + * Default constructor, 1 seg, 1 span and two masked threads. + * + * @param DM + */ + public ParallelRenderer(DoomMain DM) { + this(DM, 1, 1, 2); + } + + /** + * R_RenderView As you can guess, this renders the player view of a + * particular player object. In practice, it could render the view of any + * mobj too, provided you adapt the SetupFrame method (where the viewing + * variables are set). + * + * @throws IOException + */ + public void RenderPlayerView(player_t player) { + + // Viewing variables are set according to the player's mobj. Interesting + // hacks like + // free cameras or monster views can be done. + SetupFrame(player); + + /* + * Uncommenting this will result in a very existential experience if + * (Math.random()>0.999){ thinker_t shit=P.getRandomThinker(); try { + * mobj_t crap=(mobj_t)shit; player.mo=crap; } catch (ClassCastException + * e){ } } + */ + // Clear buffers. + MyBSP.ClearClipSegs(); + seg_vars.ClearDrawSegs(); + vp_vars.ClearPlanes(); + MySegs.ClearClips(); + VIS.ClearSprites(); + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // The head node is the last node output. + MyBSP.RenderBSPNode(DOOM.levelLoader.numnodes - 1); + + // System.out.printf("Submitted %d RWIs\n",RWIcount); + MySegs.CompleteRendering(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // "Warped floor" fixed, same-height visplane merging fixed. + MyPlanes.DrawPlanes(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + MySegs.sync(); + MyPlanes.sync(); + +// drawsegsbarrier.await(); +// visplanebarrier.await(); + MyThings.DrawMasked(); + + // RenderRMIPipeline(); + /* + * try { maskedbarrier.await(); } catch (Exception e) { + * e.printStackTrace(); } + */ + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + } + + public static final class Indexed extends ParallelRenderer { + + public Indexed(DoomMain DM, int wallthread, + int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new byte[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new byte[colormaps.maxLightScale()][]; + colormaps.zlight = new byte[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + + } + + /** + * R_InitColormaps + * + * @throws IOException + */ + @Override + protected void InitColormaps() throws IOException { + // Load in the light tables, + // 256 byte align tables. + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + + // MAES: blurry effect is hardcoded to this colormap. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + // colormaps = (byte *)( ((int)colormaps + 255)&~0xff); + } + + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + // DrawTLColumn=new R_DrawTLColumn(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + + protected void completeInit() { + super.completeInit(); + InitMaskedWorkers(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + maskedworkers = new MaskedWorker[NUMMASKEDTHREADS]; + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.Indexed( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, + screen, maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + @Override + public RenderWallExecutor[] InitRWIExecutors( + int num, ColVars[] RWI) { + RenderWallExecutor[] tmp + = new RenderWallExecutor.Indexed[num]; + + for (int i = 0; i < num; i++) { + tmp[i] = new RenderWallExecutor.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), columnofs, ylookup, screen, RWI, drawsegsbarrier); + } + + return tmp; + } + + } + + @Override + protected void InitParallelStuff() { + + // ...yeah, it works. + if (!(RWIs == null)) { + ColVars[] RWI = RWIs.getRWI(); + RenderWallExecutor[] RWIExec = InitRWIExecutors(NUMWALLTHREADS, RWI); + RWIs.setExecutors(RWIExec); + + for (int i = 0; i < NUMWALLTHREADS; i++) { + + detailaware.add(RWIExec[i]); + } + } + + // CATCH: this must be executed AFTER screen is set, and + // AFTER we initialize the RWI themselves, + // before V is set (right?) + // This actually only creates the necessary arrays and + // barriers. Things aren't "wired" yet. + // Using "render wall instruction" subsystem + // Using masked sprites + // RMIExec = new RenderMaskedExecutor[NUMMASKEDTHREADS]; + // Using + //vpw = new Runnable[NUMFLOORTHREADS]; + //maskedworkers = new MaskedWorker.Indexed[NUMMASKEDTHREADS]; + // RWIcount = 0; + // InitRWISubsystem(); + // InitRMISubsystem(); + // InitPlaneWorkers(); + // InitMaskedWorkers(); + // If using masked threads, set these too. + TexMan.setSMPVars(NUMMASKEDTHREADS); + + } + + /* + * private void InitPlaneWorkers(){ for (int i = 0; i < NUMFLOORTHREADS; + * i++) { vpw[i] = new VisplaneWorker2(i,SCREENWIDTH, SCREENHEIGHT, + * columnofs, ylookup, screen, visplanebarrier, NUMFLOORTHREADS); + * //vpw[i].id = i; detailaware.add((IDetailAware) vpw[i]); } } + */ + /* + * TODO: relay to dependent objects. super.initScaling(); + * ColVars fake = new ColVars(); RWI = + * C2JUtils.createArrayOfObjects(fake, SCREENWIDTH * 3); // Be MUCH more + * generous with this one. RMI = C2JUtils.createArrayOfObjects(fake, + * SCREENWIDTH * 6); + */ + protected abstract void InitMaskedWorkers(); + + public static final class HiColor extends ParallelRenderer { + + public HiColor(DoomMain DM, int wallthread, + int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new short[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new short[colormaps.maxLightScale()][]; + colormaps.zlight = new short[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + maskedworkers = new MaskedWorker[NUMMASKEDTHREADS]; + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.HiColor( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, + screen, maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + /** + * R_InitColormaps This is VERY different for hicolor. + * + * @throws IOException + */ + protected void InitColormaps() throws IOException { + + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS15 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + // Pointless, since we don't use indexes. Instead, a half-brite + // processing works just fine. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTLColumn = new R_DrawTLColumn(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + + @Override + public RenderWallExecutor[] InitRWIExecutors( + int num, ColVars[] RWI) { + RenderWallExecutor[] tmp + = new RenderWallExecutor.HiColor[num]; + + for (int i = 0; i < num; i++) { + tmp[i] = new RenderWallExecutor.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), columnofs, ylookup, screen, RWI, drawsegsbarrier); + } + + return tmp; + } + + } + + public static final class TrueColor extends ParallelRenderer { + + public TrueColor(DoomMain DM, int wallthread, + int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new int[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new int[colormaps.maxLightScale()][]; + colormaps.zlight = new int[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + } + + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + //DrawTLColumn=new R_DrawTLColumn(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + + /** + * R_InitColormaps This is VERY different for hicolor. + * + * @throws IOException + */ + protected void InitColormaps() + throws IOException { + + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS15 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + // Pointless, since we don't use indexes. Instead, a half-brite + // processing works just fine. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + maskedworkers = new MaskedWorker[NUMMASKEDTHREADS]; + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.TrueColor( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, screen, + maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + @Override + public RenderWallExecutor[] InitRWIExecutors( + int num, ColVars[] RWI) { + RenderWallExecutor[] tmp + = new RenderWallExecutor.TrueColor[num]; + + for (int i = 0; i < num; i++) { + tmp[i] = new RenderWallExecutor.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), columnofs, ylookup, screen, RWI, drawsegsbarrier); + } + + return tmp; + } + + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/ParallelRenderer2.java b/doom/src/rr/parallel/ParallelRenderer2.java new file mode 100644 index 0000000..eccfa0b --- /dev/null +++ b/doom/src/rr/parallel/ParallelRenderer2.java @@ -0,0 +1,475 @@ +package rr.parallel; + +import static data.Limits.MAXSEGS; +import doom.DoomMain; +import doom.player_t; +import java.io.IOException; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.drawfuns.R_DrawColumnBoom; +import rr.drawfuns.R_DrawColumnBoomLow; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.drawfuns.R_DrawFuzzColumn; +import rr.drawfuns.R_DrawFuzzColumnLow; +import rr.drawfuns.R_DrawSpanLow; +import rr.drawfuns.R_DrawSpanUnrolled; +import rr.drawfuns.R_DrawTLColumn; +import rr.drawfuns.R_DrawTranslatedColumn; +import rr.drawfuns.R_DrawTranslatedColumnLow; +import static utils.GenericCopy.malloc; + +/** This is a second attempt at building a seg-focused parallel renderer, instead of + * column-based. It does function, but is broken and has unsolved data dependencies. + * It's therefore not used in official releases, and I chose to deprecate it. + * If you still want to develop it, be my guest. + * + * @author velktron + * + */ +public abstract class ParallelRenderer2 extends AbstractParallelRenderer { + + private static final Logger LOGGER = Loggers.getLogger(ParallelRenderer2.class.getName()); + + @SuppressWarnings("unchecked") + public ParallelRenderer2(DoomMain DOOM, int wallthread, int floorthreads, int nummaskedthreads) { + super(DOOM, wallthread, floorthreads, nummaskedthreads); + LOGGER.log(Level.FINE, "Parallel Renderer 2 (Seg-based)"); + + this.MySegs = new ParallelSegs2<>(this); + this.MyPlanes = new ParallelPlanes<>(DOOM, this); + this.MyThings = new ParallelThings2<>(DOOM.vs, this); + + // TO BE LATE INIT? AFTER CONS? + // Masked workers. + ((ParallelThings2) MyThings).maskedworkers = maskedworkers = new MaskedWorker[NUMMASKEDTHREADS]; + InitMaskedWorkers(); + + ((ParallelSegs2) MySegs).RSI = malloc(RenderSegInstruction::new, RenderSegInstruction[]::new, MAXSEGS * 3); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitParallelStuff() { + // Prepare parallel stuff + ((ParallelSegs2) MySegs).RSIExec = new RenderSegExecutor[NUMWALLTHREADS]; + tp = Executors.newFixedThreadPool(NUMWALLTHREADS + NUMFLOORTHREADS); + // Prepare the barrier for MAXTHREADS + main thread. + //wallbarrier=new CyclicBarrier(NUMWALLTHREADS+1); + visplanebarrier = new CyclicBarrier(NUMFLOORTHREADS + NUMWALLTHREADS + 1); + + vpw = new VisplaneWorker2[NUMFLOORTHREADS]; + + // Uses "seg" parallel drawer, so RSI. + InitRSISubsystem(); + + maskedbarrier = new CyclicBarrier(NUMMASKEDTHREADS + 1); + + // If using masked threads, set these too. + TexMan.setSMPVars(NUMMASKEDTHREADS); + } + + ///////////////////////// The actual rendering calls /////////////////////// + /** + * R_RenderView + * + * As you can guess, this renders the player view of a particular player object. + * In practice, it could render the view of any mobj too, provided you adapt the + * SetupFrame method (where the viewing variables are set). + * + */ + @Override + @SuppressWarnings("unchecked") + public void RenderPlayerView(player_t player) { + // Viewing variables are set according to the player's mobj. Interesting hacks like + // free cameras or monster views can be done. + SetupFrame(player); + + /* Uncommenting this will result in a very existential experience + if (Math.random()>0.999){ + thinker_t shit=P.getRandomThinker(); + try { + mobj_t crap=(mobj_t)shit; + player.mo=crap; + } catch (ClassCastException e){ + + } + }*/ + // Clear buffers. + MyBSP.ClearClipSegs(); + seg_vars.ClearDrawSegs(); + vp_vars.ClearPlanes(); + MySegs.ClearClips(); + VIS.ClearSprites(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // The head node is the last node output. + MyBSP.RenderBSPNode(DOOM.levelLoader.numnodes - 1); + + // RenderRMIPipeline(); + /* + * try { maskedbarrier.await(); } catch (Exception e) { + * e.printStackTrace(); } + */ + ((ParallelSegs2) MySegs).RenderRSIPipeline(); + // System.out.printf("Submitted %d RSIs\n",RSIcount); + + MySegs.CompleteRendering(); + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + // "Warped floor" fixed, same-height visplane merging fixed. + MyPlanes.DrawPlanes(); + + try { + visplanebarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "RenderPlayerView failure", e); + } + + // Check for new console commands. + DOOM.gameNetworking.NetUpdate(); + + MySegs.sync(); + MyPlanes.sync(); + +// drawsegsbarrier.await(); +// visplanebarrier.await(); + MyThings.DrawMasked(); + } + + abstract protected void InitRSISubsystem(); + + /* + * { // CATCH: this must be executed AFTER screen is set, and // AFTER we + * initialize the RWI themselves, // before V is set (right?) //offsets=new + * int[NUMWALLTHREADS]; for (int i=0;i { + + public Indexed(DoomMain DM, int wallthread, int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new byte[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new byte[colormaps.maxLightScale()][]; + colormaps.zlight = new byte[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitRSISubsystem() { + // int[] offsets = new int[NUMWALLTHREADS]; + final ParallelSegs2 parallelSegs = ((ParallelSegs2) MySegs); + for (int i = 0; i < NUMWALLTHREADS; i++) { + parallelSegs.RSIExec[i] = new RenderSegExecutor.Indexed( + DOOM, i, screen, TexMan, + parallelSegs.RSI, MySegs.getBLANKCEILINGCLIP(), MySegs.getBLANKFLOORCLIP(), + MySegs.getCeilingClip(), MySegs.getFloorClip(), columnofs, view.xtoviewangle, + ylookup, vp_vars.visplanes, this.visplanebarrier, colormaps + ); + // SegExecutor sticks to its own half (or 1/nth) of the screen. + parallelSegs.RSIExec[i].setScreenRange(i * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS), (i + 1) * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS)); + detailaware.add(parallelSegs.RSIExec[i]); + } + + for (int i = 0; i < NUMFLOORTHREADS; i++) { + final VisplaneWorker2 w = new VisplaneWorker2.Indexed( + DOOM, this, i, columnofs, ylookup, screen, visplanebarrier, NUMFLOORTHREADS + ); + vpw[i] = w; + detailaware.add(w); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.Indexed( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, + screen, maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + @Override + protected void InitColormaps() throws IOException { + // Load in the light tables, + // 256 byte align tables. + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + // MAES: blurry effect is hardcoded to this colormap. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + // colormaps = (byte *)( ((int)colormaps + 255)&~0xff); + } + + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + // DrawTLColumn=new R_DrawTLColumn(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + } + + public static final class HiColor extends ParallelRenderer2 { + + public HiColor(DoomMain DM, int wallthread, int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new short[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new short[colormaps.maxLightScale()][]; + colormaps.zlight = new short[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitRSISubsystem() { + // int[] offsets = new int[NUMWALLTHREADS]; + final ParallelSegs2 parallelSegs = ((ParallelSegs2) MySegs); + for (int i = 0; i < NUMWALLTHREADS; i++) { + parallelSegs.RSIExec[i] = new RenderSegExecutor.HiColor( + DOOM, i, screen, TexMan, + parallelSegs.RSI, MySegs.getBLANKCEILINGCLIP(), MySegs.getBLANKFLOORCLIP(), + MySegs.getCeilingClip(), MySegs.getFloorClip(), columnofs, view.xtoviewangle, + ylookup, vp_vars.visplanes, this.visplanebarrier, colormaps + ); + // SegExecutor sticks to its own half (or 1/nth) of the screen. + parallelSegs.RSIExec[i].setScreenRange(i * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS), (i + 1) * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS)); + detailaware.add(parallelSegs.RSIExec[i]); + } + + for (int i = 0; i < NUMFLOORTHREADS; i++) { + final VisplaneWorker2 w = new VisplaneWorker2.HiColor( + DOOM, this, i, columnofs, ylookup, screen, visplanebarrier, NUMFLOORTHREADS + ); + vpw[i] = w; + detailaware.add(w); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.HiColor( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, + screen, maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + @Override + protected void InitColormaps() throws IOException { + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS15 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + // Pointless, since we don't use indexes. Instead, a half-brite + // processing works just fine. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTLColumn = new R_DrawTLColumn(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + } + + public static final class TrueColor extends ParallelRenderer2 { + + public TrueColor(DoomMain DM, int wallthread, int floorthreads, int nummaskedthreads) { + super(DM, wallthread, floorthreads, nummaskedthreads); + + // Init light levels + colormaps.scalelight = new int[colormaps.lightLevels()][colormaps.maxLightScale()][]; + colormaps.scalelightfixed = new int[colormaps.maxLightScale()][]; + colormaps.zlight = new int[colormaps.lightLevels()][colormaps.maxLightZ()][]; + + completeInit(); + } + + @Override + @SuppressWarnings("unchecked") + protected void InitRSISubsystem() { + // int[] offsets = new int[NUMWALLTHREADS]; + final ParallelSegs2 parallelSegs = ((ParallelSegs2) MySegs); + for (int i = 0; i < NUMWALLTHREADS; i++) { + parallelSegs.RSIExec[i] = new RenderSegExecutor.TrueColor( + DOOM, i, screen, TexMan, + parallelSegs.RSI, MySegs.getBLANKCEILINGCLIP(), MySegs.getBLANKFLOORCLIP(), + MySegs.getCeilingClip(), MySegs.getFloorClip(), columnofs, view.xtoviewangle, + ylookup, vp_vars.visplanes, this.visplanebarrier, colormaps + ); + // SegExecutor sticks to its own half (or 1/nth) of the screen. + parallelSegs.RSIExec[i].setScreenRange(i * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS), (i + 1) * (DOOM.vs.getScreenWidth() / NUMWALLTHREADS)); + detailaware.add(parallelSegs.RSIExec[i]); + } + + for (int i = 0; i < NUMFLOORTHREADS; i++) { + final VisplaneWorker2 w = new VisplaneWorker2.TrueColor( + DOOM, this, i, columnofs, ylookup, screen, visplanebarrier, NUMFLOORTHREADS + ); + vpw[i] = w; + detailaware.add(w); + } + } + + @Override + @SuppressWarnings("unchecked") + protected void InitMaskedWorkers() { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + maskedworkers[i] = new MaskedWorker.TrueColor( + DOOM.vs, this, i, ylookup, columnofs, NUMMASKEDTHREADS, screen, + maskedbarrier, BLURRY_MAP + ); + + detailaware.add(maskedworkers[i]); + // "Peg" to sprite manager. + maskedworkers[i].cacheSpriteManager(DOOM.spriteManager); + } + } + + @Override + protected void InitColormaps() throws IOException { + colormaps.colormaps = DOOM.graphicSystem.getColorMap(); + LOGGER.log(Level.FINE, String.format("COLORS15 Colormaps: %d", colormaps.colormaps.length)); + + // MAES: blurry effect is hardcoded to this colormap. + // Pointless, since we don't use indexes. Instead, a half-brite + // processing works just fine. + BLURRY_MAP = DOOM.graphicSystem.getBlurryTable(); + } + + @Override + protected void R_InitDrawingFunctions() { + + // Span functions. Common to all renderers unless overriden + // or unused e.g. parallel renderers ignore them. + DrawSpan = new R_DrawSpanUnrolled.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + DrawSpanLow = new R_DrawSpanLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dsvars, screen, DOOM.doomSystem); + + // Translated columns are usually sprites-only. + DrawTranslatedColumn = new R_DrawTranslatedColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawTranslatedColumnLow = new R_DrawTranslatedColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + //DrawTLColumn=new R_DrawTLColumn(SCREENWIDTH,SCREENHEIGHT,ylookup,columnofs,maskedcvars,screen,I); + + // Fuzzy columns. These are also masked. + DrawFuzzColumn = new R_DrawFuzzColumn.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + DrawFuzzColumnLow = new R_DrawFuzzColumnLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem, BLURRY_MAP); + + // Regular draw for solid columns/walls. Full optimizations. + DrawColumn = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + DrawColumnLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, DOOM.doomSystem); + + // Non-optimized stuff for masked. + DrawColumnMasked = new R_DrawColumnBoom.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + DrawColumnMaskedLow = new R_DrawColumnBoomLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, maskedcvars, screen, DOOM.doomSystem); + + // Player uses masked + DrawColumnPlayer = DrawColumnMasked; // Player normally uses masked. + + // Skies use their own. This is done in order not to stomp parallel threads. + DrawColumnSkies = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + DrawColumnSkiesLow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, skydcvars, screen, DOOM.doomSystem); + + super.R_InitDrawingFunctions(); + } + } +} \ No newline at end of file diff --git a/doom/src/rr/parallel/ParallelThings.java b/doom/src/rr/parallel/ParallelThings.java new file mode 100644 index 0000000..261e208 --- /dev/null +++ b/doom/src/rr/parallel/ParallelThings.java @@ -0,0 +1,194 @@ +package rr.parallel; + +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.AbstractThings; +import rr.IDetailAware; +import rr.SceneRenderer; +import rr.drawfuns.ColVars; +import rr.drawfuns.DcFlags; +import utils.C2JUtils; +import v.scale.VideoScale; +import v.tables.BlurryTable; + +/** + * Parallel Things drawing class, column based, using RMI pipeline. + * For N threads, each thread only draws those columns of sprites that + * are in its own 1/N portion of the screen. + * + * Overrides only the terminal drawing methods from things, using a + * mechanism very similar to column-based wall threading. It's not very + * efficient, since some of the really heavy parts (such as visibility + * priority) are still done serially, and actually do take up a lot of the + * actual rendering time, and the number of columns generated is REALLY + * enormous (100K+ for something like nuts.wad), and the thing chokes on + * synchronization, more than anything. The only appropriate thing to do + * would be to have a per-vissprite renderer, which would actually move much + * of the brunt work away from the main thread. Some interesting benchmarks + * on nuts.wad timedemo: Normal things serial renderer: 60-62 fps "Dummy" + * completeColumns: 72 fps "Dummy" things renderer without final drawing: 80 + * fps "Dummy" things renderer without ANY calculations: 90 fps. This means + * that even a complete parallelization will likely have a quite limited + * impact. + * + * @author velktron + */ +public abstract class ParallelThings extends AbstractThings { + + private static final Logger LOGGER = Loggers.getLogger(ParallelThings.class.getName()); + + // stuff to get from container + /** Render Masked Instuction subsystem. Essentially, a way to split sprite work + * between threads on a column-basis. + */ + protected ColVars[] RMI; + + /** + * Increment this as you submit RMIs to the "queue". Remember to reset to 0 + * when you have drawn everything! + */ + protected int RMIcount = 0; + + protected RenderMaskedExecutor[] RMIExec; + + protected final int NUMMASKEDTHREADS; + protected final CyclicBarrier maskedbarrier; + protected final Executor tp; + + public ParallelThings(VideoScale vs, SceneRenderer R, Executor tp, int numthreads) { + super(vs, R); + this.tp = tp; + this.NUMMASKEDTHREADS = numthreads; + this.maskedbarrier = new CyclicBarrier(NUMMASKEDTHREADS + 1); + } + + @Override + public void DrawMasked() { + + // This just generates the RMI instructions. + super.DrawMasked(); + // This splits the work among threads and fires them up + RenderRMIPipeline(); + + try { + // Wait for them to be done. + maskedbarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + // TODO Auto-generated catch block + LOGGER.log(Level.SEVERE, "ParallelThings DrawMasked failure", e); + } + // TODO Auto-generated catch block + + } + + @Override + public void completeColumn() { + + if (view.detailshift == 1) { + flags = DcFlags.LOW_DETAIL; + } + // Don't wait to go over + if (RMIcount >= RMI.length) { + ResizeRMIBuffer(); + } + + // A deep copy is still necessary, as well as setting dc_flags + RMI[RMIcount].copyFrom(maskedcvars, colfunc.getFlags()); + + // We only need to point to the next one in the list. + RMIcount++; + } + + int flags; + + protected void RenderRMIPipeline() { + + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + + RMIExec[i].setRange((i * this.vs.getScreenWidth()) / NUMMASKEDTHREADS, + ((i + 1) * this.vs.getScreenWidth()) / NUMMASKEDTHREADS); + RMIExec[i].setRMIEnd(RMIcount); + // RWIExec[i].setRange(i%NUMWALLTHREADS,RWIcount,NUMWALLTHREADS); + tp.execute(RMIExec[i]); + } + + // System.out.println("RWI count"+RWIcount); + RMIcount = 0; + } + + protected void ResizeRMIBuffer() { + ColVars fake = new ColVars<>(); + ColVars[] tmp + = // TODO Auto-generated constructor stub + C2JUtils.createArrayOfObjects(fake, RMI.length * 2); + System.arraycopy(RMI, 0, tmp, 0, RMI.length); + + // Bye bye, old RMI. + RMI = tmp; + + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + RMIExec[i].updateRMI(RMI); + } + + LOGGER.log(Level.FINE, String.format("RMI Buffer resized. Actual capacity %d", RMI.length)); + } + + protected abstract void InitRMISubsystem(int[] columnofs, int[] ylookup, V screen, CyclicBarrier maskedbarrier, BlurryTable BLURRY_MAP, List detailaware); + + public static class Indexed extends ParallelThings { + + public Indexed(VideoScale vs, SceneRenderer R, Executor tp, int numthreads) { + super(vs, R, tp, numthreads); + } + + protected void InitRMISubsystem(int[] columnofs, int[] ylookup, byte[] screen, CyclicBarrier maskedbarrier, BlurryTable BLURRY_MAP, List detailaware) { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + RMIExec[i] + = new RenderMaskedExecutor.Indexed(vs.getScreenWidth(), vs.getScreenHeight(), columnofs, + ylookup, screen, RMI, maskedbarrier, I, BLURRY_MAP); + + detailaware.add(RMIExec[i]); + } + } + } + + public static class HiColor extends ParallelThings { + + public HiColor(VideoScale vs, SceneRenderer R, Executor tp, int numthreads) { + super(vs, R, tp, numthreads); + } + + protected void InitRMISubsystem(int[] columnofs, int[] ylookup, short[] screen, CyclicBarrier maskedbarrier, BlurryTable BLURRY_MAP, List detailaware) { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + RMIExec[i] + = new RenderMaskedExecutor.HiColor(vs.getScreenWidth(), vs.getScreenHeight(), columnofs, + ylookup, screen, RMI, maskedbarrier, I, BLURRY_MAP); + + detailaware.add(RMIExec[i]); + } + } + } + + public static class TrueColor extends ParallelThings { + + public TrueColor(VideoScale vs, SceneRenderer R, Executor tp, int numthreads) { + super(vs, R, tp, numthreads); + } + + protected void InitRMISubsystem(int[] columnofs, int[] ylookup, int[] screen, CyclicBarrier maskedbarrier, BlurryTable BLURRY_MAP, List detailaware) { + for (int i = 0; i < NUMMASKEDTHREADS; i++) { + RMIExec[i] + = new RenderMaskedExecutor.TrueColor(vs.getScreenWidth(), vs.getScreenHeight(), columnofs, + ylookup, screen, RMI, maskedbarrier, I, BLURRY_MAP); + + detailaware.add(RMIExec[i]); + } + } + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/ParallelThings2.java b/doom/src/rr/parallel/ParallelThings2.java new file mode 100644 index 0000000..7bba83d --- /dev/null +++ b/doom/src/rr/parallel/ParallelThings2.java @@ -0,0 +1,101 @@ +package rr.parallel; + +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.IMaskedDrawer; +import rr.ISpriteManager; +import rr.IVisSpriteManagement; +import rr.SceneRenderer; +import v.scale.VideoScale; + +/** Alternate parallel sprite renderer using a split-screen strategy. + * For N threads, each thread gets to render only the sprites that are entirely + * in its own 1/Nth portion of the screen. + * + * Sprites that span more than one section, are drawn partially. Each thread + * only has to worry with the priority of its own sprites. Similar to the + * split-seg parallel drawer. + * + * Uses the "masked workers" subsystem, there is no column pipeline: workers + * "tap" directly in the sprite sorted table and act accordingly (draw entirely, + * draw nothing, draw partially). + * + * It uses masked workers to perform the actual work, each of which is a complete + * Thing Drawer. + * + * @author velktron + * + */ +public final class ParallelThings2 implements IMaskedDrawer { + + private static final Logger LOGGER = Loggers.getLogger(ParallelThings2.class.getName()); + + MaskedWorker[] maskedworkers; + CyclicBarrier maskedbarrier; + Executor tp; + protected final IVisSpriteManagement VIS; + protected final VideoScale vs; + + public ParallelThings2(VideoScale vs, SceneRenderer R) { + this.VIS = R.getVisSpriteManager(); + this.vs = vs; + } + + @Override + public void DrawMasked() { + + VIS.SortVisSprites(); + + for (int i = 0; i < maskedworkers.length; i++) { + tp.execute(maskedworkers[i]); + } + + try { + maskedbarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "ParallelThings2 DrawMasked failure", e); + } + // TODO Auto-generated catch block + + } + + @Override + public void completeColumn() { + // Does nothing. Dummy. + } + + @Override + public void setPspriteScale(int scale) { + for (int i = 0; i < maskedworkers.length; i++) { + maskedworkers[i].setPspriteScale(scale); + } + } + + @Override + public void setPspriteIscale(int scale) { + for (int i = 0; i < maskedworkers.length; i++) { + maskedworkers[i].setPspriteIscale(scale); + } + } + + @Override + public void setDetail(int detailshift) { + for (int i = 0; i < maskedworkers.length; i++) { + maskedworkers[i].setDetail(detailshift); + } + + } + + @Override + public void cacheSpriteManager(ISpriteManager SM) { + for (int i = 0; i < maskedworkers.length; i++) { + maskedworkers[i].cacheSpriteManager(SM); + } + + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/RWI.java b/doom/src/rr/parallel/RWI.java new file mode 100644 index 0000000..18177bf --- /dev/null +++ b/doom/src/rr/parallel/RWI.java @@ -0,0 +1,18 @@ +package rr.parallel; + +import rr.drawfuns.ColVars; + +public interface RWI { + + public interface Init { + + RenderWallExecutor[] InitRWIExecutors(int num, ColVars[] RWI); + } + + public interface Get { + + ColVars[] getRWI(); + + void setExecutors(RenderWallExecutor[] RWIExec); + } +} \ No newline at end of file diff --git a/doom/src/rr/parallel/RenderMaskedExecutor.java b/doom/src/rr/parallel/RenderMaskedExecutor.java new file mode 100644 index 0000000..18b5686 --- /dev/null +++ b/doom/src/rr/parallel/RenderMaskedExecutor.java @@ -0,0 +1,199 @@ +package rr.parallel; + +import i.IDoomSystem; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.IDetailAware; +import rr.drawfuns.ColVars; +import rr.drawfuns.DcFlags; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.R_DrawColumnBoom; +import rr.drawfuns.R_DrawColumnBoomLow; +import rr.drawfuns.R_DrawFuzzColumn; +import rr.drawfuns.R_DrawFuzzColumnLow; +import rr.drawfuns.R_DrawTranslatedColumn; +import rr.drawfuns.R_DrawTranslatedColumnLow; +import v.tables.BlurryTable; + +/** + * This is what actual executes the RenderWallInstruction. Essentially it's a + * self-contained column rendering function. + * + * @author velktron + */ +public abstract class RenderMaskedExecutor + implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(RenderMaskedExecutor.class.getName()); + + protected CyclicBarrier barrier; + + protected ColVars[] RMI; + + protected int rmiend; + + protected boolean lowdetail = false; + + protected int start, end; + + protected DoomColumnFunction colfunchi, colfunclow; + protected DoomColumnFunction fuzzfunchi, fuzzfunclow; + protected DoomColumnFunction transfunchi, transfunclow; + + protected DoomColumnFunction colfunc; + + public RenderMaskedExecutor(int SCREENWIDTH, int SCREENHEIGHT, + ColVars[] RMI, CyclicBarrier barrier + ) { + this.RMI = RMI; + this.barrier = barrier; + this.SCREENWIDTH = SCREENWIDTH; + this.SCREENHEIGHT = SCREENHEIGHT; + } + + public void setRange(int start, int end) { + this.end = end; + this.start = start; + } + + public void setDetail(int detailshift) { + lowdetail = detailshift != 0; + } + + public void run() { + + // System.out.println("Wall executor from "+start +" to "+ end); + int dc_flags = 0; + + // Check out ALL valid RMIs, but only draw those on YOUR side of the screen. + for (int i = 0; i < rmiend; i++) { + + if (RMI[i].dc_x >= start && RMI[i].dc_x <= end) { + // Change function type according to flags. + // No flag change means reusing the last used type + dc_flags = RMI[i].dc_flags; + //System.err.printf("Flags transition %d\n",dc_flags); + if (lowdetail) { + if ((dc_flags & DcFlags.FUZZY) != 0) { + colfunc = fuzzfunclow; + } else if ((dc_flags & DcFlags.TRANSLATED) != 0) { + colfunc = transfunclow; + } else { + colfunc = colfunclow; + } + } else { + if ((dc_flags & DcFlags.FUZZY) != 0) { + colfunc = fuzzfunchi; + } else if ((dc_flags & DcFlags.TRANSLATED) != 0) { + colfunc = transfunchi; + } else { + colfunc = colfunchi; + } + } + + // No need to set shared DCvars, because it's passed with the arg. + colfunc.invoke(RMI[i]); + } + } + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "RenderMaskedExecutor run failure", e); + } + } + + public void setRMIEnd(int rmiend) { + this.rmiend = rmiend; + } + + public void updateRMI(ColVars[] RMI) { + this.RMI = RMI; + + } + + /////////////// VIDEO SCALE STUFF////////////////////// + protected final int SCREENWIDTH; + + protected final int SCREENHEIGHT; + + /* + * protected IVideoScale vs; + * @Override public void setVideoScale(IVideoScale vs) { this.vs=vs; } + * @Override public void initScaling() { + * this.SCREENHEIGHT=vs.getScreenHeight(); + * this.SCREENWIDTH=vs.getScreenWidth(); } + */ + public static final class HiColor extends RenderMaskedExecutor { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, short[] screen, ColVars[] RMI, + CyclicBarrier barrier, IDoomSystem I, BlurryTable BLURRY_MAP) { + super(SCREENWIDTH, SCREENHEIGHT, RMI, barrier); + + // Regular masked columns + this.colfunc = new R_DrawColumnBoom.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.colfunclow = new R_DrawColumnBoomLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + // Fuzzy columns + this.fuzzfunchi = new R_DrawFuzzColumn.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + this.fuzzfunclow = new R_DrawFuzzColumnLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + + // Translated columns + this.transfunchi = new R_DrawTranslatedColumn.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.transfunclow = new R_DrawTranslatedColumnLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + } + + } + + public static final class Indexed extends RenderMaskedExecutor { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, byte[] screen, ColVars[] RMI, + CyclicBarrier barrier, IDoomSystem I, BlurryTable BLURRY_MAP) { + super(SCREENWIDTH, SCREENHEIGHT, RMI, barrier); + + // Regular masked columns + this.colfunc = new R_DrawColumnBoom.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.colfunclow = new R_DrawColumnBoomLow.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + // Fuzzy columns + this.fuzzfunchi = new R_DrawFuzzColumn.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + this.fuzzfunclow = new R_DrawFuzzColumnLow.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + + // Translated columns + this.transfunchi = new R_DrawTranslatedColumn.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.transfunclow = new R_DrawTranslatedColumnLow.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + } + + } + + public static final class TrueColor extends RenderMaskedExecutor { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, int[] screen, ColVars[] RMI, + CyclicBarrier barrier, IDoomSystem I, BlurryTable BLURRY_MAP) { + super(SCREENWIDTH, SCREENHEIGHT, RMI, barrier); + + // Regular masked columns + this.colfunc = new R_DrawColumnBoom.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.colfunclow = new R_DrawColumnBoomLow.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + // Fuzzy columns + this.fuzzfunchi = new R_DrawFuzzColumn.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + this.fuzzfunclow = new R_DrawFuzzColumnLow.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I, BLURRY_MAP); + + // Translated columns + this.transfunchi = new R_DrawTranslatedColumn.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + this.transfunclow = new R_DrawTranslatedColumnLow.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, null, screen, I); + + } + + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/RenderSegExecutor.java b/doom/src/rr/parallel/RenderSegExecutor.java new file mode 100644 index 0000000..3c05972 --- /dev/null +++ b/doom/src/rr/parallel/RenderSegExecutor.java @@ -0,0 +1,444 @@ +package rr.parallel; + +import data.Tables; +import static data.Tables.finetangent; +import doom.DoomMain; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import mochadoom.Loggers; +import rr.IDetailAware; +import rr.TextureManager; +import rr.drawfuns.ColVars; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.visplane_t; +import v.tables.LightsAndColors; + +/** This is what actual executes the RenderSegInstructions. + * * + * Each thread actually operates on a FIXED PORTION OF THE SCREEN + * (e.g. half-width, third-width etc.) and only renders the portions + * of the RenderSegInstructions that are completely contained + * within its own screen area. For this reason, all threads + * check out all RenderSegInstructions of the list, and render any + * and all portions that are within their responsability domain, so + * to speak. + * + * FIXME there's a complex data dependency with ceilingclip/floorclip + * I was not quite able to fix yet. Practically, in the serial renderer, + * calls to RenderSegLoop are done in a correct, non-overlapping order, + * and certain parts are drawn before others in order to set current + * floor/ceiling markers and visibility e.g. think of a wall visible + * through windows. + * + * FIXME 7/6/2011 Data dependencies and per-thread clipping are now + * fixed, however there is still visible "jitter" or "noise" on some + * of the walls, probably related to column offsets. + * + * @author velktron + * + */ +public abstract class RenderSegExecutor implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(RenderSegExecutor.class.getName()); + + // This needs to be set by the partitioner. + protected int rw_start, rw_end, rsiend; + // These need to be set on creation, and are unchangeable. + protected final LightsAndColors colormaps; + protected final TextureManager TexMan; + protected final CyclicBarrier barrier; + protected RenderSegInstruction[] RSI; + protected final long[] xtoviewangle; + protected final short[] ceilingclip, floorclip; + + // Each thread should do its own ceiling/floor blanking + protected final short[] BLANKFLOORCLIP; + protected final short[] BLANKCEILINGCLIP; + + protected static final int HEIGHTBITS = 12; + protected static final int HEIGHTUNIT = (1 << HEIGHTBITS); + protected final int id; + + protected DoomColumnFunction colfunchi, colfunclow; + protected DoomColumnFunction colfunc; + protected ColVars dcvars; + protected final DoomMain DOOM; + + public RenderSegExecutor(DoomMain DOOM, int id, V screen, + TextureManager texman, + RenderSegInstruction[] RSI, + short[] BLANKCEILINGCLIP, + short[] BLANKFLOORCLIP, + short[] ceilingclip, + short[] floorclip, + int[] columnofs, + long[] xtoviewangle, + int[] ylookup, + visplane_t[] visplanes, + CyclicBarrier barrier, + LightsAndColors colormaps) { + this.id = id; + this.TexMan = texman; + this.RSI = RSI; + this.barrier = barrier; + this.ceilingclip = ceilingclip; + this.floorclip = floorclip; + this.xtoviewangle = xtoviewangle; + this.BLANKCEILINGCLIP = BLANKCEILINGCLIP; + this.BLANKFLOORCLIP = BLANKFLOORCLIP; + this.colormaps = colormaps; + this.DOOM = DOOM; + } + + protected final void ProcessRSI(RenderSegInstruction rsi, int startx, int endx, boolean contained) { + int angle; // angle_t + int index; + int yl; // low + int yh; // hight + int mid; + int pixlow, pixhigh, pixhighstep, pixlowstep; + int rw_scale, topfrac, bottomfrac, bottomstep; + // These are going to be modified A LOT, so we cache them here. + pixhighstep = rsi.pixhighstep; + pixlowstep = rsi.pixlowstep; + bottomstep = rsi.bottomstep; + // We must re-scale it. + int rw_scalestep = rsi.rw_scalestep; + int topstep = rsi.topstep; + int texturecolumn = 0; // fixed_t + final int bias; + // Well is entirely contained in our screen zone + // (or the very least it starts in it). + if (contained) { + bias = 0; + } // We are continuing a wall that started in another + // screen zone. + else { + bias = (startx - rsi.rw_x); + } + // PROBLEM: these must be pre-biased when multithreading. + rw_scale = rsi.rw_scale + bias * rw_scalestep; + topfrac = rsi.topfrac + bias * topstep; + bottomfrac = rsi.bottomfrac + bias * bottomstep; + pixlow = rsi.pixlow + bias * pixlowstep; + pixhigh = rsi.pixhigh + bias * pixhighstep; + + { + + for (int rw_x = startx; rw_x < endx; rw_x++) { + // mark floor / ceiling areas + yl = (topfrac + HEIGHTUNIT - 1) >> HEIGHTBITS; + + // no space above wall? + if (yl < ceilingclip[rw_x] + 1) { + yl = ceilingclip[rw_x] + 1; + } + + yh = bottomfrac >> HEIGHTBITS; + + if (yh >= floorclip[rw_x]) { + yh = floorclip[rw_x] - 1; + } + + // System.out.printf("Thread: rw %d yl %d yh %d\n",rw_x,yl,yh); + // A particular seg has been identified as a floor marker. + // texturecolumn and lighting are independent of wall tiers + if (rsi.segtextured) { + // calculate texture offset + + // CAREFUL: a VERY anomalous point in the code. Their sum is supposed + // to give an angle not exceeding 45 degrees (or 0x0FFF after shifting). + // If added with pure unsigned rules, this doesn't hold anymore, + // not even if accounting for overflow. + angle = Tables.toBAMIndex(rsi.rw_centerangle + (int) xtoviewangle[rw_x]); + //angle = (int) (((rw_centerangle + xtoviewangle[rw_x])&BITS31)>>>ANGLETOFINESHIFT); + //angle&=0x1FFF; + + // FIXME: We are accessing finetangent here, the code seems pretty confident + // in that angle won't exceed 4K no matter what. But xtoviewangle + // alone can yield 8K when shifted. + // This usually only overflows if we idclip and look at certain directions + // (probably angles get fucked up), however it seems rare enough to just + // "swallow" the exception. You can eliminate it by anding with 0x1FFF + // if you're so inclined. + texturecolumn = rsi.rw_offset - FixedMul(finetangent[angle], rsi.rw_distance); + texturecolumn >>= FRACBITS; + // calculate lighting + index = rw_scale >> colormaps.lightScaleShift(); + + if (index >= colormaps.maxLightScale()) { + index = colormaps.maxLightScale() - 1; + } + + dcvars.dc_colormap = rsi.walllights[index]; + dcvars.dc_x = rw_x; + dcvars.dc_iscale = (int) (0xffffffffL / rw_scale); + } + + // draw the wall tiers + if (rsi.midtexture != 0) { + // single sided line + dcvars.dc_yl = yl; + dcvars.dc_yh = yh; + dcvars.dc_texheight = TexMan.getTextureheight(rsi.midtexture) >> FRACBITS; // killough + dcvars.dc_texturemid = rsi.rw_midtexturemid; + dcvars.dc_source = TexMan.GetCachedColumn(rsi.midtexture, texturecolumn); + dcvars.dc_source_ofs = 0; + colfunc.invoke(); + ceilingclip[rw_x] = (short) rsi.viewheight; + floorclip[rw_x] = -1; + } else { + // two sided line + if (rsi.toptexture != 0) { + // top wall + mid = pixhigh >> HEIGHTBITS; + pixhigh += pixhighstep; + + if (mid >= floorclip[rw_x]) { + mid = floorclip[rw_x] - 1; + } + + if (mid >= yl) { + dcvars.dc_yl = yl; + dcvars.dc_yh = mid; + dcvars.dc_texturemid = rsi.rw_toptexturemid; + dcvars.dc_texheight = TexMan.getTextureheight(rsi.toptexture) >> FRACBITS; + dcvars.dc_source = TexMan.GetCachedColumn(rsi.toptexture, texturecolumn); + //dc_source_ofs=0; + colfunc.invoke(); + ceilingclip[rw_x] = (short) mid; + } else { + ceilingclip[rw_x] = (short) (yl - 1); + } + } // if toptexture + else { + // no top wall + if (rsi.markceiling) { + ceilingclip[rw_x] = (short) (yl - 1); + } + } + + if (rsi.bottomtexture != 0) { + // bottom wall + mid = (pixlow + HEIGHTUNIT - 1) >> HEIGHTBITS; + pixlow += pixlowstep; + + // no space above wall? + if (mid <= ceilingclip[rw_x]) { + mid = ceilingclip[rw_x] + 1; + } + + if (mid <= yh) { + dcvars.dc_yl = mid; + dcvars.dc_yh = yh; + dcvars.dc_texturemid = rsi.rw_bottomtexturemid; + dcvars.dc_texheight = TexMan.getTextureheight(rsi.bottomtexture) >> FRACBITS; + dcvars.dc_source = TexMan.GetCachedColumn(rsi.bottomtexture, texturecolumn); + // dc_source_ofs=0; + colfunc.invoke(); + floorclip[rw_x] = (short) mid; + } else { + floorclip[rw_x] = (short) (yh + 1); + } + + } // end-bottomtexture + else { + // no bottom wall + if (rsi.markfloor) { + floorclip[rw_x] = (short) (yh + 1); + } + } + + } // end-else (two-sided line) + rw_scale += rw_scalestep; + topfrac += topstep; + bottomfrac += bottomstep; + } // end-rw + } // end-block + } + + @Override + public void setDetail(int detailshift) { + if (detailshift == 0) { + colfunc = colfunchi; + } else { + colfunc = colfunclow; + } + } + + /** Only called once per screen width change */ + public void setScreenRange(int rwstart, int rwend) { + this.rw_end = rwend; + this.rw_start = rwstart; + } + + /** How many instructions TOTAL are there to wade through. + * Not all will be executed on one thread, except in some rare + * circumstances. + * + * @param rsiend + */ + public void setRSIEnd(int rsiend) { + this.rsiend = rsiend; + } + + public void run() { + + RenderSegInstruction rsi; + + // Each worker blanks its own portion of the floor/ceiling clippers. + System.arraycopy(BLANKFLOORCLIP, rw_start, floorclip, rw_start, rw_end - rw_start); + System.arraycopy(BLANKCEILINGCLIP, rw_start, ceilingclip, rw_start, rw_end - rw_start); + + // For each "SegDraw" instruction... + for (int i = 0; i < rsiend; i++) { + rsi = RSI[i]; + dcvars.centery = RSI[i].centery; + int startx, endx; + // Does a wall actually start in our screen zone? + // If yes, we need no bias, since it was meant for it. + // If the wall started BEFORE our zone, then we + // will need to add a bias to it (see ProcessRSI). + // If its entirely non-contained, ProcessRSI won't be + // called anyway, so we don't need to check for the end. + + boolean contained = (rsi.rw_x >= rw_start); + // Keep to your part of the screen. It's possible that several + // threads will process the same RSI, but different parts of it. + + // Trim stuff that starts before our rw_start position. + startx = Math.max(rsi.rw_x, rw_start); + // Similarly, trim stuff after our rw_end position. + endx = Math.min(rsi.rw_stopx, rw_end); + // Is there anything to actually draw? + if ((endx - startx) > 0) { + ProcessRSI(rsi, startx, endx, contained); + } + } // end-instruction + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "RenderSegExecutor run failure", e); + } + // TODO Auto-generated catch block + + } + + //protected abstract void ProcessRSI(RenderSegInstruction rsi, int startx,int endx,boolean contained); + ////////////////////////////VIDEO SCALE STUFF //////////////////////////////// + public void updateRSI(RenderSegInstruction[] rsi) { + this.RSI = rsi; + } + + public static final class TrueColor extends RenderSegExecutor { + + public TrueColor(DoomMain DOOM, int id, + int[] screen, TextureManager texman, + RenderSegInstruction[] RSI, short[] BLANKCEILINGCLIP, + short[] BLANKFLOORCLIP, short[] ceilingclip, short[] floorclip, + int[] columnofs, long[] xtoviewangle, int[] ylookup, + visplane_t[] visplanes, CyclicBarrier barrier, LightsAndColors colormaps) { + super(DOOM, id, screen, texman, RSI, BLANKCEILINGCLIP, + BLANKFLOORCLIP, ceilingclip, floorclip, columnofs, xtoviewangle, + ylookup, visplanes, barrier, colormaps); + dcvars = new ColVars<>(); + colfunc = colfunchi = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + colfunclow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + } + } + + public static final class HiColor extends RenderSegExecutor { + + public HiColor(DoomMain DOOM, int id, + short[] screen, TextureManager texman, + RenderSegInstruction[] RSI, short[] BLANKCEILINGCLIP, + short[] BLANKFLOORCLIP, short[] ceilingclip, short[] floorclip, + int[] columnofs, long[] xtoviewangle, int[] ylookup, + visplane_t[] visplanes, CyclicBarrier barrier, LightsAndColors colormaps) { + super(DOOM, id, screen, texman, RSI, BLANKCEILINGCLIP, + BLANKFLOORCLIP, ceilingclip, floorclip, columnofs, xtoviewangle, + ylookup, visplanes, barrier, colormaps); + dcvars = new ColVars<>(); + colfunc = colfunchi = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + colfunclow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + } + } + + public static final class Indexed extends RenderSegExecutor { + + public Indexed(DoomMain DOOM, int id, + byte[] screen, TextureManager texman, + RenderSegInstruction[] RSI, short[] BLANKCEILINGCLIP, + short[] BLANKFLOORCLIP, short[] ceilingclip, short[] floorclip, + int[] columnofs, long[] xtoviewangle, int[] ylookup, + visplane_t[] visplanes, CyclicBarrier barrier, LightsAndColors colormaps) { + super(DOOM, id, screen, texman, RSI, BLANKCEILINGCLIP, + BLANKFLOORCLIP, ceilingclip, floorclip, columnofs, xtoviewangle, + ylookup, visplanes, barrier, colormaps); + dcvars = new ColVars<>(); + colfunc = colfunchi = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + colfunclow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, dcvars, screen, null); + } + } + +} + +// $Log: RenderSegExecutor.java,v $ +// Revision 1.2 2012/09/24 17:16:22 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.1.2.2 2012/09/21 16:18:29 velktron +// More progress...but no cigar, yet. +// +// Revision 1.1.2.1 2012/09/20 14:20:10 velktron +// Parallel stuff in their own package (STILL BROKEN) +// +// Revision 1.12.2.4 2012/09/19 21:45:41 velktron +// More extensions... +// +// Revision 1.12.2.3 2012/09/18 16:11:50 velktron +// Started new "all in one" approach for unifying Indexed, HiColor and (future) TrueColor branches. +// +// Revision 1.12.2.2 2012/06/15 14:44:09 velktron +// Doesn't need walllights? +// +// Revision 1.12.2.1 2011/11/14 00:27:11 velktron +// A barely functional HiColor branch. Most stuff broken. DO NOT USE +// +// Revision 1.12 2011/11/01 19:04:06 velktron +// Cleaned up, using IDetailAware for subsystems. +// +// Revision 1.11 2011/10/31 18:33:56 velktron +// Much cleaner implementation using the new rendering model. Now it's quite faster, too. +// +// Revision 1.10 2011/07/25 11:39:10 velktron +// Optimized to work without dc_source_ofs (uses only cached, solid textures) +// +// Revision 1.9 2011/07/12 16:29:35 velktron +// Now using GetCachedColumn +// +// Revision 1.8 2011/07/12 16:25:02 velktron +// Removed dependency on per-thread column pointers. +// +// Revision 1.7 2011/06/07 21:21:15 velktron +// Definitively fixed jitter bug, which was due to dc_offset_contention. Now the alternative parallel renderer is just as good as the original one. +// +// Revision 1.6 2011/06/07 13:35:38 velktron +// Definitively fixed drawing priority/zones. Now to solve the jitter :-/ +// +// Revision 1.5 2011/06/07 01:32:32 velktron +// Parallel Renderer 2 still buggy :-( +// +// Revision 1.4 2011/06/07 00:50:47 velktron +// Alternate Parallel Renderer fixed. +// +// Revision 1.3 2011/06/07 00:11:11 velktron +// Fixed alternate parallel renderer (seg based). No longer deprecated. +// \ No newline at end of file diff --git a/doom/src/rr/parallel/RenderSegInstruction.java b/doom/src/rr/parallel/RenderSegInstruction.java new file mode 100644 index 0000000..3115054 --- /dev/null +++ b/doom/src/rr/parallel/RenderSegInstruction.java @@ -0,0 +1,23 @@ +package rr.parallel; + +/** This is all the information needed to draw a particular SEG. + * It's quite a lot, actually, but much better than in testing + * versions. + * + */ +public class RenderSegInstruction { + + public int rw_x, rw_stopx; + public int toptexture, midtexture, bottomtexture; + public int pixhigh, pixlow, pixhighstep, pixlowstep, + topfrac, topstep, bottomfrac, bottomstep; + public boolean segtextured, markfloor, markceiling; + public long rw_centerangle; // angle_t + /** fixed_t */ + public int rw_offset, rw_distance, rw_scale, + rw_scalestep, rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid; + public int viewheight; + V[] walllights; + public int centery; + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/RenderWallExecutor.java b/doom/src/rr/parallel/RenderWallExecutor.java new file mode 100644 index 0000000..207c766 --- /dev/null +++ b/doom/src/rr/parallel/RenderWallExecutor.java @@ -0,0 +1,140 @@ +package rr.parallel; + +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.IDetailAware; +import rr.drawfuns.ColVars; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; + +/** + * This is what actual executes the RenderWallInstruction. Essentially it's a + * self-contained column rendering function. + * + * @author admin + */ +public class RenderWallExecutor + implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(RenderWallExecutor.class.getName()); + + protected CyclicBarrier barrier; + + protected ColVars[] RWI; + + protected int start, end; + + protected DoomColumnFunction colfunchi, colfunclow; + + protected DoomColumnFunction colfunc; + + public RenderWallExecutor(int SCREENWIDTH, int SCREENHEIGHT, + int[] columnofs, int[] ylookup, V screen, + ColVars[] RWI, CyclicBarrier barrier) { + this.RWI = RWI; + this.barrier = barrier; + this.SCREENWIDTH = SCREENWIDTH; + this.SCREENHEIGHT = SCREENHEIGHT; + + } + + public void setRange(int start, int end) { + this.end = end; + this.start = start; + } + + public void setDetail(int detailshift) { + if (detailshift == 0) { + colfunc = colfunchi; + } else { + colfunc = colfunclow; + } + } + + public void run() { + + // System.out.println("Wall executor from "+start +" to "+ end); + for (int i = start; i < end; i++) { + colfunc.invoke(RWI[i]); + } + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "RenderWallExecutor run failure", e); + } + } + + public void updateRWI(ColVars[] RWI) { + this.RWI = RWI; + + } + + /////////////// VIDEO SCALE STUFF////////////////////// + protected final int SCREENWIDTH; + + protected final int SCREENHEIGHT; + + /* + * protected IVideoScale vs; + * @Override public void setVideoScale(IVideoScale vs) { this.vs=vs; } + * @Override public void initScaling() { + * this.SCREENHEIGHT=vs.getScreenHeight(); + * this.SCREENWIDTH=vs.getScreenWidth(); } + */ + public static final class HiColor extends RenderWallExecutor { + + public HiColor(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, short[] screen, + ColVars[] RWI, CyclicBarrier barrier) { + super(SCREENWIDTH, SCREENHEIGHT, columnofs, ylookup, screen, RWI, barrier); + colfunc + = colfunchi + = new R_DrawColumnBoomOpt.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + colfunclow + = new R_DrawColumnBoomOptLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + } + + } + + public static final class Indexed extends RenderWallExecutor { + + public Indexed(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, byte[] screen, + ColVars[] RWI, CyclicBarrier barrier) { + super(SCREENWIDTH, SCREENHEIGHT, columnofs, ylookup, screen, RWI, barrier); + colfunc + = colfunchi + = new R_DrawColumnBoomOpt.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + colfunclow + = new R_DrawColumnBoomOptLow.Indexed(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + } + + } + + public static final class TrueColor extends RenderWallExecutor { + + public TrueColor(int SCREENWIDTH, int SCREENHEIGHT, int[] columnofs, + int[] ylookup, int[] screen, + ColVars[] RWI, CyclicBarrier barrier) { + super(SCREENWIDTH, SCREENHEIGHT, columnofs, ylookup, screen, RWI, barrier); + colfunc + = colfunchi + = new R_DrawColumnBoomOpt.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + colfunclow + = new R_DrawColumnBoomOptLow.TrueColor(SCREENWIDTH, SCREENHEIGHT, ylookup, + columnofs, null, screen, null); + } + + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/VisplaneWorker.java b/doom/src/rr/parallel/VisplaneWorker.java new file mode 100644 index 0000000..08a14da --- /dev/null +++ b/doom/src/rr/parallel/VisplaneWorker.java @@ -0,0 +1,188 @@ +package rr.parallel; + +import static data.Defines.ANGLETOSKYSHIFT; +import static data.Tables.addAngles; +import doom.DoomMain; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import mochadoom.Loggers; +import rr.IDetailAware; +import rr.PlaneDrawer; +import rr.SceneRenderer; +import rr.drawfuns.ColVars; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.DoomSpanFunction; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.drawfuns.R_DrawSpanLow; +import rr.drawfuns.R_DrawSpanUnrolled; +import rr.drawfuns.SpanVars; +import rr.visplane_t; +import v.graphics.Palettes; + +/** Visplane worker which shares work in an equal-visplane number strategy + * with other workers. Might be unbalanced if one worker gets too large + * visplanes and others get smaller ones. Balancing strategy is applied in + * run(), otherwise it's practically similar to a PlaneDrwer. + * + * + * @author velktron + * + */ +public abstract class VisplaneWorker extends PlaneDrawer implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(VisplaneWorker.class.getName()); + + // Private to each thread. + protected final int id; + protected final int NUMFLOORTHREADS; + protected final CyclicBarrier barrier; + + protected int vpw_planeheight; + protected V[] vpw_planezlight; + protected int vpw_basexscale, vpw_baseyscale; + + protected SpanVars vpw_dsvars; + protected ColVars vpw_dcvars; + + // OBVIOUSLY each thread must have its own span functions. + protected DoomSpanFunction vpw_spanfunc; + protected DoomColumnFunction vpw_skyfunc; + protected DoomSpanFunction vpw_spanfunchi; + protected DoomSpanFunction vpw_spanfunclow; + protected DoomColumnFunction vpw_skyfunchi; + protected DoomColumnFunction vpw_skyfunclow; + + public VisplaneWorker(DoomMain DOOM, int id, int SCREENWIDTH, int SCREENHEIGHT, SceneRenderer R, CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, R); + this.barrier = visplanebarrier; + this.id = id; + this.NUMFLOORTHREADS = NUMFLOORTHREADS; + } + + public static final class HiColor extends VisplaneWorker { + + public HiColor(DoomMain DOOM, int id, int SCREENWIDTH, int SCREENHEIGHT, SceneRenderer R, + int[] columnofs, int[] ylookup, short[] screen, + CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, id, SCREENWIDTH, SCREENHEIGHT, R, visplanebarrier, NUMFLOORTHREADS); + // Alias to those of Planes. + + vpw_dsvars = new SpanVars<>(); + vpw_dcvars = new ColVars<>(); + vpw_spanfunc = vpw_spanfunchi = new R_DrawSpanUnrolled.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, vpw_dsvars, screen, I); + vpw_spanfunclow = new R_DrawSpanLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, vpw_dsvars, screen, I); + vpw_skyfunc = vpw_skyfunchi = new R_DrawColumnBoomOpt.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, vpw_dcvars, screen, I); + vpw_skyfunclow = new R_DrawColumnBoomOptLow.HiColor(SCREENWIDTH, SCREENHEIGHT, ylookup, columnofs, vpw_dcvars, screen, I); + + } + + } + + public void setDetail(int detailshift) { + if (detailshift == 0) { + vpw_spanfunc = vpw_spanfunchi; + vpw_skyfunc = vpw_skyfunchi; + } else { + vpw_spanfunc = vpw_spanfunclow; + vpw_skyfunc = vpw_skyfunclow; + } + } + + @Override + public void run() { + visplane_t pln = null; //visplane_t + // These must override the global ones + int light; + int x; + int stop; + int angle; + + // Now it's a good moment to set them. + vpw_basexscale = vpvars.getBaseXScale(); + vpw_baseyscale = vpvars.getBaseYScale(); + + // TODO: find a better way to split work. As it is, it's very uneven + // and merged visplanes in particular are utterly dire. + for (int pl = this.id; pl < vpvars.lastvisplane; pl += NUMFLOORTHREADS) { + pln = vpvars.visplanes[pl]; + // System.out.println(id +" : "+ pl); + + if (pln.minx > pln.maxx) { + continue; + } + + // sky flat + if (pln.picnum == TexMan.getSkyFlatNum()) { + // Cache skytexture stuff here. They aren't going to change while + // being drawn, after all, are they? + int skytexture = TexMan.getSkyTexture(); + // MAES: these must be updated to keep up with screen size changes. + vpw_dcvars.viewheight = view.height; + vpw_dcvars.centery = view.centery; + vpw_dcvars.dc_texheight = TexMan.getTextureheight(skytexture) >> FRACBITS; + vpw_dcvars.dc_iscale = vpvars.getSkyScale() >> view.detailshift; + + vpw_dcvars.dc_colormap = colormap.colormaps[Palettes.COLORMAP_FIXED]; + vpw_dcvars.dc_texturemid = TexMan.getSkyTextureMid(); + for (x = pln.minx; x <= pln.maxx; x++) { + + vpw_dcvars.dc_yl = pln.getTop(x); + vpw_dcvars.dc_yh = pln.getBottom(x); + + if (vpw_dcvars.dc_yl <= vpw_dcvars.dc_yh) { + angle = (int) (addAngles(view.angle, view.xtoviewangle[x]) >>> ANGLETOSKYSHIFT); + vpw_dcvars.dc_x = x; + // Optimized: texheight is going to be the same during normal skies drawing...right? + vpw_dcvars.dc_source = TexMan.GetCachedColumn(TexMan.getSkyTexture(), angle); + vpw_skyfunc.invoke(); + } + } + continue; + } + + // regular flat + vpw_dsvars.ds_source = TexMan.getSafeFlat(pln.picnum); + + vpw_planeheight = Math.abs(pln.height - view.z); + light = (pln.lightlevel >>> colormap.lightSegShift()) + colormap.extralight; + + if (light >= colormap.lightLevels()) { + light = colormap.lightLevels() - 1; + } + + if (light < 0) { + light = 0; + } + + vpw_planezlight = colormap.zlight[light]; + + // We set those values at the border of a plane's top to a "sentinel" value...ok. + pln.setTop(pln.maxx + 1, (char) 0xffff); + pln.setTop(pln.minx - 1, (char) 0xffff); + + stop = pln.maxx + 1; + + for (x = pln.minx; x <= stop; x++) { + MakeSpans(x, pln.getTop(x - 1), + pln.getBottom(x - 1), + pln.getTop(x), + pln.getBottom(x)); + } + + } + // We're done, wait. + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "VisplaneWorker run failure", e); + } + // TODO Auto-generated catch block + + } + +} \ No newline at end of file diff --git a/doom/src/rr/parallel/VisplaneWorker2.java b/doom/src/rr/parallel/VisplaneWorker2.java new file mode 100644 index 0000000..b7639e9 --- /dev/null +++ b/doom/src/rr/parallel/VisplaneWorker2.java @@ -0,0 +1,376 @@ +package rr.parallel; + +import static data.Defines.ANGLETOSKYSHIFT; +import static data.Tables.addAngles; +import doom.DoomMain; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import mochadoom.Loggers; +import rr.IDetailAware; +import rr.PlaneDrawer; +import rr.SceneRenderer; +import rr.drawfuns.ColVars; +import rr.drawfuns.DoomColumnFunction; +import rr.drawfuns.DoomSpanFunction; +import rr.drawfuns.R_DrawColumnBoomOpt; +import rr.drawfuns.R_DrawColumnBoomOptLow; +import rr.drawfuns.R_DrawSpanLow; +import rr.drawfuns.R_DrawSpanUnrolled; +import rr.drawfuns.SpanVars; +import rr.visplane_t; +import v.graphics.Palettes; + +/** Visplane worker which shares work in an equal screen-portions strategy. + * + * More balanced, but requires careful synchronization to avoid overdrawing and + * stomping. + * + * + * @author vepitrop. + * + * TODO: fix crashes + */ +public abstract class VisplaneWorker2 extends PlaneDrawer implements Runnable, IDetailAware { + + private static final Logger LOGGER = Loggers.getLogger(VisplaneWorker2.class.getName()); + + protected final int id; + protected final int NUMFLOORTHREADS; + protected int startvp; + protected int endvp; + protected int vpw_planeheight; + protected V[] vpw_planezlight; + protected int vpw_basexscale, vpw_baseyscale; + protected final SpanVars vpw_dsvars; + protected final ColVars vpw_dcvars; + protected DoomSpanFunction vpw_spanfunc; + protected DoomColumnFunction vpw_skyfunc; + protected DoomSpanFunction vpw_spanfunchi; + protected DoomSpanFunction vpw_spanfunclow; + protected DoomColumnFunction vpw_skyfunchi; + protected DoomColumnFunction vpw_skyfunclow; + protected visplane_t pln; + + public VisplaneWorker2(DoomMain DOOM, SceneRenderer R, int id, CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, R); + this.barrier = visplanebarrier; + this.id = id; + // Alias to those of Planes. + vpw_dsvars = new SpanVars<>(); + vpw_dcvars = new ColVars<>(); + this.NUMFLOORTHREADS = NUMFLOORTHREADS; + } + + public static class HiColor extends VisplaneWorker2 { + + public HiColor(DoomMain DOOM, SceneRenderer R, int id, + int[] columnofs, int[] ylookup, short[] screen, + CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, R, id, visplanebarrier, NUMFLOORTHREADS); + vpw_spanfunc = vpw_spanfunchi = new R_DrawSpanUnrolled.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_spanfunclow = new R_DrawSpanLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_skyfunc = vpw_skyfunchi = new R_DrawColumnBoomOpt.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + vpw_skyfunclow = new R_DrawColumnBoomOptLow.HiColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + } + } + + public static class Indexed extends VisplaneWorker2 { + + public Indexed(DoomMain DOOM, SceneRenderer R, int id, + int[] columnofs, int[] ylookup, byte[] screen, + CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, R, id, visplanebarrier, NUMFLOORTHREADS); + vpw_spanfunc = vpw_spanfunchi = new R_DrawSpanUnrolled.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_spanfunclow = new R_DrawSpanLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_skyfunc = vpw_skyfunchi = new R_DrawColumnBoomOpt.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + vpw_skyfunclow = new R_DrawColumnBoomOptLow.Indexed(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + } + } + + public static class TrueColor extends VisplaneWorker2 { + + public TrueColor(DoomMain DOOM, SceneRenderer R, int id, + int[] columnofs, int[] ylookup, int[] screen, + CyclicBarrier visplanebarrier, int NUMFLOORTHREADS) { + super(DOOM, R, id, visplanebarrier, NUMFLOORTHREADS); + vpw_spanfunc = vpw_spanfunchi = new R_DrawSpanUnrolled.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_spanfunclow = new R_DrawSpanLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dsvars, screen, I); + vpw_skyfunc = vpw_skyfunchi = new R_DrawColumnBoomOpt.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + vpw_skyfunclow = new R_DrawColumnBoomOptLow.TrueColor(DOOM.vs.getScreenWidth(), DOOM.vs.getScreenHeight(), ylookup, columnofs, vpw_dcvars, screen, I); + } + } + + @Override + public void run() { + pln = null; //visplane_t + // These must override the global ones + + int light; + int x; + int stop; + int angle; + int minx, maxx; + + // Now it's a good moment to set them. + vpw_basexscale = vpvars.getBaseXScale(); + vpw_baseyscale = vpvars.getBaseYScale(); + + startvp = ((id * view.width) / NUMFLOORTHREADS); + endvp = (((id + 1) * view.width) / NUMFLOORTHREADS); + + // TODO: find a better way to split work. As it is, it's very uneven + // and merged visplanes in particular are utterly dire. + for (int pl = 0; pl < vpvars.lastvisplane; pl++) { + pln = vpvars.visplanes[pl]; + // System.out.println(id +" : "+ pl); + + // Trivial rejection. + if ((pln.minx > endvp) || (pln.maxx < startvp)) { + continue; + } + + // Reject non-visible + if (pln.minx > pln.maxx) { + continue; + } + + // Trim to zone + minx = Math.max(pln.minx, startvp); + maxx = Math.min(pln.maxx, endvp); + + // sky flat + if (pln.picnum == TexMan.getSkyFlatNum()) { + // Cache skytexture stuff here. They aren't going to change while + // being drawn, after all, are they? + int skytexture = TexMan.getSkyTexture(); + // MAES: these must be updated to keep up with screen size changes. + vpw_dcvars.viewheight = view.height; + vpw_dcvars.centery = view.centery; + vpw_dcvars.dc_texheight = TexMan.getTextureheight(skytexture) >> FRACBITS; + vpw_dcvars.dc_iscale = vpvars.getSkyScale() >> view.detailshift; + + vpw_dcvars.dc_colormap = colormap.colormaps[Palettes.COLORMAP_FIXED]; + vpw_dcvars.dc_texturemid = TexMan.getSkyTextureMid(); + for (x = minx; x <= maxx; x++) { + + vpw_dcvars.dc_yl = pln.getTop(x); + vpw_dcvars.dc_yh = pln.getBottom(x); + + if (vpw_dcvars.dc_yl <= vpw_dcvars.dc_yh) { + angle = (int) (addAngles(view.angle, view.xtoviewangle[x]) >>> ANGLETOSKYSHIFT); + vpw_dcvars.dc_x = x; + vpw_dcvars.dc_texheight = TexMan.getTextureheight(TexMan.getSkyTexture()) >> FRACBITS; + vpw_dcvars.dc_source = TexMan.GetCachedColumn(TexMan.getSkyTexture(), angle); + vpw_skyfunc.invoke(); + } + } + continue; + } + + // regular flat + vpw_dsvars.ds_source = TexMan.getSafeFlat(pln.picnum); + vpw_planeheight = Math.abs(pln.height - view.z); + light = (pln.lightlevel >>> colormap.lightSegShift()) + colormap.extralight; + + if (light >= colormap.lightLevels()) { + light = colormap.lightLevels() - 1; + } + + if (light < 0) { + light = 0; + } + + vpw_planezlight = colormap.zlight[light]; + + // Some tinkering required to make sure visplanes + // don't end prematurely on each other's stop markers + char value = pln.getTop(maxx + 1); + if (!isMarker(value)) { // is it a marker? + value |= visplane_t.SENTINEL; // Mark it so. + value &= visplane_t.THREADIDCLEAR; //clear id bits + value |= (id << visplane_t.THREADIDSHIFT); // set our own id. + } // Otherwise, it was set by another thread. + // Leave it be. + + pln.setTop(maxx + 1, value); + + value = pln.getTop(minx - 1); + if (!isMarker(value)) { // is it a marker? + value |= visplane_t.SENTINEL; // Mark it so. + value &= visplane_t.THREADIDCLEAR; //clear id bits + value |= (id << visplane_t.THREADIDSHIFT); // set our own id. + } // Otherwise, it was set by another thread. + // Leave it be. + + pln.setTop(minx - 1, value); + + stop = maxx + 1; + + for (x = minx; x <= stop; x++) { + MakeSpans(x, pln.getTop(x - 1), + pln.getBottom(x - 1), + pln.getTop(x), + pln.getBottom(x)); + } + + } + // We're done, wait. + + try { + barrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, "VisplaneWorker2 run failure", e); + } + // TODO Auto-generated catch block + } + + private boolean isMarker(int t1) { + return ((t1 & visplane_t.SENTINEL) != 0); + } + + private int decodeID(int t1) { + return (t1 & visplane_t.THREADIDBITS) >> visplane_t.THREADIDSHIFT; + } + + private int decodeValue(int t1) { + return t1 & visplane_t.THREADVALUEBITS; + } + + @Override + public void setDetail(int detailshift) { + if (detailshift == 0) { + vpw_spanfunc = vpw_spanfunchi; + vpw_skyfunc = vpw_skyfunchi; + } else { + vpw_spanfunc = vpw_spanfunclow; + vpw_skyfunc = vpw_skyfunclow; + } + } + + /** + * R_MakeSpans + * + * Called only by DrawPlanes. + * If you wondered where the actual boundaries for the visplane + * flood-fill are laid out, this is it. + * + * The system of coords seems to be defining a sort of cone. + * + * + * @param x Horizontal position + * @param t1 Top-left y coord? + * @param b1 Bottom-left y coord? + * @param t2 Top-right y coord ? + * @param b2 Bottom-right y coord ? + * + */ + @Override + protected final void MakeSpans(int x, int t1, int b1, int t2, int b2) { + + // Top 1 sentinel encountered. + if (isMarker(t1)) { + if (decodeID(t1) != id) // We didn't put it here. + { + t1 = decodeValue(t1); + } + } + + // Top 2 sentinel encountered. + if (isMarker(t2)) { + if (decodeID(t2) != id) { + t2 = decodeValue(t2); + } + } + + super.MakeSpans(x, t1, b1, t2, b2); + } + + /** + * R_MapPlane + * + * Called only by R_MakeSpans. + * + * This is where the actual span drawing function is called. + * + * Uses global vars: + * planeheight + * ds_source -> flat data has already been set. + * basexscale -> actual drawing angle and position is computed from these + * baseyscale + * viewx + * viewy + * + * BASIC PRIMITIVE + */ + /* TODO: entirely similar to serial version? + private void + MapPlane + ( int y, + int x1, + int x2 ) + { + // MAES: angle_t + int angle; + // fixed_t + int distance; + int length; + int index; + + if (RANGECHECK){ + if (x2 < x1 + || x1<0 + || x2>=view.width + || y>view.height) + { + I.Error ("R_MapPlane: %d, %d at %d",x1,x2,y); + } + } + + if (vpw_planeheight != cachedheight[y]) + { + cachedheight[y] = vpw_planeheight; + distance = cacheddistance[y] = FixedMul (vpw_planeheight , yslope[y]); + vpw_dsvars.ds_xstep = cachedxstep[y] = FixedMul (distance,vpw_basexscale); + vpw_dsvars.ds_ystep = cachedystep[y] = FixedMul (distance,vpw_baseyscale); + } + else + { + distance = cacheddistance[y]; + vpw_dsvars.ds_xstep = cachedxstep[y]; + vpw_dsvars.ds_ystep = cachedystep[y]; + } + + length = FixedMul (distance,distscale[x1]); + angle = (int)(((view.angle +xtoviewangle[x1])&BITS32)>>>ANGLETOFINESHIFT); + vpw_dsvars.ds_xfrac = view.x + FixedMul(finecosine[angle], length); + vpw_dsvars.ds_yfrac = -view.y - FixedMul(finesine[angle], length); + + if (colormap.fixedcolormap!=null) + vpw_dsvars.ds_colormap = colormap.fixedcolormap; + else + { + index = distance >>> LIGHTZSHIFT; + + if (index >= MAXLIGHTZ ) + index = MAXLIGHTZ-1; + + vpw_dsvars.ds_colormap = vpw_planezlight[index]; + } + + vpw_dsvars.ds_y = y; + vpw_dsvars.ds_x1 = x1; + vpw_dsvars.ds_x2 = x2; + + // high or low detail + if (view.detailshift==0) + vpw_spanfunc.invoke(); + else + vpw_spanfunclow.invoke(); + } + */ + // Private to each thread. + CyclicBarrier barrier; +} \ No newline at end of file diff --git a/doom/src/rr/patch_t.java b/doom/src/rr/patch_t.java new file mode 100644 index 0000000..a87acca --- /dev/null +++ b/doom/src/rr/patch_t.java @@ -0,0 +1,159 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Hashtable; +import utils.C2JUtils; +import w.CacheableDoomObject; +import w.DoomBuffer; + +//Patches. +//A patch holds one or more columns. +//Patches are used for sprites and all masked pictures, +//and we compose textures from the TEXTURE1/2 lists +//of patches. +public class patch_t implements /*IReadableDoomObject,*/ CacheableDoomObject { + + /** bounding box size */ + public short width, height; + /** pixels to the left of origin */ + public short leftoffset; + /** pixels below the origin */ + public short topoffset; + /** This used to be an implicit array pointing to raw posts of data. + * TODO: get rid of it? It's never used + * only [width] used the [0] is &columnofs[width] */ + public int[] columnofs; + /** The ACTUAL data is here, nicely deserialized (well, almost) */ + public column_t[] columns; + + /** Added for debug aid purposes */ + public String name; + + /** Synthesizing constructor. + * You have to provide the columns yourself, a-posteriori. + * + * @param name + * @param width + * @param height + * @param leftoffset + * @param topoffset + */ + public patch_t(String name, int width, int height, int leftoffset, int topoffset) { + this.name = name; + this.width = (short) width; + this.height = (short) height; + this.leftoffset = (short) leftoffset; + this.columns = new column_t[width]; + } + + public patch_t() { + + } + + /* @Override + public void read(DoomFile f) throws IOException{ + + long pos=f.getFilePointer(); + this.width=f.readLEShort(); + this.height=f.readLEShort(); + this.leftoffset=f.readLEShort(); + this.topoffset=f.readLEShort(); + // As many columns as width...right??? + this.columnofs=new int[this.width]; + this.columns=new column_t[this.width]; + C2JUtils.initArrayOfObjects( this.columns, column_t.class); + + // Read the column offsets. + f.readIntArray(this.columnofs, this.columnofs.length, ByteOrder.LITTLE_ENDIAN); + for (int i=0;i badColumns = new Hashtable<>(); + + private static column_t getBadColumn(int size) { + + if (badColumns.get(size) == null) { + column_t tmp = new column_t(); + tmp.data = new byte[size + 5]; + for (int i = 3; i < size + 3; i++) { + tmp.data[i] = (byte) (i - 3); + } + + tmp.data[size + 4] = (byte) 0xFF; + tmp.posts = 1; + //tmp.length=(short) size; + //tmp.topdelta=0; + tmp.postofs = new int[]{3}; + tmp.postdeltas = new short[]{0}; + tmp.postlen = new short[]{(short) (size % 256)}; + //tmp.setData(); + badColumns.put(size, tmp); + } + + return badColumns.get(size); + + } + +} \ No newline at end of file diff --git a/doom/src/rr/planefunction_t.java b/doom/src/rr/planefunction_t.java new file mode 100644 index 0000000..047091d --- /dev/null +++ b/doom/src/rr/planefunction_t.java @@ -0,0 +1,5 @@ +package rr; + +public interface planefunction_t { + +} \ No newline at end of file diff --git a/doom/src/rr/sector_t.java b/doom/src/rr/sector_t.java new file mode 100644 index 0000000..84976b9 --- /dev/null +++ b/doom/src/rr/sector_t.java @@ -0,0 +1,330 @@ +package rr; + +import static data.Limits.MAXINT; +import static data.Limits.MAX_ADJOINING_SECTORS; +import doom.SourceCode; +import doom.SourceCode.P_Spec; +import static doom.SourceCode.P_Spec.P_FindLowestCeilingSurrounding; +import doom.SourceCode.fixed_t; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.IRandom; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FRACUNIT; +import mochadoom.Loggers; +import p.Resettable; +import p.ThinkerList; +import p.mobj_t; +import s.degenmobj_t; +import static utils.C2JUtils.memset; +import w.DoomIO; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +/** + * The SECTORS record, at runtime. Stores things/mobjs. Can be + * archived/unarchived during savegames. + * + * @author Maes + */ +public class sector_t implements IReadableDoomObject, IPackableDoomObject, Resettable { + + private static final Logger LOGGER = Loggers.getLogger(sector_t.class.getName()); + + public ThinkerList TL; + + public IRandom RND; + + public sector_t() { + blockbox = new int[4]; + id = -1; + } + + /** (fixed_t) */ + public int floorheight, ceilingheight; + + public short floorpic; + + public short ceilingpic; + + public short lightlevel; + + public short special; + + public short tag; + + /** 0 = untraversed, 1,2 = sndlines -1 */ + public int soundtraversed; + + /** thing that made a sound (or null) (MAES: single pointer) */ + public mobj_t soundtarget; + + /** mapblock bounding box for height changes */ + public int[] blockbox; + + /** + * origin for any sounds played by the sector. Used to be degenmobj_t, but + * that's really a futile distinction. + */ + public degenmobj_t soundorg; + + /** if == validcount, already checked */ + public int validcount; + + /** list of mobjs in sector (MAES: it's used as a linked list) */ + public mobj_t thinglist; + + /** + * thinker_t for reversable actions. This actually was a void*, and in + * practice it could store doors, plats, floors and ceiling objects. + */ + public SectorAction specialdata; + + public int linecount; + + // struct line_s** lines; // [linecount] size + // MAES: make this line_t[] for now? + public line_t[] lines; + + /** Use for internal identification */ + public int id; + + /** killough 1/30/98: improves searches for tags. */ + public int nexttag, firsttag; + + @Override + public String toString() { + // needed? + + return String.format("Sector: %d %x %x %d %d %d %d %d", id, floorheight, + ceilingheight, floorpic, ceilingpic, lightlevel, special, // needed? + tag); + } + + // + // P_FindLowestFloorSurrounding() + // FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS + // + public int FindLowestFloorSurrounding() { + int i; + line_t check; + sector_t other; + int floor = this.floorheight; + + for (i = 0; i < this.linecount; i++) { + check = this.lines[i]; + other = check.getNextSector(this); + + if (other == null) { + continue; + } + + if (other.floorheight < floor) { + floor = other.floorheight; + } + } + return floor; + } + + /** + * P_FindHighestFloorSurrounding() FIND HIGHEST FLOOR HEIGHT IN SURROUNDING + * SECTORS Compatibility problem: apparently this is hardcoded for vanilla + * compatibility (instead of Integer.MIN_VALUE), but it will cause some + * "semi-Boom" maps not to work, since it won't be able to lower stuff below + * -500 units. The correct fix here would be to allow for -compatlevel style + * options. Maybe later. + * + * @param sec + */ + public int FindHighestFloorSurrounding() { + int i; + line_t check; + sector_t other; + + int floor = -500 * FRACUNIT; + + for (i = 0; i < this.linecount; i++) { + check = this.lines[i]; + other = check.getNextSector(this); + + // The compiler nagged about this being unreachable, with + // some older 1.6 JDKs, but that's obviously not true. + if (other == null) { + continue; + } + + if (other.floorheight > floor) { + floor = other.floorheight; + } + } + return floor; + } + + /** + * P_FindNextHighestFloor FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS + * Note: this should be doable w/o a fixed array. + * + * @param sec + * @param currentheight + * @return fixed + */ + public int FindNextHighestFloor(int currentheight) { + int i; + int h; + int min; + line_t check; + sector_t other; + int height = currentheight; + + int heightlist[] = new int[MAX_ADJOINING_SECTORS]; + + for (i = 0, h = 0; i < this.linecount; i++) { + check = this.lines[i]; + other = check.getNextSector(this); + + if (other == null) { + continue; + } + + if (other.floorheight > height) { + heightlist[h++] = other.floorheight; + } + + // Check for overflow. Exit. + if (h >= MAX_ADJOINING_SECTORS) { + LOGGER.log(Level.WARNING, + "Sector with more than 20 adjoining sectors"); + break; + } + } + + // Find lowest height in list + if (h == 0) { + return currentheight; + } + + min = heightlist[0]; + + // Range checking? + for (i = 1; i < h; i++) { + if (heightlist[i] < min) { + min = heightlist[i]; + } + } + + return min; + } + + // + // FIND LOWEST CEILING IN THE SURROUNDING SECTORS + // + @SourceCode.Exact + @P_Spec.C(P_FindLowestCeilingSurrounding) + public @fixed_t + int FindLowestCeilingSurrounding() { + line_t check; + sector_t other; + int height = MAXINT; + + for (int i = 0; i < this.linecount; i++) { + check = this.lines[i]; + getNextSector: + { + other = check.getNextSector(this); + } + + if (other == null) { + continue; + } + + if (other.ceilingheight < height) { + height = other.ceilingheight; + } + } + return height; + } + + // + // FIND HIGHEST CEILING IN THE SURROUNDING SECTORS + // + public int FindHighestCeilingSurrounding() { + int i; + line_t check; + sector_t other; + int height = 0; + + for (i = 0; i < this.linecount; i++) { + check = this.lines[i]; + other = check.getNextSector(this); + + if (other == null) { + continue; + } + + if (other.ceilingheight > height) { + height = other.ceilingheight; + } + } + return height; + } + + @Override + public void read(DataInputStream f) + throws IOException { + + // ACHTUNG: the only situation where we'd + // like to read memory-format sector_t's is from + // savegames, and in vanilla savegames, not all info + // is saved (or read) from disk. + this.floorheight = DoomIO.readLEShort(f) << FRACBITS; + this.ceilingheight = DoomIO.readLEShort(f) << FRACBITS; + // MAES: it may be necessary to apply a hack in order to + // read vanilla savegames. + this.floorpic = DoomIO.readLEShort(f); + this.ceilingpic = DoomIO.readLEShort(f); + // f.skipBytes(4); + this.lightlevel = DoomIO.readLEShort(f); + this.special = DoomIO.readLEShort(f); // needed? + this.tag = DoomIO.readLEShort(f); // needed? + } + + @Override + public void pack(ByteBuffer b) { + + b.putShort((short) (floorheight >> FRACBITS)); + b.putShort((short) (ceilingheight >> FRACBITS)); + // MAES: it may be necessary to apply a hack in order to + // read vanilla savegames. + b.putShort(floorpic); + b.putShort(ceilingpic); + // f.skipBytes(4); + b.putShort(lightlevel); + b.putShort(special); + b.putShort(tag); + } + + @Override + public void reset() { + floorheight = 0; + ceilingheight = 0; + floorpic = 0; + ceilingpic = 0; + lightlevel = 0; + special = 0; + tag = 0; + soundtraversed = 0; + soundtarget = null; + memset(blockbox, 0, blockbox.length); + soundorg = null; + validcount = 0; + thinglist = null; + specialdata = null; + linecount = 0; + lines = null; + id = -1; + + } +} \ No newline at end of file diff --git a/doom/src/rr/seg_t.java b/doom/src/rr/seg_t.java new file mode 100644 index 0000000..3600391 --- /dev/null +++ b/doom/src/rr/seg_t.java @@ -0,0 +1,198 @@ +package rr; + +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import p.Resettable; + +/** + * The LineSeg. Must be built from on-disk mapsegs_t, which are much simpler. + * + * @author Maes + */ +public class seg_t + implements Resettable { + + /** To be used as references */ + public vertex_t v1, v2; + + /** Local caching. Spares us using one extra reference level */ + public int v1x, v1y, v2x, v2y; + + /** (fixed_t) */ + public int offset; + + /** (angle_t) */ + public long angle; + + // MAES: all were single pointers. + public side_t sidedef; + + public line_t linedef; + + /** + * Sector references. Could be retrieved from linedef, too. backsector is + * NULL for one sided lines + */ + public sector_t frontsector, backsector; + + // Boom stuff + public boolean miniseg; + + public float length; + + /** proff 11/05/2000: needed for OpenGL */ + public int iSegID; + + public void assignVertexValues() { + this.v1x = v1.x; + this.v1y = v1.y; + this.v2x = v2.x; + this.v2y = v2.y; + + } + + /** + * R_PointOnSegSide + * + * @param x + * @param y + * @param line + * @return + */ + public static int PointOnSegSide(int x, int y, seg_t line) { + int lx; + int ly; + int ldx; + int ldy; + int dx; + int dy; + int left; + int right; + + lx = line.v1x; + ly = line.v1y; + + ldx = line.v2x - lx; + ldy = line.v2y - ly; + + if (ldx == 0) { + if (x <= lx) { + return (ldy > 0) ? 1 : 0; + } + + return (ldy < 0) ? 1 : 0; + } + if (ldy == 0) { + if (y <= ly) { + return (ldx < 0) ? 1 : 0; + } + + return (ldx > 0) ? 1 : 0; + } + + dx = x - lx; + dy = y - ly; + + // Try to quickly decide by looking at sign bits. + if (((ldy ^ ldx ^ dx ^ dy) & 0x80000000) != 0) { + if (((ldy ^ dx) & 0x80000000) != 0) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(ldy >> FRACBITS, dx); + right = FixedMul(dy, ldx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; + } + + /** + * R_PointOnSegSide + * + * @param x + * @param y + * @param line + * @return + */ + public int PointOnSegSide(int x, int y) { + int lx; + int ly; + int ldx; + int ldy; + int dx; + int dy; + int left; + int right; + + lx = this.v1x; + ly = this.v1y; + + ldx = this.v2x - lx; + ldy = this.v2y - ly; + + if (ldx == 0) { + if (x <= lx) { + return (ldy > 0) ? 1 : 0; + } + + return (ldy < 0) ? 1 : 0; + } + if (ldy == 0) { + if (y <= ly) { + return (ldx < 0) ? 1 : 0; + } + + return (ldx > 0) ? 1 : 0; + } + + dx = x - lx; + dy = y - ly; + + // Try to quickly decide by looking at sign bits. + if (((ldy ^ ldx ^ dx ^ dy) & 0x80000000) != 0) { + if (((ldy ^ dx) & 0x80000000) != 0) { + // (left is negative) + return 1; + } + return 0; + } + + left = FixedMul(ldy >> FRACBITS, dx); + right = FixedMul(dy, ldx >> FRACBITS); + + if (right < left) { + // front side + return 0; + } + // back side + return 1; + } + + public String toString() { + return String + .format( + "Seg %d\n\tFrontsector: %s\n\tBacksector: %s\n\tVertexes: %x %x %x %x", + iSegID, frontsector, backsector, v1x, v1y, v2x, v2y); + } + + @Override + public void reset() { + v1 = v2 = null; + v1x = v1y = v2x = v2y = 0; + angle = 0; + frontsector = backsector = null; + iSegID = 0; + linedef = null; + miniseg = false; + offset = 0; + length = 0; + } + +} \ No newline at end of file diff --git a/doom/src/rr/side_t.java b/doom/src/rr/side_t.java new file mode 100644 index 0000000..4cb4590 --- /dev/null +++ b/doom/src/rr/side_t.java @@ -0,0 +1,90 @@ +package rr; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import static m.fixed_t.FRACBITS; +import p.Resettable; +import w.DoomIO; +import w.IPackableDoomObject; +import w.IReadableDoomObject; + +/** + * The SideDef. + * + * @author admin + */ +public class side_t + implements IReadableDoomObject, IPackableDoomObject, Resettable { + + /** (fixed_t) add this to the calculated texture column */ + public int textureoffset; + + /** (fixed_t) add this to the calculated texture top */ + public int rowoffset; + + /** + * Texture indices. We do not maintain names here. + */ + public short toptexture; + + public short bottomtexture; + + public short midtexture; + + /** Sector the SideDef is facing. MAES: pointer */ + public sector_t sector; + + public int sectorid; + + public int special; + + public side_t() { + } + + public side_t(int textureoffset, int rowoffset, short toptexture, + short bottomtexture, short midtexture, sector_t sector) { + super(); + this.textureoffset = textureoffset; + this.rowoffset = rowoffset; + this.toptexture = toptexture; + this.bottomtexture = bottomtexture; + this.midtexture = midtexture; + this.sector = sector; + } + + @Override + public void read(DataInputStream f) + throws IOException { + this.textureoffset = DoomIO.readLEShort(f) << FRACBITS; + this.rowoffset = DoomIO.readLEShort(f) << FRACBITS; + this.toptexture = DoomIO.readLEShort(f); + this.bottomtexture = DoomIO.readLEShort(f); + this.midtexture = DoomIO.readLEShort(f); + // this.sectorid=f.readLEInt(); + + } + + @Override + public void pack(ByteBuffer buffer) { + buffer.putShort((short) (textureoffset >> FRACBITS)); + buffer.putShort((short) (rowoffset >> FRACBITS)); + buffer.putShort(toptexture); + buffer.putShort(bottomtexture); + buffer.putShort(midtexture); + } + + @Override + public void reset() { + textureoffset = 0; + rowoffset = 0; + toptexture = 0; + bottomtexture = 0; + midtexture = 0; + sector = null; + sectorid = 0; + special = 0; + + } + +} \ No newline at end of file diff --git a/doom/src/rr/spritedef_t.java b/doom/src/rr/spritedef_t.java new file mode 100644 index 0000000..7556578 --- /dev/null +++ b/doom/src/rr/spritedef_t.java @@ -0,0 +1,44 @@ +package rr; + +/** + * A sprite definition: + * a number of animation frames. + */ +public class spritedef_t { + + /** the very least, primitive fields won't bomb, + * and copy constructors can do their job. + */ + public spritedef_t() { + } + + public spritedef_t(int numframes) { + this.numframes = numframes; + this.spriteframes = new spriteframe_t[numframes]; + } + + public spritedef_t(spriteframe_t[] frames) { + this.numframes = frames.length; + this.spriteframes = new spriteframe_t[numframes]; + // copy shit over... + for (int i = 0; i < numframes; i++) { + spriteframes[i] = frames[i].clone(); + } + } + + /** Use this constructor, as we usually need less than 30 frames + * It will actually clone the frames. + */ + public void copy(spriteframe_t[] from, int maxframes) { + this.numframes = maxframes; + this.spriteframes = new spriteframe_t[maxframes]; + // copy shit over... + for (int i = 0; i < maxframes; i++) { + spriteframes[i] = from[i].clone(); + } + } + + public int numframes; + public spriteframe_t[] spriteframes; + +}; \ No newline at end of file diff --git a/doom/src/rr/spriteframe_t.java b/doom/src/rr/spriteframe_t.java new file mode 100644 index 0000000..2409e04 --- /dev/null +++ b/doom/src/rr/spriteframe_t.java @@ -0,0 +1,50 @@ +package rr; + +/** Sprites are patches with a special naming convention + * so they can be recognized by R_InitSprites. + * The base name is NNNNFx or NNNNFxFx, with + * x indicating the rotation, x = 0, 1-7. + * The sprite and frame specified by a thing_t + * is range checked at run time. + * A sprite is a patch_t that is assumed to represent + * a three dimensional object and may have multiple + * rotations pre drawn. + * Horizontal flipping is used to save space, + * thus NNNNF2F5 defines a mirrored patch. + * Some sprites will only have one picture used + * for all views: NNNNF0 + */ +public class spriteframe_t implements Cloneable { + + public spriteframe_t() { + lump = new int[8]; + flip = new byte[8]; + } + + /** If false use 0 for any position. + * Note: as eight entries are available, + * we might as well insert the same name eight times. + * + * FIXME: this is used as a tri-state. + * 0= false + * 1= true + * -1= cleared/indeterminate, which should not evaluate to either true or false. + * */ + public int rotate; + + /** Lump to use for view angles 0-7. */ + public int[] lump; + + /** Flip bit (1 = flip) to use for view angles 0-7. */ + public byte[] flip; + + public spriteframe_t clone() { + spriteframe_t response = new spriteframe_t(); + response.rotate = rotate; + System.arraycopy(this.lump, 0, response.lump, 0, lump.length); + System.arraycopy(this.flip, 0, response.flip, 0, flip.length); + return response; + + } + +} \ No newline at end of file diff --git a/doom/src/rr/subsector_t.java b/doom/src/rr/subsector_t.java new file mode 100644 index 0000000..4a36538 --- /dev/null +++ b/doom/src/rr/subsector_t.java @@ -0,0 +1,56 @@ +package rr; + +import p.Resettable; + +/** + * + * A SubSector. References a Sector. Basically, this is a list of LineSegs, + * indicating the visible walls that define (all or some) sides of a convex BSP + * leaf. + * + * @author admin + */ +public class subsector_t implements Resettable { + + public subsector_t() { + this(null, 0, 0); + } + + public subsector_t(sector_t sector, int numlines, int firstline) { + this.sector = sector; + this.numlines = numlines; + this.firstline = firstline; + } + + // Maes: single pointer + public sector_t sector; + // e6y: support for extended nodes + // 'int' instead of 'short' + public int numlines; + public int firstline; + + public String toString() { + sb.setLength(0); + sb.append("Subsector"); + sb.append('\t'); + sb.append("Sector: "); + sb.append(sector); + sb.append('\t'); + sb.append("numlines "); + sb.append(numlines); + sb.append('\t'); + sb.append("firstline "); + sb.append(firstline); + return sb.toString(); + + } + + private static StringBuilder sb = new StringBuilder(); + + @Override + public void reset() { + sector = null; + firstline = numlines = 0; + } + +} \ No newline at end of file diff --git a/doom/src/rr/texpatch_t.java b/doom/src/rr/texpatch_t.java new file mode 100644 index 0000000..6b6e32b --- /dev/null +++ b/doom/src/rr/texpatch_t.java @@ -0,0 +1,24 @@ +package rr; + +/** + * A single patch from a texture definition, + * basically a rectangular area within + * the texture rectangle. + * @author admin + * + */ +public class texpatch_t { +// Block origin (allways UL), +// which has allready accounted +// for the internal origin of the patch. + + int originx; + int originy; + int patch; + + public void copyFromMapPatch(mappatch_t mpp) { + this.originx = mpp.originx; + this.originy = mpp.originy; + this.patch = mpp.patch; + } +} \ No newline at end of file diff --git a/doom/src/rr/texture_t.java b/doom/src/rr/texture_t.java new file mode 100644 index 0000000..fd33914 --- /dev/null +++ b/doom/src/rr/texture_t.java @@ -0,0 +1,51 @@ +package rr; + +/** A maptexturedef_t describes a rectangular texture, + * which is composed of one or more mappatch_t structures + * that arrange graphic patches. + * + * This is the in-memory format, which is similar to maptexture_t (which is on-disk). + * + * @author Maes + * + */ +public class texture_t { + + /** Keep name for switch changing, etc. */ + public String name; + public short width; + public short height; + + // All the patches[patchcount] + // are drawn back to front into the cached texture. + public short patchcount; + public texpatch_t[] patches; + + /** Unmarshalling at its best! */ + public void copyFromMapTexture(maptexture_t mt) { + this.name = new String(mt.name); + this.width = mt.width; + this.height = mt.height; + this.patchcount = mt.patchcount; + this.patches = new texpatch_t[patchcount]; + + for (int i = 0; i < patchcount; i++) { + patches[i] = new texpatch_t(); + patches[i].copyFromMapPatch(mt.patches[i]); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + sb.append(" Height "); + sb.append(height); + sb.append(" Width "); + sb.append(width); + sb.append(" Patchcount "); + sb.append(patchcount); + return sb.toString(); + + } +} \ No newline at end of file diff --git a/doom/src/rr/vertex_t.java b/doom/src/rr/vertex_t.java new file mode 100644 index 0000000..9a0263f --- /dev/null +++ b/doom/src/rr/vertex_t.java @@ -0,0 +1,43 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import static m.fixed_t.FRACBITS; +import p.Resettable; +import w.CacheableDoomObject; + +/** This is the vertex structure used IN MEMORY with fixed-point arithmetic. + * It's DIFFERENT than the one used on disk, which has 16-bit signed shorts. + * However, it must be parsed. + * + */ +public class vertex_t implements CacheableDoomObject, Resettable { + + public vertex_t() { + + } + /** treat as (fixed_t) */ + public int x, y; + + /** Notice how we auto-expand to fixed_t */ + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.x = buf.getShort() << FRACBITS; + this.y = buf.getShort() << FRACBITS; + + } + + @Override + public void reset() { + x = 0; + y = 0; + } + + public static int sizeOf() { + return 4; + } + +} \ No newline at end of file diff --git a/doom/src/rr/visplane_t.java b/doom/src/rr/visplane_t.java new file mode 100644 index 0000000..0bdb550 --- /dev/null +++ b/doom/src/rr/visplane_t.java @@ -0,0 +1,163 @@ +package rr; + +import static utils.C2JUtils.memset; +import v.scale.VideoScale; + +/** + * Now what is a visplane, anyway? Basically, it's a bunch of arrays buffer representing a top and a bottom boundary of + * a region to be filled with a specific kind of flat. They are as wide as the screen, and actually store height + * bounding values or sentinel valuesThink of it as an arbitrary boundary. + * + * These are refreshed continuously during rendering, and mark the limits between flat regions. Special values mean "do + * not render this column at all", while clipping out of the map bounds results in well-known bleeding effects. + * + * @author admin + * + */ +public class visplane_t { + + public static final int TOPOFFSET = 1; + public static final int MIDDLEPADDING = 2; + public static int BOTTOMOFFSET; + + // Multithreading trickery (for strictly x-bounded drawers) + // The thread if is encoded in the upper 3 bits (puts an upper limit + // of 8 floor threads), and the stomped value is encoded in the next 12 + // bits (this puts an upper height limit of 4096 pixels). + // Not the cleanest system possible, but it's backwards compatible + // TODO: expand visplane buffers to full-fledged ints? + public static final char SENTINEL = 0x8000; + public static final char THREADIDSHIFT = 12; + public static final char THREADIDCLEAR = 0x8FFF; + public static final char THREADIDBITS = 0XFFFF - THREADIDCLEAR; + public static final char THREADVALUEBITS = THREADIDCLEAR - SENTINEL; + + public visplane_t() { + this.data = new char[4 + 2 * vs.getScreenWidth()]; + this.updateHashCode(); + } + + public visplane_t(int height, int picnum, int lightlevel) { + this.height = height; + this.picnum = picnum; + this.lightlevel = lightlevel; + this.updateHashCode(); + this.data = new char[4 + 2 * vs.getScreenWidth()]; + } + + /** + * (fixed_t) + */ + public int height; + public int picnum; + public int lightlevel; + public int minx; + public int maxx; + + // leave pads for [minx-1]/[maxx+1] + + /* + public byte pad1; + // Here lies the rub for all + // dynamic resize/change of resolution. + public byte[] top=new byte[vs.getScreenWidth()]; + public byte pad2; + public byte pad3; + // See above. + public byte[] bottom=new byte [vs.getScreenWidth()]; + public byte pad4;*/ + char data[]; + + // Hack to allow quick clearing of visplanes. + protected static char[] clearvisplane; + + /** + * "Clear" the top with FF's. + */ + public void clearTop() { + System.arraycopy(clearvisplane, 0, this.data, TOPOFFSET, vs.getScreenWidth()); + + } + + /** + * "Clear" the bottom with FF's. + */ + public void clearBottom() { + System.arraycopy(clearvisplane, 0, this.data, BOTTOMOFFSET, vs.getScreenWidth()); + } + + public void setTop(int index, char value) { + this.data[TOPOFFSET + index] = value; + } + + public char getTop(int index) { + return this.data[TOPOFFSET + index]; + + } + + public void setBottom(int index, char value) { + this.data[BOTTOMOFFSET + index] = value; + + } + + public int getBottom(int index) { + return this.data[BOTTOMOFFSET + index]; + + } + + public String toString() { + sb.setLength(0); + sb.append("Visplane\n"); + sb.append('\t'); + sb.append("Height: "); + sb.append(this.height); + sb.append('\t'); + sb.append("Min-Max: "); + sb.append(this.minx); + sb.append('-'); + sb.append(this.maxx); + sb.append('\t'); + sb.append("Picnum: "); + sb.append(this.picnum); + sb.append('\t'); + sb.append("Lightlevel: "); + sb.append(this.lightlevel); + + return sb.toString(); + + } + + protected int hash; + + /** + * Call this upon any changed in height, picnum or lightlevel + */ + public void updateHashCode() { + this.hash = height ^ picnum ^ lightlevel; + } + + public int hashCode() { + return this.hash; + } + + public static int visplaneHash(int height, int picnum, int lightlevel) { + return height ^ picnum ^ lightlevel; + + } + + protected static StringBuilder sb = new StringBuilder(); + + // HACK: the resolution awareness is shared between all visplanes. + // Change this if you ever plan on running multiple renderers with + // different resolution or something. + protected static VideoScale vs; + + public static void setVideoScale(VideoScale vs) { + visplane_t.vs = vs; + BOTTOMOFFSET = vs.getScreenWidth() + TOPOFFSET + MIDDLEPADDING; + if (clearvisplane == null || clearvisplane.length < vs.getScreenWidth()) { + clearvisplane = new char[vs.getScreenWidth()]; + memset(clearvisplane, Character.MAX_VALUE, clearvisplane.length); + } + } +}; \ No newline at end of file diff --git a/doom/src/rr/vissprite_t.java b/doom/src/rr/vissprite_t.java new file mode 100644 index 0000000..fdb62cf --- /dev/null +++ b/doom/src/rr/vissprite_t.java @@ -0,0 +1,63 @@ +package rr; + +/** A vissprite_t is a thing + * that will be drawn during a refresh. + * I.e. a sprite object that is partly visible. + */ +public class vissprite_t implements Comparable> { + +// Doubly linked list. + public vissprite_t prev; + public vissprite_t next; + + public int x1; + public int x2; + +// for line side calculation + public int gx; + public int gy; + +// global bottom / top for silhouette clipping + public int gz; + public int gzt; + +// horizontal position of x1 + public int startfrac; + + public int scale; + +// negative if flipped + public int xiscale; + + public int texturemid; + public int patch; + + /** for color translation and shadow draw, + * maxbright frames as well. + * + * Use paired with pcolormap; + */ + public V colormap; + + /* pointer into colormap +public int pcolormap; */ + public long mobjflags; + + /** visspites are sorted by scale */ + @Override + public final int compareTo(vissprite_t o) { + // We only really care if it's drawn before. + if (this.scale > o.scale) { + return 1; + } + if (this.scale < o.scale) { + return -1; + } + return 0; + } + + public String toString() { + return ("Effective drawing position x1: " + x1 + " x2: " + x2 + " scale " + (scale / 65535.0) + " iscale " + (xiscale / 65535.0)); + } + +} \ No newline at end of file diff --git a/doom/src/rr/z_vertex_t.java b/doom/src/rr/z_vertex_t.java new file mode 100644 index 0000000..0401d72 --- /dev/null +++ b/doom/src/rr/z_vertex_t.java @@ -0,0 +1,28 @@ +package rr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class z_vertex_t + extends vertex_t { + + public z_vertex_t() { + super(); + } + + /** Notice how we auto-expand to fixed_t */ + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + this.x = buf.getInt(); + this.y = buf.getInt(); + + } + + public final static int sizeOf() { + return 8; + } + +} \ No newline at end of file diff --git a/doom/src/s/AbstractDoomAudio.java b/doom/src/s/AbstractDoomAudio.java new file mode 100644 index 0000000..1792a8b --- /dev/null +++ b/doom/src/s/AbstractDoomAudio.java @@ -0,0 +1,823 @@ +package s; + +import data.Defines; +import static data.Tables.ANGLETOFINESHIFT; +import static data.Tables.BITS32; +import static data.Tables.finesine; +import data.musicinfo_t; +import data.sfxinfo_t; +import data.sounds; +import static data.sounds.S_sfx; +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import java.util.logging.Level; +import java.util.logging.Logger; +import static m.fixed_t.FRACBITS; +import static m.fixed_t.FixedMul; +import mochadoom.Loggers; +import p.mobj_t; + +/** + * Some stuff that is not implementation dependant + * This includes channel management, sound priorities, + * positioning, distance attenuation etc. It's up to + * lower-level "drivers" to actually implements those. + * This particular class needs not be a dummy itself, but + * the drivers it "talks" to might be. + **/ +public class AbstractDoomAudio implements IDoomSound { + + private static final Logger LOGGER = Loggers.getLogger(AbstractDoomAudio.class.getName()); + + protected final DoomMain DS; + protected final IMusic IMUS; + protected final ISoundDriver ISND; + + protected final int numChannels; + + protected final static boolean D = false; + + /** the set of channels available. These are "soft" descriptor + channels, not to be confused with actual hardware audio + lines, which are an entirely different concern. + + */ + protected final channel_t[] channels; + + // These are not used, but should be (menu). + // Maximum volume of a sound effect. + // Internal default is max out of 0-15. + protected int snd_SfxVolume = 15; + + // Maximum volume of music. Useless so far. + protected int snd_MusicVolume = 15; + + // whether songs are mus_paused + protected boolean mus_paused; + + // music currently being played + protected musicinfo_t mus_playing; + + protected int nextcleanup; + + public AbstractDoomAudio(DoomMain DS, int numChannels) { + this.DS = DS; + this.numChannels = numChannels; + this.channels = new channel_t[numChannels]; + this.IMUS = DS.music; + this.ISND = DS.soundDriver; + } + + /** Volume, pitch, separation & priority packed for parameter passing */ + protected class vps_t { + + int volume; + int pitch; + int sep; + int priority; + } + + /** + * Initializes sound stuff, including volume + * Sets channels, SFX and music volume, + * allocates channel buffer, sets S_sfx lookup. + */ + public void Init(int sfxVolume, + int musicVolume) { + int i; + + LOGGER.log(Level.INFO, String.format("S_Init: default sfx volume %d", sfxVolume)); + + this.snd_SfxVolume = sfxVolume; + this.snd_MusicVolume = musicVolume; + // Whatever these did with DMX, these are rather dummies now. + // MAES: any implementation-dependant channel setup should start here. + ISND.SetChannels(numChannels); + + SetSfxVolume(sfxVolume); + // No music with Linux - another dummy. + // MAES: these must be initialized somewhere, perhaps not here? + IMUS.SetMusicVolume(musicVolume); + + // Allocating the internal channels for mixing + // (the maximum numer of sounds rendered + // simultaneously) within zone memory. + // MAES: already done that in the constructor. + // Free all channels for use + for (i = 0; i < numChannels; i++) { + channels[i] = new channel_t(); + //channels[i].sfxinfo = null; + } + + // no sounds are playing, and they are not mus_paused + mus_paused = false; + + // Note that sounds have not been cached (yet). + for (i = 1; i < S_sfx.length; i++) { + S_sfx[i].lumpnum = S_sfx[i].usefulness = -1; + } + } + + // + // Per level startup code. + // Kills playing sounds at start of level, + // determines music if any, changes music. + // + public void Start() { + int cnum; + int mnum; + + // kill all playing sounds at start of level + // (trust me - a good idea) + for (cnum = 0; cnum < numChannels; cnum++) { + if (channels[cnum].sfxinfo != null) { + StopChannel(cnum); + } + } + + // start new music for the level + mus_paused = false; + + if (DS.isCommercial()) { + mnum = musicenum_t.mus_runnin.ordinal() + DS.gamemap - 1; + } else { + musicenum_t[] spmus + = { + // Song - Who? - Where? + + musicenum_t.mus_e3m4, // American e4m1 + musicenum_t.mus_e3m2, // Romero e4m2 + musicenum_t.mus_e3m3, // Shawn e4m3 + musicenum_t.mus_e1m5, // American e4m4 + musicenum_t.mus_e2m7, // Tim e4m5 + musicenum_t.mus_e2m4, // Romero e4m6 + musicenum_t.mus_e2m6, // J.Anderson e4m7 CHIRON.WAD + musicenum_t.mus_e2m5, // Shawn e4m8 + musicenum_t.mus_e1m9 // Tim e4m9 + }; + + if (DS.gameepisode < 4) { + mnum = musicenum_t.mus_e1m1.ordinal() + (DS.gameepisode - 1) * 9 + DS.gamemap - 1; + } else { + mnum = spmus[DS.gamemap - 1].ordinal(); + } + } + + // HACK FOR COMMERCIAL + // if (commercial && mnum > mus_e3m9) + // mnum -= mus_e3m9; + ChangeMusic(mnum, true); + + nextcleanup = 15; + } + + private vps_t vps = new vps_t(); + + public void + StartSoundAtVolume(ISoundOrigin origin_p, + int sfx_id, + int volume) { + + boolean rc; + int sep = 0; // This is set later. + int pitch; + int priority; + sfxinfo_t sfx; + int cnum; + + ISoundOrigin origin = (ISoundOrigin) origin_p; + + // Debug. + //if (origin!=null && origin.type!=null) + // System.err.printf( + // "S_StartSoundAtVolume: playing sound %d (%s) from %s %d\n", + // sfx_id, S_sfx[sfx_id].name , origin.type.toString(),origin.hashCode()); + // check for bogus sound # + if (sfx_id < 1 || sfx_id > NUMSFX) { + DS.doomSystem.Error("Bad sfx #: %d", sfx_id); + } + + sfx = S_sfx[sfx_id]; + + // Initialize sound parameters + if (sfx.link != null) { + pitch = sfx.pitch; + priority = sfx.priority; + volume += sfx.volume; + + if (volume < 1) { + return; + } + + if (volume > snd_SfxVolume) { + volume = snd_SfxVolume; + } + } else { + pitch = NORM_PITCH; + priority = NORM_PRIORITY; + } + + // Check to see if it is audible, + // and if not, modify the params + if ((origin != null) && origin != DS.players[DS.consoleplayer].mo) { + vps.volume = volume; + vps.pitch = pitch; + vps.sep = sep; + rc = AdjustSoundParams(DS.players[DS.consoleplayer].mo, + origin, vps); + volume = vps.volume; + pitch = vps.pitch; + sep = vps.sep; + + if (origin.getX() == DS.players[DS.consoleplayer].mo.x + && origin.getY() == DS.players[DS.consoleplayer].mo.y) { + sep = NORM_SEP; + } + + if (!rc) { + //System.err.printf("S_StartSoundAtVolume: Sound %d (%s) rejected because: inaudible\n", + // sfx_id, S_sfx[sfx_id].name ); + return; + } + } else { + sep = NORM_SEP; + } + + // hacks to vary the sfx pitches + if (sfx_id >= sfxenum_t.sfx_sawup.ordinal() + && sfx_id <= sfxenum_t.sfx_sawhit.ordinal()) { + pitch += 8 - (DS.random.M_Random() & 15); + + if (pitch < 0) { + pitch = 0; + } else if (pitch > 255) { + pitch = 255; + } + } else if (sfx_id != sfxenum_t.sfx_itemup.ordinal() + && sfx_id != sfxenum_t.sfx_tink.ordinal()) { + pitch += 16 - (DS.random.M_Random() & 31); + + if (pitch < 0) { + pitch = 0; + } else if (pitch > 255) { + pitch = 255; + } + } + + // kill old sound + StopSound(origin); + + // try to find a channel + cnum = getChannel(origin, sfx); + + if (cnum < 0) { + return; + } + + // + // This is supposed to handle the loading/caching. + // For some odd reason, the caching is done nearly + // each time the sound is needed? + // + // get lumpnum if necessary + if (sfx.lumpnum < 0) // Now, it crosses into specific territory. + { + sfx.lumpnum = ISND.GetSfxLumpNum(sfx); + } + + /* + #ifndef SNDSRV + // cache data if necessary + if (!sfx->data) + { + fprintf( stderr, + "S_StartSoundAtVolume: 16bit and not pre-cached - wtf?\n"); + + // DOS remains, 8bit handling + //sfx->data = (void *) W_CacheLumpNum(sfx->lumpnum, PU_MUSIC); + // fprintf( stderr, + // "S_StartSoundAtVolume: loading %d (lump %d) : 0x%x\n", + // sfx_id, sfx->lumpnum, (int)sfx->data ); + + } + #endif */ + // increase the usefulness + if (sfx.usefulness++ < 0) { + sfx.usefulness = 1; + } + + // Assigns the handle to one of the channels in the + // mix/output buffer. This is when things actually + // become hard (pun intended). + // TODO: which channel? How do we know how the actual hardware + // ones map with the "soft" ones? + // Essentially we're begging to get an actual channel. + channels[cnum].handle = ISND.StartSound(sfx_id, + /*sfx->data,*/ + volume, + sep, + pitch, + priority); + + if (D) { + LOGGER.log(Level.FINE, String.format("Handle %d for channel %d for sound %s vol %d sep %d\n", channels[cnum].handle, + cnum, sfx.name, volume, sep)); + } + } + + public void + StartSound(ISoundOrigin origin, + sfxenum_t sfx_id) { + // MAES: necessary sanity check at this point. + if (sfx_id != null && sfx_id.ordinal() > 0) { + StartSound(origin, sfx_id.ordinal()); + } + } + + public void + StartSound(ISoundOrigin origin, + int sfx_id) { + /* #ifdef SAWDEBUG + // if (sfx_id == sfx_sawful) + // sfx_id = sfx_itemup; + #endif */ + + StartSoundAtVolume(origin, sfx_id, snd_SfxVolume); + + // UNUSED. We had problems, had we not? + /* #ifdef SAWDEBUG + { + int i; + int n; + + static mobj_t* last_saw_origins[10] = {1,1,1,1,1,1,1,1,1,1}; + static int first_saw=0; + static int next_saw=0; + + if (sfx_id == sfx_sawidl + || sfx_id == sfx_sawful + || sfx_id == sfx_sawhit) + { + for (i=first_saw;i!=next_saw;i=(i+1)%10) + if (last_saw_origins[i] != origin) + fprintf(stderr, "old origin 0x%lx != " + "origin 0x%lx for sfx %d\n", + last_saw_origins[i], + origin, + sfx_id); + + last_saw_origins[next_saw] = origin; + next_saw = (next_saw + 1) % 10; + if (next_saw == first_saw) + first_saw = (first_saw + 1) % 10; + + for (n=i=0; i1) + { + for (i=0; i -1) + { + if (--S_sfx[i].usefulness == -1) + { + Z_ChangeTag(S_sfx[i].data, PU_CACHE); + S_sfx[i].data = 0; + } + } + } + nextcleanup = gametic + 15; + }*/ + for (cnum = 0; cnum < numChannels; cnum++) { + c = channels[cnum]; + sfx = c.sfxinfo; + + //System.out.printf("Updating channel %d %s\n",cnum,c); + if (c.sfxinfo != null) { + if (ISND.SoundIsPlaying(c.handle)) { + // initialize parameters + vps.volume = snd_SfxVolume; + vps.pitch = NORM_PITCH; + vps.sep = NORM_SEP; + + sfx = c.sfxinfo; + + if (sfx.link != null) { + vps.pitch = sfx.pitch; + vps.volume += sfx.volume; + if (vps.volume < 1) { + StopChannel(cnum); + continue; + } else if (vps.volume > snd_SfxVolume) { + vps.volume = snd_SfxVolume; + } + } + + // check non-local sounds for distance clipping + // or modify their params + if (c.origin != null && (listener != c.origin)) { + audible = AdjustSoundParams(listener, + c.origin, + vps); + + if (!audible) { + StopChannel(cnum); + } else { + ISND.UpdateSoundParams(c.handle, vps.volume, vps.sep, vps.pitch); + } + } + } else { + // if channel is allocated but sound has stopped, + // free it + StopChannel(cnum); + } + } + } + // kill music if it is a single-play && finished + // if ( mus_playing + // && !I_QrySongPlaying(mus_playing->handle) + // && !mus_paused ) + // S_StopMusic(); + } + + public void SetMusicVolume(int volume) { + if (volume < 0 || volume > 127) { + DS.doomSystem.Error("Attempt to set music volume at %d", + volume); + } + + IMUS.SetMusicVolume(volume); + snd_MusicVolume = volume; + } + + public void SetSfxVolume(int volume) { + + if (volume < 0 || volume > 127) { + DS.doomSystem.Error("Attempt to set sfx volume at %d", volume); + } + + snd_SfxVolume = volume; + + } + + // + // Starts some music with the music id found in sounds.h. + // + public void StartMusic(int m_id) { + ChangeMusic(m_id, false); + } + + // + // Starts some music with the music id found in sounds.h. + // + public void StartMusic(musicenum_t m_id) { + ChangeMusic(m_id.ordinal(), false); + } + + public void ChangeMusic(musicenum_t musicnum, + boolean looping) { + ChangeMusic(musicnum.ordinal(), false); + } + + public void + ChangeMusic(int musicnum, + boolean looping) { + musicinfo_t music = null; + String namebuf; + + if ((musicnum <= musicenum_t.mus_None.ordinal()) + || (musicnum >= musicenum_t.NUMMUSIC.ordinal())) { + + DS.doomSystem.Error("Bad music number %d", musicnum); + } else { + music = sounds.S_music[musicnum]; + } + + if (mus_playing == music) { + return; + } + + // shutdown old music + StopMusic(); + + // get lumpnum if neccessary + if (music.lumpnum == 0) { + namebuf = String.format("d_%s", music.name); + music.lumpnum = DS.wadLoader.GetNumForName(namebuf); + } + + // load & register it + music.data = DS.wadLoader.CacheLumpNumAsRawBytes(music.lumpnum, Defines.PU_MUSIC); + music.handle = IMUS.RegisterSong(music.data); + + // play it + IMUS.PlaySong(music.handle, looping); + SetMusicVolume(this.snd_MusicVolume); + + mus_playing = music; + } + + public void StopMusic() { + if (mus_playing != null) { + if (mus_paused) { + IMUS.ResumeSong(mus_playing.handle); + } + + IMUS.StopSong(mus_playing.handle); + IMUS.UnRegisterSong(mus_playing.handle); + //Z_ChangeTag(mus_playing->data, PU_CACHE); + + mus_playing.data = null; + mus_playing = null; + } + } + + /** This is S_StopChannel. There's another StopChannel + * with a similar contract in ISound. Don't confuse the two. + * + * + * + * @param cnum + */ + protected void StopChannel(int cnum) { + + int i; + channel_t c = channels[cnum]; + + // Is it playing? + if (c.sfxinfo != null) { + // stop the sound playing + if (ISND.SoundIsPlaying(c.handle)) { + /*#ifdef SAWDEBUG + if (c.sfxinfo == &S_sfx[sfx_sawful]) + fprintf(stderr, "stopped\n"); + #endif*/ + ISND.StopSound(c.handle); + } + + // check to see + // if other channels are playing the sound + for (i = 0; i < numChannels; i++) { + if (cnum != i + && c.sfxinfo == channels[i].sfxinfo) { + break; + } + } + + // degrade usefulness of sound data + c.sfxinfo.usefulness--; + + c.sfxinfo = null; + } + } + + // + // Changes volume, stereo-separation, and pitch variables + // from the norm of a sound effect to be played. + // If the sound is not audible, returns a 0. + // Otherwise, modifies parameters and returns 1. + // + protected boolean + AdjustSoundParams(mobj_t listener, + ISoundOrigin source, + vps_t vps) { + int approx_dist; + int adx; + int ady; + long angle; + + // calculate the distance to sound origin + // and clip it if necessary + adx = Math.abs(listener.x - source.getX()); + ady = Math.abs(listener.y - source.getY()); + + // From _GG1_ p.428. Appox. eucledian distance fast. + approx_dist = adx + ady - ((adx < ady ? adx : ady) >> 1); + + if (DS.gamemap != 8 + && approx_dist > S_CLIPPING_DIST) { + return false; + } + + // angle of source to listener + angle = rr.RendererState.PointToAngle(listener.x, + listener.y, + source.getX(), + source.getY()); + + if (angle > listener.angle) { + angle = angle - listener.angle; + } else { + angle = angle + (0xffffffffL - listener.angle & BITS32); + } + + angle &= BITS32; + angle >>= ANGLETOFINESHIFT; + + // stereo separation + vps.sep = 128 - (FixedMul(S_STEREO_SWING, finesine[(int) angle]) >> FRACBITS); + + // volume calculation + if (approx_dist < S_CLOSE_DIST) { + vps.volume = snd_SfxVolume; + } else if (DS.gamemap == 8) { + if (approx_dist > S_CLIPPING_DIST) { + approx_dist = S_CLIPPING_DIST; + } + + vps.volume = 15 + ((snd_SfxVolume - 15) + * ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) + / S_ATTENUATOR; + } else { + // distance effect + vps.volume = (snd_SfxVolume + * ((S_CLIPPING_DIST - approx_dist) >> FRACBITS)) + / S_ATTENUATOR; + // Let's do some maths here: S_CLIPPING_DIST-approx_dist + // can be at most 0x04100000. shifting left means 0x0410, + // or 1040 in decimal. + // The unmultiplied max volume is 15, attenuator is 1040. + // So snd_SfxVolume should be 0-127. + + } + + // MAES: pitch calculation for doppler effects. Nothing to write + // home about. + /* + + // calculate the relative speed between source and sound origin. + // and clip it if necessary + adx = Math.abs(listener.momx - source.momx); + ady = Math.abs(listener.momy - source.momy); + + // From _GG1_ p.428. Appox. eucledian distance fast. + // Here used for "approximate speed" + approx_dist = adx + ady - ((adx < ady ? adx : ady)>>1); + + // The idea is that for low speeds, no doppler effect occurs. + // For higher ones however, a shift occurs. We don't want this + // to be annoying, so we'll only apply it for large speed differences + // Then again, Doomguy can sprint like Carl Lewis... + + if (approx_dist>0x100000){ + + // Quickly decide sign of pitch based on speed vectors + + // angle of source (speed) to listener (speed) + angle = rr.RendererState.PointToAngle(listener.momx, + listener.momy, + source.momx, + source.momy); + + if ((0<=angle && angle<=Tables.ANG90)|| + (180<=angle && angle<=Tables.ANG270)) + vps.pitch+=(approx_dist>>16); + else + vps.pitch-=(approx_dist>>16); + } + + if (vps.pitch<0) vps.pitch=0; + if (vps.pitch>255) vps.pitch=255; + */ + return (vps.volume > 0); + } + + // + // S_getChannel : + // If none available, return -1. Otherwise channel #. + // + protected int getChannel(ISoundOrigin origin, sfxinfo_t sfxinfo) { + // channel number to use + int cnum; + + channel_t c; + + // Find an open channel + // If it's null, OK, use that. + // If it's an origin-specific sound and has the same origin, override. + for (cnum = 0; cnum < numChannels; cnum++) { + if (channels[cnum].sfxinfo == null) { + break; + } else if (origin != null && channels[cnum].origin == origin) { + StopChannel(cnum); + break; + } + } + + // None available + if (cnum == numChannels) { + // Look for lower priority + for (cnum = 0; cnum < numChannels; cnum++) { + if (channels[cnum].sfxinfo.priority >= sfxinfo.priority) { + break; + } + } + + if (cnum == numChannels) { + // FUCK! No lower priority. Sorry, Charlie. + return -1; + } else { + // Otherwise, kick out lower priority. + StopChannel(cnum); + } + } + + c = channels[cnum]; + + // channel is decided to be cnum. + c.sfxinfo = sfxinfo; + c.origin = origin; + + return cnum; + } + + /** Nice one. A sound should have a maximum duration in tics, + * and we can give it a handle proportional to the future tics + * it should play until. Ofc, this means the minimum timeframe + * for cutting a sound off is just 1 tic. + * + * @param handle + * @return + */ + + /* + public boolean SoundIsPlaying(int handle) + { + // Ouch. + return (DS.gametic < handle); + } */ +} \ No newline at end of file diff --git a/doom/src/s/AbstractSoundDriver.java b/doom/src/s/AbstractSoundDriver.java new file mode 100644 index 0000000..8b5cef3 --- /dev/null +++ b/doom/src/s/AbstractSoundDriver.java @@ -0,0 +1,386 @@ +package s; + +import data.sfxinfo_t; +import data.sounds; +import static data.sounds.S_sfx; +import doom.DoomMain; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * Functionality and fields that are common among the various "sound drivers" + * should go here. + * + * @author Maes + */ +public abstract class AbstractSoundDriver implements ISoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(AbstractSoundDriver.class.getName()); + + protected final static boolean D = false; // debug + + protected final DoomMain DM; + + /** + * The global mixing buffer. Basically, samples from all active internal + * channels are modifed and added, and stored in the buffer that is + * submitted to the audio device. This is a 16-bit stereo signed PCM + * mixbuffer. Memory order is LSB (?) and channel order is L-R-L-R... + * + * Not all i + * + */ + protected byte[] mixbuffer;// = new byte[MIXBUFFERSIZE]; + + protected final int numChannels; + + /** The actual lengths of all sound effects. */ + protected final int[] lengths = new int[NUMSFX]; + + /** + * The sound in channel handles, determined on registration, might be used + * to unregister/stop/modify, currently unused. + */ + protected final int[] channelhandles; + + /** + * SFX id of the playing sound effect. Used to catch duplicates (like + * chainsaw). + */ + protected final int[] channelids; + + /** + * Pitch to stepping lookup, used in ClassicSoundDriver It's actually rigged + * to have a -/+ 400% pitch variation! + */ + protected final int[] steptable = new int[256]; + + /** Volume lookups. 128 levels */ + protected final int[][] vol_lookup = new int[128][256]; + + /** + * Time/gametic that the channel started playing, used to determine oldest, + * which automatically has lowest priority. In case number of active sounds + * exceeds available channels. + */ + protected final int[] channelstart; + + // protected final static DataLine.Info info = new DataLine.Info(Clip.class, + // format); + public AbstractSoundDriver(DoomMain DM, int numChannels) { + this.DM = DM; + this.numChannels = numChannels; + channelids = new int[numChannels]; + channelhandles = new int[numChannels]; + channelstart = new int[numChannels]; + } + + /** + * Generates volume lookup tables which also turn the unsigned samples into + * signed samples. + */ + protected final void generateVolumeLUT() { + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 256; j++) { + vol_lookup[i][j] = (i * (j - 128) * 256) / 127; + } + } + } + + /** + * This table provides step widths for pitch parameters. Values go from 16K + * to 256K roughly, with the middle of the table being 64K, and presumably + * representing unitary pitch. So the pitch variation can be quite extreme, + * allowing -/+ 400% stepping :-S + * + * @param steptablemid + * @return + */ + protected void generateStepTable(int steptablemid) { + for (int i = -128; i < 128; i++) { + steptable[steptablemid + i] + = (int) (Math.pow(2.0, (i / 64.0)) * 65536.0); + //System.out.printf("Pitch %d %d %f\n",i,steptable[steptablemid + i],FixedFloat.toFloat(steptable[steptablemid + i])); + } + } + + /** Read a Doom-format sound effect from disk, leaving it in 8-bit mono format but + * upsampling it to the target sample rate. + * + * @param sfxname + * @param len + * @param index + * @return + */ + protected byte[] getsfx(String sfxname, int[] len, int index) { + byte[] sfx; + byte[] paddedsfx; + int i; + int size; + int paddedsize; + String name; + int sfxlump; + + // Get the sound data from the WAD, allocate lump + // in zone memory. + name = String.format("ds%s", sfxname).toUpperCase(); + + // Now, there is a severe problem with the + // sound handling, in it is not (yet/anymore) + // gamemode aware. That means, sounds from + // DOOM II will be requested even with DOOM + // shareware. + // The sound list is wired into sounds.c, + // which sets the external variable. + // I do not do runtime patches to that + // variable. Instead, we will use a + // default sound for replacement. + if (DM.wadLoader.CheckNumForName(name) == -1) { + sfxlump = DM.wadLoader.GetNumForName("dspistol"); + } else { + sfxlump = DM.wadLoader.GetNumForName(name); + } + + DMXSound dmx = DM.wadLoader.CacheLumpNum(sfxlump, 0, DMXSound.class); + + // KRUDE + if (dmx.speed == SAMPLERATE / 2) { + // Plain linear interpolation. + dmx.data = DSP.crudeResample(dmx.data, 2); + //DSP.filter(dmx.data,SAMPLERATE, SAMPLERATE/4); + dmx.datasize = dmx.data.length; + + } + + sfx = dmx.data; + + // MAES: A-ha! So that's how they do it. + // SOund effects are padded to the highest multiple integer of + // the mixing buffer's size (with silence) + paddedsize + = ((dmx.datasize + (SAMPLECOUNT - 1)) / SAMPLECOUNT) * SAMPLECOUNT; + + // Allocate from zone memory. + paddedsfx = new byte[paddedsize]; + + // Now copy and pad. The first 8 bytes are header info, so we discard + // them. + System.arraycopy(sfx, 0, paddedsfx, 0, dmx.datasize); + + // Pad with silence (unsigned) + for (i = dmx.datasize; i < paddedsize; i++) { + paddedsfx[i] = (byte) 127; + } + + // Remove the cached lump. + DM.wadLoader.UnlockLumpNum(sfxlump); + + if (D) { + LOGGER.log(Level.FINE, + String.format("SFX %d name %s size %d speed %d padded to %d", index, S_sfx[index].name, dmx.datasize, dmx.speed, paddedsize)); + } + // Preserve padded length. + len[index] = paddedsize; + + // Return allocated padded data. + // So the first 8 bytes are useless? + return paddedsfx; + } + + /** + * Modified getsfx, which transforms samples into 16-bit, signed, stereo + * beforehand, before being "fed" to the audio clips. + * + * @param sfxname + * @param index + * @return + */ + protected final byte[] getsfx16(String sfxname, int[] len, int index) { + byte[] sfx; + byte[] paddedsfx; + int i; + int size; + int paddedsize; + String name; + int sfxlump; + + // Get the sound data from the WAD, allocate lump + // in zone memory. + name = String.format("ds%s", sfxname).toUpperCase(); + + // Now, there is a severe problem with the + // sound handling, in it is not (yet/anymore) + // gamemode aware. That means, sounds from + // DOOM II will be requested even with DOOM + // shareware. + // The sound list is wired into sounds.c, + // which sets the external variable. + // I do not do runtime patches to that + // variable. Instead, we will use a + // default sound for replacement. + if (DM.wadLoader.CheckNumForName(name) == -1) { + sfxlump = DM.wadLoader.GetNumForName("dspistol"); + } else { + sfxlump = DM.wadLoader.GetNumForName(name); + } + + size = DM.wadLoader.LumpLength(sfxlump); + + sfx = DM.wadLoader.CacheLumpNumAsRawBytes(sfxlump, 0); + + // Size blown up to accommodate two channels and 16 bits. + // Sampling rate stays the same. + paddedsize = (size - 8) * 2 * 2; + // Allocate from zone memory. + paddedsfx = new byte[paddedsize]; + + // Skip first 8 bytes (header), blow up the data + // to stereo, BIG ENDIAN, SIGNED, 16 bit. Don't expect any fancy DSP + // here! + int sample = 0; + for (i = 8; i < size; i++) { + // final short sam=(short) vol_lookup[127][0xFF&sfx[i]]; + final short sam = (short) ((0xFF & sfx[i] - 128) << 8); + paddedsfx[sample++] = (byte) (0xFF & (sam >> 8)); + paddedsfx[sample++] = (byte) (0xFF & sam); + paddedsfx[sample++] = (byte) (0xFF & (sam >> 8)); + paddedsfx[sample++] = (byte) (0xFF & sam); + } + + // Remove the cached lump. + DM.wadLoader.UnlockLumpNum(sfxlump); + + // Preserve padded length. + len[index] = paddedsize; + + // Return allocated padded data. + // So the first 8 bytes are useless? + return paddedsfx; + } + + /** + * Starting a sound means adding it to the current list of active sounds in + * the internal channels. As the SFX info struct contains e.g. a pointer to + * the raw data it is ignored. As our sound handling does not handle + * priority, it is ignored. Pitching (that is, increased speed of playback) + * is set, but whether it's used or not depends on the final implementation + * (e.g. classic mixer uses it, but AudioLine-based implementations are not + * guaranteed. + */ + @Override + public int StartSound(int id, int vol, int sep, int pitch, int priority) { + + if (id < 1 || id > S_sfx.length - 1) { + return BUSY_HANDLE; + } + // Find a free channel and get a timestamp/handle for the new sound. + + return this.addsfx(id, vol, steptable[pitch], sep); + } + + /** + * This function adds a sound to the list of currently active sounds, which + * is maintained as a given number (eight, usually) of internal channels. + * Returns a handle. + * + * @param sfxid + * @param volume + * @param step + * @param seperation + * @return + */ + protected abstract int addsfx(int sfxid, int volume, int step, + int seperation); + + protected short handlenums = 0; + + // + // Retrieve the raw data lump index + // for a given SFX name. + // + public final int GetSfxLumpNum(sfxinfo_t sfx) { + String namebuf; + namebuf = String.format("ds%s", sfx.name).toUpperCase(); + if (namebuf.equals("DSNONE")) { + return -1; + } + + int lump; + try { + lump = DM.wadLoader.GetNumForName(namebuf); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "GetSfxLumpNum failure", e); + return -1; + } + + return lump; + } + + /** + * Initialize + * + * @return + */ + protected final void initMixBuffer() { + for (int i = 0; i < MIXBUFFERSIZE; i += 4) { + mixbuffer[i] + = (byte) (((int) (0x7FFF * Math.sin(1.5 * Math.PI * (double) i + / MIXBUFFERSIZE)) & 0xff00) >>> 8); + mixbuffer[i + 1] + = (byte) ((int) (0x7FFF * Math.sin(1.5 * Math.PI * (double) i + / MIXBUFFERSIZE)) & 0xff); + mixbuffer[i + 2] + = (byte) (((int) (0x7FFF * Math.sin(1.5 * Math.PI * (double) i + / MIXBUFFERSIZE)) & 0xff00) >>> 8); + mixbuffer[i + 3] + = (byte) ((int) (0x7FFF * Math.sin(1.5 * Math.PI * (double) i + / MIXBUFFERSIZE)) & 0xff); + + } + } + + /** + * Loads samples in 8-bit format, forcibly converts them to the common sampling rate. + * Used by. + */ + protected final void initSound8() { + int i; + + // Initialize external data (all sounds) at start, keep static. + for (i = 1; i < NUMSFX; i++) { + // Alias? Example is the chaingun sound linked to pistol. + if (sounds.S_sfx[i].link == null) { + // Load data from WAD file. + S_sfx[i].data = getsfx(S_sfx[i].name, lengths, i); + } else { + // Previously loaded already? + S_sfx[i].data = S_sfx[i].link.data; + } + } + } + + /** + * This is only the common part of InitSound that caches sound data in + * 16-bit, stereo format (used by Audiolines). INTO sfxenum_t. + * + * Only used by the Clip and David "drivers". + * + */ + protected final void initSound16() { + int i; + + // Initialize external data (all sounds) at start, keep static. + for (i = 1; i < NUMSFX; i++) { + // Alias? Example is the chaingun sound linked to pistol. + if (sounds.S_sfx[i].link == null) { + // Load data from WAD file. + S_sfx[i].data = getsfx16(S_sfx[i].name, lengths, i); + } else { + // Previously loaded already? + S_sfx[i].data = S_sfx[i].link.data; + } + } + } + +} \ No newline at end of file diff --git a/doom/src/s/AudioChunk.java b/doom/src/s/AudioChunk.java new file mode 100644 index 0000000..7214e16 --- /dev/null +++ b/doom/src/s/AudioChunk.java @@ -0,0 +1,21 @@ +package s; + +public class AudioChunk { + + public AudioChunk() { + buffer = new byte[s.ISoundDriver.MIXBUFFERSIZE]; + setStuff(0, 0); + this.free = true; + } + + public void setStuff(int chunk, int time) { + this.chunk = chunk; + this.time = time; + } + + public int chunk; + public int time; + public byte[] buffer; + public boolean free; + +} \ No newline at end of file diff --git a/doom/src/s/ClassicDoomSoundDriver.java b/doom/src/s/ClassicDoomSoundDriver.java new file mode 100644 index 0000000..b686cba --- /dev/null +++ b/doom/src/s/ClassicDoomSoundDriver.java @@ -0,0 +1,704 @@ +package s; + +import static data.sounds.S_sfx; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import java.util.HashMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import mochadoom.Loggers; +import pooling.AudioChunkPool; + +/** + * A close recreation of the classic linux doom sound mixer. + * + * PROS: + * a) Very faithful to pitch and stereo effects, and original + * volume ramping. + * b) Uses only one audioline and one playback thread + * + * CONS: + * a) May sound a bit off if production/consumption rates don't match + * b) Sounds awful when mixing too many sounds together, just like the original. + * + * @author Maes + */ +public class ClassicDoomSoundDriver extends AbstractSoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(ClassicDoomSoundDriver.class.getName()); + + protected final Semaphore produce; + + protected final Semaphore consume; + + protected int chunk = 0; + + // protected FileOutputStream fos; + // protected DataOutputStream dao; + // The one and only line + protected SourceDataLine line = null; + + protected HashMap cachedSounds + = new HashMap<>(); + + public ClassicDoomSoundDriver(DoomMain DM, int numChannels) { + super(DM, numChannels); + channelleftvol_lookup = new int[numChannels][]; + channelrightvol_lookup = new int[numChannels][]; + channelstep = new int[numChannels]; + channelstepremainder = new int[numChannels]; + channels = new byte[numChannels][]; + p_channels = new int[numChannels]; + channelsend = new int[numChannels]; + produce = new Semaphore(1); + consume = new Semaphore(1); + produce.drainPermits(); + mixbuffer = new byte[MIXBUFFERSIZE]; + } + + /** The channel step amount... */ + protected final int[] channelstep; + + /** ... and a 0.16 bit remainder of last step. */ + protected final int[] channelstepremainder; + + /** + * The channel data pointers, start and end. These were referred to as + * "channels" in two different source files: s_sound.c and i_sound.c. In + * s_sound.c they are actually channel_t (they are only informational). In + * i_sound.c they are actual data channels. + */ + protected byte[][] channels; + + /** + * MAES: we'll have to use this for actual pointing. channels[] holds just + * the data. + */ + protected int[] p_channels; + + /** + * The second one is supposed to point at "the end", so I'll make it an int. + */ + protected int[] channelsend; + + /** Hardware left and right channel volume lookup. */ + protected final int[][] channelleftvol_lookup, channelrightvol_lookup; + + protected volatile boolean mixed = false; + + /** + * This function loops all active (internal) sound channels, retrieves a + * given number of samples from the raw sound data, modifies it according to + * the current (internal) channel parameters, mixes the per channel samples + * into the global mixbuffer, clamping it to the allowed range, and sets up + * everything for transferring the contents of the mixbuffer to the (two) + * hardware channels (left and right, that is). This function currently + * supports only 16bit. + */ + public void UpdateSound() { + + mixed = false; + + // Mix current sound data. + // Data, from raw sound, for right and left. + int sample = 0; + int dl; + int dr; + + // Pointers in global mixbuffer, left, right, end. + // Maes: those were explicitly signed short pointers... + int leftout; + int rightout; + int leftend; + // Step in mixbuffer, left and right, thus two. + int step; + + // Mixing channel index. + int chan; + + // POINTERS to Left and right channel + // which are in global mixbuffer, alternating. + leftout = 0; + rightout = 2; + step = 4; + + // Determine end, for left channel only + // (right channel is implicit). + // MAES: this implies that the buffer will only mix + // that many samples at a time, and that the size is just right. + // Thus, it must be flushed (p_mixbuffer=0) before reusing it. + leftend = SAMPLECOUNT * step; + + for (chan = 0; chan < numChannels; chan++) { + if (channels[chan] != null) // SOME mixing has taken place. + { + mixed = true; + } + } + + // Mix sounds into the mixing buffer. + // Loop over step*SAMPLECOUNT, + // that is SAMPLECOUNT values for two channels. + while (leftout < leftend) { + // Reset left/right value. + dl = 0; + dr = 0; + + // Love thy L2 chache - made this a loop. + // Now more channels could be set at compile time + // as well. Thus loop those channels. + for (chan = 0; chan < numChannels; chan++) { + + // if (D) System.err.printf("Checking channel %d\n",chan); + // Check channel, if active. + // MAES: this means that we must point to raw data here. + if (channels[chan] != null) { + int channel_pointer = p_channels[chan]; + + // Get the raw data from the channel. + // Maes: this is supposed to be an 8-bit unsigned value. + sample = 0x00FF & channels[chan][channel_pointer]; + + // Add left and right part for this channel (sound) + // to the current data. Adjust volume accordingly. + // Q: could this be optimized by converting samples to 16-bit + // at load time, while also allowing for stereo samples? + // A: Only for the stereo part. You would still look a lookup + // for the CURRENT volume level. + dl += channelleftvol_lookup[chan][sample]; + dr += channelrightvol_lookup[chan][sample]; + + // This should increment the index inside a channel, but is + // expressed in 16.16 fixed point arithmetic. + channelstepremainder[chan] += channelstep[chan]; + + // The actual channel pointer is increased here. + // The above trickery allows playing back different pitches. + // The shifting retains only the integer part. + channel_pointer += channelstepremainder[chan] >> 16; + + // This limits it to the "decimal" part in order to + // avoid undue accumulation. + channelstepremainder[chan] &= 0xFFFF; + + // Check whether we are done. Also to avoid overflows. + if (channel_pointer >= channelsend[chan]) { + // Reset pointer for a channel. + if (D) { + LOGGER.log(Level.INFO, String.format( + "Channel %d handle %d pointer %d thus done, stopping", + chan, this.channelhandles[chan], + channel_pointer)); + } + channels[chan] = null; + channel_pointer = 0; + } + + // Write pointer back, so we know where a certain channel + // is the next time UpdateSounds is called. + p_channels[chan] = channel_pointer; + } + + } // for all channels. + + // MAES: at this point, the actual values for a single sample + // (YIKES!) are in d1 and d2. We must use the leftout/rightout + // pointers to write them back into the mixbuffer. + // Clamp to range. Left hardware channel. + // Remnant of 8-bit mixing code? That must have raped ears + // and made them bleed. + // if (dl > 127) *leftout = 127; + // else if (dl < -128) *leftout = -128; + // else *leftout = dl; + if (dl > 0x7fff) { + dl = 0x7fff; + } else if (dl < -0x8000) { + dl = -0x8000; + } + + // Write left channel + mixbuffer[leftout] = (byte) ((dl & 0xFF00) >>> 8); + mixbuffer[leftout + 1] = (byte) (dl & 0x00FF); + + // Same for right hardware channel. + if (dr > 0x7fff) { + dr = 0x7fff; + } else if (dr < -0x8000) { + dr = -0x8000; + } + + // Write right channel. + mixbuffer[rightout] = (byte) ((dr & 0xFF00) >>> 8); + mixbuffer[rightout + 1] = (byte) (dr & 0x00FF); + + // Increment current pointers in mixbuffer. + leftout += 4; + rightout += 4; + } // End leftend/leftout while + + // Q: how do we know whether the mixbuffer isn't entirely used + // and instead it has residual garbage samples in it? + // A: DOOM kind of padded samples in memory, so presumably + // they all played silence. + // Q: what's the purpose of channelremainder etc? + // A: pitch variations were done with fractional pointers 16.16 + // style. + } + + /** + * SFX API Note: this was called by S_Init. However, whatever they did in + * the old DPMS based DOS version, this were simply dummies in the Linux + * version. See soundserver initdata(). + */ + @Override + public void SetChannels(int numChannels) { + // Init internal lookups (raw data, mixing buffer, channels). + // This function sets up internal lookups used during + // the mixing process. + + int steptablemid = 128; + + // Okay, reset internal mixing channels to zero. + for (int i = 0; i < this.numChannels; i++) { + channels[i] = null; + } + + generateStepTable(steptablemid); + + generateVolumeLUT(); + } + + protected MixServer SOUNDSRV; + + protected Thread SOUNDTHREAD; + + @Override + public boolean InitSound() { + + // Secure and configure sound device first. + LOGGER.log(Level.INFO, "I_InitSound"); + + // We only need a single data line. + // PCM, signed, 16-bit, stereo, 22025 KHz, 2048 bytes per "frame", + // maximum of 44100/2048 "fps" + AudioFormat format = new AudioFormat(SAMPLERATE, 16, 2, true, true); + + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + + if (AudioSystem.isLineSupported(info)) + try { + line = (SourceDataLine) AudioSystem.getSourceDataLine(format); + line.open(format, AUDIOLINE_BUFFER); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Could not play signed 16 data", e); + return false; + } + + if (line != null) { + LOGGER.log(Level.INFO, "configured audio device"); + line.start(); + } else { + LOGGER.log(Level.SEVERE, "could not configure audio device"); + return false; + } + + // This was here only for debugging purposes + /* + * try { fos=new FileOutputStream("test.raw"); dao=new + * DataOutputStream(fos); } catch (FileNotFoundException e) { + * Auto-generated catch block e.printStackTrace(); } + */ + SOUNDSRV = new MixServer(line); + SOUNDTHREAD = new Thread(SOUNDSRV); + SOUNDTHREAD.start(); + + // Initialize external data (all sounds) at start, keep static. + LOGGER.log(Level.INFO, "I_InitSound"); + + super.initSound8(); + + LOGGER.log(Level.INFO, "pre-cached all sound data"); + + // Now initialize mixbuffer with zero. + initMixBuffer(); + + // Finished initialization. + LOGGER.log(Level.INFO, "I_InitSound: sound module ready"); + + return true; + } + + @Override + protected int addsfx(int sfxid, int volume, int step, int seperation) { + int i; + int rc = -1; + + int oldest = DM.gametic; + int oldestnum = 0; + int slot; + + int rightvol; + int leftvol; + + int broken = -1; + + // Chainsaw troubles. + // Play these sound effects only one at a time. + if ((sfxid >= sfxenum_t.sfx_sawup.ordinal() + && sfxid <= sfxenum_t.sfx_sawhit.ordinal()) + || sfxid == sfxenum_t.sfx_stnmov.ordinal() + || sfxid == sfxenum_t.sfx_pistol.ordinal()) { + // Loop all channels, check. + for (i = 0; i < numChannels; i++) { + // Active, and using the same SFX? + if ((channels[i] != null) && (channelids[i] == sfxid)) { + // Reset. + this.p_channels[i] = 0; + this.channels[i] = null; + // We are sure that iff, + // there will only be one. + broken = i; + break; + } + } + } + + // Loop all channels to find oldest SFX. + if (broken >= 0) { + i = broken; + oldestnum = broken; + } else { + for (i = 0; (i < numChannels) && (channels[i] != null); i++) { + if (channelstart[i] < oldest) { + oldestnum = i; + } + } + } + + oldest = channelstart[oldestnum]; + + // Tales from the cryptic. + // If we found a channel, fine. + // If not, we simply overwrite the first one, 0. + // Probably only happens at startup. + if (i == numChannels) { + slot = oldestnum; + } else { + slot = i; + } + + // Okay, in the less recent channel, + // we will handle the new SFX. + // Set pointer to raw data. + channels[slot] = S_sfx[sfxid].data; + + // MAES: if you don't zero-out the channel pointer here, it gets ugly + p_channels[slot] = 0; + + // Set pointer to end of raw data. + channelsend[slot] = lengths[sfxid]; + + // Reset current handle number, limited to 0..100. + if (handlenums == 0) // was !handlenums, so it's actually 1...100? + { + handlenums = 100; + } + + // Assign current handle number. + // Preserved so sounds could be stopped (unused). + // Maes: this should really be decreasing, otherwide handles + // should start at 0 and go towards 100. Just saying. + channelhandles[slot] = rc = handlenums--; + + // Set stepping??? + // Kinda getting the impression this is never used. + // MAES: you're wrong amigo. + channelstep[slot] = step; + // ??? + channelstepremainder[slot] = 0; + // Should be gametic, I presume. + channelstart[slot] = DM.gametic; + + // Separation, that is, orientation/stereo. + // range is: 1 - 256 + seperation += 1; + + // Per left/right channel. + // x^2 seperation, + // adjust volume properly. + leftvol = volume - ((volume * seperation * seperation) >> 16); // /(256*256); + seperation = seperation - 257; + rightvol = volume - ((volume * seperation * seperation) >> 16); + + // Sanity check, clamp volume. + // Maes: better to clamp than to crash, no? + if (rightvol < 0) { + rightvol = 0; + } + if (rightvol > 127) { + rightvol = 127; + } + if (leftvol < 0) { + leftvol = 0; + } + if (leftvol > 127) { + leftvol = 127; + } + + // Get the proper lookup table piece + // for this volume level??? + channelleftvol_lookup[slot] = vol_lookup[leftvol]; + channelrightvol_lookup[slot] = vol_lookup[rightvol]; + + // Preserve sound SFX id, + // e.g. for avoiding duplicates of chainsaw. + channelids[slot] = sfxid; + + if (D) { + LOGGER.log(Level.FINE, String.valueOf(channelStatus())); + } + if (D) { + LOGGER.log(Level.FINE, String.format( + "Playing sfxid %d handle %d length %d vol %d on channel %d", + sfxid, rc, S_sfx[sfxid].data.length, volume, slot)); + } + + // You tell me. + return rc; + } + + @Override + public void ShutdownSound() { + + boolean done = false; + + // Unlock sound thread if it's waiting. + produce.release(); + + int i; + while (!done) { + for (i = 0; i < numChannels && (channels[i] == null); i++) { + + } + + // System.err.printf("%d channels died off\n",i); + UpdateSound(); + SubmitSound(); + if (i == numChannels) { + done = true; + } + } + + this.line.drain(); + SOUNDSRV.terminate = true; + produce.release(); + + try { + SOUNDTHREAD.join(); + } catch (InterruptedException e) { + // Well, I don't care. + } + line.close(); + + } + + protected class MixServer + implements Runnable { + + public boolean terminate = false; + + public MixServer(SourceDataLine line) { + this.auline = line; + } + + private SourceDataLine auline; + + private ArrayBlockingQueue audiochunks + = new ArrayBlockingQueue<>(BUFFER_CHUNKS * 2); + + public void addChunk(AudioChunk chunk) { + audiochunks.offer(chunk); + } + + public volatile int currstate = 0; + + public void run() { + + while (!terminate) { + + // while (timing[mixstate]<=mytime){ + // Try acquiring a produce permit before going on. + try { + // System.err.println("Waiting for a permit..."); + produce.acquire(); + // System.err.println("Got a permit"); + } catch (InterruptedException e) { + // Well, ouch. + LOGGER.log(Level.SEVERE, "MixServer run failure", e); + } + + int chunks = 0; + + // System.err.printf("Audio queue has %d chunks\n",audiochunks.size()); + // Play back only at most a given number of chunks once you reach + // this spot + int atMost = Math.min(ISoundDriver.BUFFER_CHUNKS, audiochunks.size()); + + while (atMost-- > 0) { + + AudioChunk chunk = null; + try { + chunk = audiochunks.take(); + } catch (InterruptedException e1) { + // Should not block + } + // Play back all chunks present in a buffer ASAP + auline.write(chunk.buffer, 0, MIXBUFFERSIZE); + chunks++; + // No matter what, give the chunk back! + chunk.free = true; + audiochunkpool.checkIn(chunk); + } + + // Signal that we consumed a whole buffer and we are ready for + // another one. + consume.release(); + } + } + } + + @Override + public boolean SoundIsPlaying(int handle) { + + int c = getChannelFromHandle(handle); + return (c != -2 && channels[c] == null); + + } + + /** + * Internal use. + * + * @param handle + * @return the channel that has the handle, or -2 if none has it. + */ + protected int getChannelFromHandle(int handle) { + // Which channel has it? + for (int i = 0; i < numChannels; i++) { + if (channelhandles[i] == handle) { + return i; + } + } + + return BUSY_HANDLE; + } + + @Override + public void StopSound(int handle) { + // Which channel has it? + int hnd = getChannelFromHandle(handle); + if (hnd >= 0) { + channels[hnd] = null; + p_channels[hnd] = 0; + this.channelhandles[hnd] = IDLE_HANDLE; + } + } + + @Override + public void SubmitSound() { + + // It's possible for us to stay silent and give the audio + // queue a chance to get drained. + if (mixed) { + silence = 0; + AudioChunk gunk = audiochunkpool.checkOut(); + // Ha ha you're ass is mine! + gunk.free = false; + + // System.err.printf("Submitted sound chunk %d to buffer %d \n",chunk,mixstate); + // Copy the currently mixed chunk into its position inside the + // master buffer. + System.arraycopy(mixbuffer, 0, gunk.buffer, 0, MIXBUFFERSIZE); + + this.SOUNDSRV.addChunk(gunk); + + // System.err.println(chunk++); + chunk++; + // System.err.println(chunk); + + if (consume.tryAcquire()) { + produce.release(); + } + + } else { + silence++; + // MAES: attempt to fix lingering noise error + if (silence > ISoundDriver.BUFFER_CHUNKS * 5) { + line.flush(); + silence = 0; + } + // System.err.println("SILENT_CHUNK"); + // this.SOUNDSRV.addChunk(SILENT_CHUNK); + } + // line.write(mixbuffer, 0, mixbuffer.length); + + } + + private int silence = 0; + + @Override + public void UpdateSoundParams(int handle, int vol, int sep, int pitch) { + + int chan = this.getChannelFromHandle(handle); + // Per left/right channel. + // x^2 seperation, + // adjust volume properly. + int leftvol = vol - ((vol * sep * sep) >> 16); // /(256*256); + sep = sep - 257; + int rightvol = vol - ((vol * sep * sep) >> 16); + + // Sanity check, clamp volume. + if (rightvol < 0 || rightvol > 127) { + DM.doomSystem.Error("rightvol out of bounds"); + } + + if (leftvol < 0 || leftvol > 127) { + DM.doomSystem.Error("leftvol out of bounds"); + } + + // Get the proper lookup table piece + // for this volume level??? + channelleftvol_lookup[chan] = vol_lookup[leftvol]; + channelrightvol_lookup[chan] = vol_lookup[rightvol]; + + // Well, if you can get pitch to change too... + this.channelstep[chan] = steptable[pitch]; + channelsend[chan] = this.lengths[this.channelids[chan]]; + + } + + protected StringBuilder sb = new StringBuilder(); + + public String channelStatus() { + sb.setLength(0); + for (int i = 0; i < numChannels; i++) { + if (channels[i] != null) { + sb.append(i); + } else { + sb.append('-'); + } + } + + return sb.toString(); + + } + + protected final AudioChunk SILENT_CHUNK = new AudioChunk(); + + protected final AudioChunkPool audiochunkpool = new AudioChunkPool(); +} \ No newline at end of file diff --git a/doom/src/s/ClipSFXModule.java b/doom/src/s/ClipSFXModule.java new file mode 100644 index 0000000..c22257d --- /dev/null +++ b/doom/src/s/ClipSFXModule.java @@ -0,0 +1,459 @@ +package s; + +import static data.sounds.S_sfx; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import java.util.Collection; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.FloatControl; +import javax.sound.sampled.FloatControl.Type; +import javax.sound.sampled.LineUnavailableException; +import mochadoom.Loggers; + +/** Experimental Clip based driver. It does work, but it has no + * tangible advantages over the Audioline or Classic one. If the + * Audioline can be used, there's no reason to fall back to this + * one. + * + * KNOWN ISSUES: + * + * a) Same general restrictions as audiolines (in fact, Clips ARE Audioline + * in disguise) + * b) Multiple instances of the same sound require multiple clips, so + * even caching them is a half-baked solution, and if you have e.g. 40 imps + * sound in a room.... + * + * + * Currently unused. + * + * @author Velktron + * + */ +public class ClipSFXModule extends AbstractSoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(ClipSFXModule.class.getName()); + + HashMap cachedSounds = new HashMap<>(); + + // Either it's null (no clip is playing) or non-null (some clip is playing). + Clip[] channels; + + public final float[] linear2db; + + public ClipSFXModule(DoomMain DM, int numChannels) { + super(DM, numChannels); + linear2db = computeLinear2DB(); + } + + private float[] computeLinear2DB() { + + // Maximum volume is 0 db, minimum is ... -96 db. + // We rig this so that half-scale actually gives quarter power, + // and so is -6 dB. + float[] tmp = new float[VOLUME_STEPS]; + + for (int i = 0; i < VOLUME_STEPS; i++) { + float linear = (float) (20 * Math.log10((float) i / (float) VOLUME_STEPS)); + // Hack. The minimum allowed value as of now is -80 db. + if (linear < -36.0) { + linear = -36.0f; + } + tmp[i] = linear; + + } + + return tmp; + } + + @Override + public boolean InitSound() { + // Secure and configure sound device first. + LOGGER.log(Level.INFO, "I_InitSound"); + + // We don't actually do this here (will happen only when we + // create the first audio clip). + // Initialize external data (all sounds) at start, keep static. + initSound16(); + + LOGGER.log(Level.INFO, "pre-cached all sound data"); + // Finished initialization. + LOGGER.log(Level.INFO, "I_InitSound: sound module ready"); + return true; + + } + + /** Modified getsfx. The individual length of each sfx is not of interest. + * However, they must be transformed into 16-bit, signed, stereo samples + * beforehand, before being "fed" to the audio clips. + * + * @param sfxname + * @param index + * @return + */ + protected byte[] getsfx(String sfxname, int index) { + byte[] sfx; + byte[] paddedsfx; + int i; + int size; + int paddedsize; + String name; + int sfxlump; + + // Get the sound data from the WAD, allocate lump + // in zone memory. + name = String.format("ds%s", sfxname).toUpperCase(); + + // Now, there is a severe problem with the + // sound handling, in it is not (yet/anymore) + // gamemode aware. That means, sounds from + // DOOM II will be requested even with DOOM + // shareware. + // The sound list is wired into sounds.c, + // which sets the external variable. + // I do not do runtime patches to that + // variable. Instead, we will use a + // default sound for replacement. + if (DM.wadLoader.CheckNumForName(name) == -1) { + sfxlump = DM.wadLoader.GetNumForName("dspistol"); + } else { + sfxlump = DM.wadLoader.GetNumForName(name); + } + + size = DM.wadLoader.LumpLength(sfxlump); + + sfx = DM.wadLoader.CacheLumpNumAsRawBytes(sfxlump, 0); + + // Size blown up to accommodate two channels and 16 bits. + // Sampling rate stays the same. + paddedsize = (size - 8) * 2 * 2; + // Allocate from zone memory. + paddedsfx = new byte[paddedsize]; + + // Skip first 8 bytes (header), blow up the data + // to stereo, BIG ENDIAN, SIGNED, 16 bit. Don't expect any fancy DSP here! + int sample = 0; + for (i = 8; i < size; i++) { + // final short sam=(short) vol_lookup[127][0xFF&sfx[i]]; + final short sam = (short) ((0xFF & sfx[i] - 128) << 8); + paddedsfx[sample++] = (byte) (0xFF & (sam >> 8)); + paddedsfx[sample++] = (byte) (0xFF & sam); + paddedsfx[sample++] = (byte) (0xFF & (sam >> 8)); + paddedsfx[sample++] = (byte) (0xFF & sam); + } + + // Remove the cached lump. + DM.wadLoader.UnlockLumpNum(sfxlump); + + // Return allocated padded data. + // So the first 8 bytes are useless? + return paddedsfx; + } + + @Override + public void UpdateSound() { + // We do nothing here, since the mixing is delegated to the OS + // Just hope that it's more efficient that our own... + + } + + @Override + public void SubmitSound() { + // Dummy. Nothing actual to do here. + + } + + @Override + public void ShutdownSound() { + // Wait till all pending sounds are finished. + boolean done = false; + int i; + + // FIXME (below). + //fprintf( stderr, "I_ShutdownSound: NOT finishing pending sounds\n"); + //fflush( stderr ); + while (!done) { + for (i = 0; i < numChannels && ((channels[i] == null) || (!channels[i].isActive())); i++); + // FIXME. No proper channel output. + if (i == numChannels) { + done = true; + } + } + + for (i = 0; i < numChannels; i++) { + if (channels[i] != null) { + channels[i].close(); + } + } + + // Free up resources taken up by cached clips. + Collection clips = this.cachedSounds.values(); + for (Clip c : clips) { + c.close(); + } + // Done. + + } + + @Override + public void SetChannels(int numChannels) { + channels = new Clip[numChannels]; + } + + private final void getClipForChannel(int c, int sfxid) { + + // Try to see if we already have such a clip. + Clip clip = this.cachedSounds.get(sfxid); + + boolean exists = false; + + // Does it exist? + if (clip != null) { + + // Well, it does, but we are not done yet. + exists = true; + // Is it NOT playing already? + if (!clip.isActive()) { + // Assign it to the channel. + channels[c] = clip; + return; + } + } + + // Sorry, Charlie. Gotta make a new one. + DataLine.Info info = new DataLine.Info(Clip.class, DoomSound.DEFAULT_SAMPLES_FORMAT); + + try { + clip = (Clip) AudioSystem.getLine(info); + } catch (LineUnavailableException e) { + LOGGER.log(Level.SEVERE, "Line unavailable", e); + } + try { + clip.open(DoomSound.DEFAULT_SAMPLES_FORMAT, S_sfx[sfxid].data, 0, S_sfx[sfxid].data.length); + } catch (LineUnavailableException e) { + LOGGER.log(Level.SEVERE, "Line unavailable", e); + } + + if (!exists) { + this.cachedSounds.put(sfxid, clip); + } + + channels[c] = clip; + + // Control[] cs=clip.getControls(); + // + // for (Control cc:cs){ + // System.out.println("Control "+cc.getType().toString()); + // } + } + + // + // This function adds a sound to the + // list of currently active sounds, + // which is maintained as a given number + // (eight, usually) of internal channels. + // Returns a handle. + // + protected short handlenums = 0; + + protected int addsfx(int sfxid, int volume, int pitch, int seperation) { + int i; + int rc = -1; + + int oldest = DM.gametic; + int oldestnum = 0; + int slot; + + // Chainsaw troubles. + // Play these sound effects only one at a time. + if (sfxid == sfxenum_t.sfx_sawup.ordinal() + || sfxid == sfxenum_t.sfx_sawidl.ordinal() + || sfxid == sfxenum_t.sfx_sawful.ordinal() + || sfxid == sfxenum_t.sfx_sawhit.ordinal() + || sfxid == sfxenum_t.sfx_stnmov.ordinal() + || sfxid == sfxenum_t.sfx_pistol.ordinal()) { + // Loop all channels, check. + for (i = 0; i < numChannels; i++) { + // Active, and using the same SFX? + if (channels[i] != null && channels[i].isRunning() + && channelids[i] == sfxid) { + // Reset. + channels[i].stop(); + // We are sure that iff, + // there will only be one. + break; + } + } + } + + // Loop all channels to find oldest SFX. + for (i = 0; (i < numChannels) && (channels[i] != null); i++) { + if (channelstart[i] < oldest) { + oldestnum = i; + oldest = channelstart[i]; + } + } + + // Tales from the cryptic. + // If we found a channel, fine. + // If not, we simply overwrite the first one, 0. + // Probably only happens at startup. + if (i == numChannels) { + slot = oldestnum; + } else { + slot = i; + } + + // Okay, in the less recent channel, + // we will handle the new SFX. + // We need to decide whether we can reuse an existing clip + // or create a new one. In any case, when this method return + // we should have a valid clip assigned to channel "slot". + getClipForChannel(slot, sfxid); + + // Reset current handle number, limited to 0..100. + if (handlenums == 0) // was !handlenums, so it's actually 1...100? + { + handlenums = MAXHANDLES; + } + + // Assign current handle number. + // Preserved so sounds could be stopped (unused). + channelhandles[slot] = rc = handlenums--; + + // Should be gametic, I presume. + channelstart[slot] = DM.gametic; + + // Get the proper lookup table piece + // for this volume level??? + //channelleftvol_lookup[slot] = vol_lookup[leftvol]; + //channelrightvol_lookup[slot] = vol_lookup[rightvol]; + // Preserve sound SFX id, + // e.g. for avoiding duplicates of chainsaw. + channelids[slot] = sfxid; + + setVolume(slot, volume); + setPanning(slot, seperation); + //channels[slot].addSound(sound, handlenums); + //channels[slot].setPitch(pitch); + + if (D) { + LOGGER.log(Level.FINE, channelStatus()); + } + if (D) { + LOGGER.log(Level.FINE, String.format("Playing %d vol %d on channel %d\n", rc, volume, slot)); + } + // Well...play it. + + // FIXME VERY BIG PROBLEM: stop() is blocking!!!! WTF ?! + //channels[slot].stop(); + //long a=System.nanoTime(); + channels[slot].setFramePosition(0); + channels[slot].start(); + // b=System.nanoTime(); + //System.err.printf("Sound playback completed in %d\n",(b-a)); + + // You tell me. + return rc; + } + + /** Accepts volume in "Doom" format (0-127). + * + * @param volume + */ + public void setVolume(int chan, int volume) { + Clip c = channels[chan]; + + if (c.isControlSupported(Type.MASTER_GAIN)) { + FloatControl vc = (FloatControl) c.getControl(Type.MASTER_GAIN); + float vol = linear2db[volume]; + vc.setValue(vol); + } else if (c.isControlSupported(Type.VOLUME)) { + FloatControl vc = (FloatControl) c.getControl(Type.VOLUME); + float vol = vc.getMinimum() + (vc.getMaximum() - vc.getMinimum()) * (float) volume / 127f; + vc.setValue(vol); + } + } + + public void setPanning(int chan, int sep) { + Clip c = channels[chan]; + + if (c.isControlSupported(Type.PAN)) { + FloatControl bc = (FloatControl) c.getControl(Type.PAN); + // Q: how does Doom's sep map to stereo panning? + // A: Apparently it's 0-255 L-R. + float pan = bc.getMinimum() + (bc.getMaximum() - bc.getMinimum()) * (float) sep / ISoundDriver.PANNING_STEPS; + bc.setValue(pan); + } + } + + @Override + public void StopSound(int handle) { + // Which channel has it? + int hnd = getChannelFromHandle(handle); + if (hnd >= 0) { + channels[hnd].stop(); + channels[hnd] = null; + } + } + + @Override + public boolean SoundIsPlaying(int handle) { + + return getChannelFromHandle(handle) != BUSY_HANDLE; + } + + @Override + public void UpdateSoundParams(int handle, int vol, int sep, int pitch) { + + // This should be called on sounds that are ALREADY playing. We really need + // to retrieve channels from their handles. + //System.err.printf("Updating sound with handle %d vol %d sep %d pitch %d\n",handle,vol,sep,pitch); + int i = getChannelFromHandle(handle); + // None has it? + if (i != BUSY_HANDLE) { + //System.err.printf("Updating sound with handle %d in channel %d\n",handle,i); + setVolume(i, vol); + setPanning(i, sep); + //channels[i].setPanning(sep); + } + + } + + /** Internal use. + * + * @param handle + * @return the channel that has the handle, or -2 if none has it. + */ + private int getChannelFromHandle(int handle) { + // Which channel has it? + for (int i = 0; i < numChannels; i++) { + if (channelhandles[i] == handle) { + return i; + } + } + + return BUSY_HANDLE; + } + + StringBuilder sb = new StringBuilder(); + + public String channelStatus() { + sb.setLength(0); + for (int i = 0; i < numChannels; i++) { + if (channels[i] != null && channels[i].isActive()) { + sb.append(i); + } else { + sb.append('-'); + } + } + + return sb.toString(); + + } + +} \ No newline at end of file diff --git a/doom/src/s/DMXSound.java b/doom/src/s/DMXSound.java new file mode 100644 index 0000000..425dfc9 --- /dev/null +++ b/doom/src/s/DMXSound.java @@ -0,0 +1,36 @@ +package s; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import w.CacheableDoomObject; + +/** An object representation of Doom's sound format */ +public class DMXSound implements CacheableDoomObject { + + /** ushort, all Doom samples are "type 3". No idea how */ + public int type; + /** ushort, speed in Hz. */ + public int speed; + /** uint */ + public int datasize; + + public byte[] data; + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + type = buf.getChar(); + speed = buf.getChar(); + try { + datasize = buf.getInt(); + } catch (BufferUnderflowException e) { + datasize = buf.capacity() - buf.position(); + } + data = new byte[Math.min(buf.remaining(), datasize)]; + buf.get(data); + } + +} \ No newline at end of file diff --git a/doom/src/s/DSP.java b/doom/src/s/DSP.java new file mode 100644 index 0000000..06d4587 --- /dev/null +++ b/doom/src/s/DSP.java @@ -0,0 +1,220 @@ +package s; + +public class DSP { + + /** + * QDSS Windowed Sinc ReSampling subroutine in Basic + * + * @param x + * new sample point location (relative to old indexes) (e.g. every + * other integer for 0.5x decimation) + * @param indat + * = original data array + * @param alim + * = size of data array + * @param fmax + * = low pass filter cutoff frequency + * @param fsr + * = sample rate + * @param wnwdth + * = width of windowed Sinc used as the low pass filter rem resamp() + * returns a filtered new sample point + */ + public float resamp(float x, float[] indat, int alim, float fmax, + float fsr, int wnwdth) { + int i, j; + float r_w, r_g, r_a; + int r_snc, r_y; // some local variables + r_g = 2 * fmax / fsr; // Calc gain correction factor + r_y = 0; + for (i = -wnwdth / 2; i < wnwdth / 2; i++) { // For 1 window width + j = (int) (x + i); // Calc input sample index + // calculate von Hann Window. Scale and calculate Sinc + r_w + = (float) (0.5 - 0.5 * Math.cos(2 * Math.PI + * (0.5 + (j - x) / wnwdth))); + r_a = (float) (2 * Math.PI * (j - x) * fmax / fsr); + r_snc = 1; + if (Math.abs(r_a) > 0) { + r_snc = (int) (Math.sin(r_a) / r_a); + } + if ((j >= 0) && (j < alim)) { + r_y = (int) (r_y + r_g * r_w * r_snc * indat[j]); + } + } + return r_y; // return new filtered sample + } + + /* + * Ron Nicholson's QDSS ReSampler cookbook recipe QDSS = Quick, Dirty, + * Simple and Short Version 0.1b - 2007-Aug-01 Copyright 2007 Ronald H. + * Nicholson Jr. No warranties implied. Error checking, optimization, and + * quality assessment of the "results" is left as an exercise for the + * student. (consider this code Open Source under a BSD style license) IMHO. + * YMMV. http://www.nicholson.com/rhn/dsp.html + */ + /** + * R. Nicholson's QDDS FIR filter generator cookbook recipe QDDS = Quick, + * Dirty, Dumb and Short version 0.6b - 2006-Dec-14, 2007-Sep-30 No + * warranties implied. Error checking, optimization, and quality assessment + * of the "results" is left as an exercise for the student. (consider this + * code Open Source under a BSD style license) Some example filter + * parameters: + * + * @param fsr + * = 44100 : rem set fsr = sample rate + * @param fc + * = 0 : rem set fc = 0 for lowpass fc = center frequency for + * bandpass filter fc = fsr/2 for a highpass + * @param bw + * = 3000 : rem bw = bandwidth, range 0 .. fsr/2 and bw >= fsr/n bw = + * 3 db corner frequency for a lowpass bw = half the 3 db passband + * for a bandpass filter + * @param nt + * = 128 : rem nt = number of taps + 1 (nt must be even) nt should be + * > fsr / bw transition band will be around 3.5 * fsr / nt depending + * on the window applied and ripple spec. + * @param g + * = 1 : rem g = filter gain for bandpass g = 0.5 , half the gain for + * a lowpass filter + * @return array of FIR taps + */ + public static double[] wsfiltgen(int nt, double fc, double fsr, double bw, double g) { + double[] fir = new double[nt];// + // fir(0) = 0 + // fir(1) is the first tap + // fir(nt/2) is the middle tap + // fir(nt-1) is the last tap + + double a, ys, yg, yf, yw; + for (int i = 1; i < nt; i++) { + a = (i - nt / 2) * 2.0 * Math.PI * bw / fsr; // scale Sinc width + ys = 1; + if (Math.abs(a) > 0) { + ys = Math.sin(a) / a; // calculate Sinc function + } + yg = g * (4.0 * bw / fsr); // correct window gain + yw = 0.54 - 0.46 * Math.cos(i * 2.0 * Math.PI / nt); // Hamming + // window + yf = Math.cos((i - nt / 2) * 2.0 * Math.PI * fc / fsr); // spectral + // shift to + // fc + fir[i] = yf * yw * yg * ys; // rem assign fir coeff. + } + return fir; + } + + public static void main(String[] argv) { + double[] fir = wsfiltgen(128, 11025 / 2.0, 22050, 22050 * 3.0 / 4, 0.5); + //System.out.println(fir); + + } + + public static byte[] crudeResample(byte[] input, int factor) { + + if (input == null || input.length < 1) { + return null; + } + + final int LEN = input.length; + + byte[] res = new byte[LEN * factor]; + int k = 0; + float start, end; + + res[0] = input[0]; + + for (int i = 0; i < LEN; i++) { + + if (i == 0) { + start = 127; + } else { + start = 0xFF & input[i]; + } + + if (i < LEN - 1) { + end = 0xFF & input[i + 1]; + } else { + end = 127; + } + + double slope = (end - start) / factor; + + res[k] = input[i]; + //res[k+factor]=input[i+1]; + + for (int j = 1; j < factor; j++) { + double ratio = j / (double) factor; + double value = start + slope * ratio; + byte bval = (byte) Math.round(value); + res[k + j] = bval; + } + k += factor; + } + + return res; + + } + + public static void filter(byte[] input, int samplerate, int cutoff) { + + double[] tmp = new double[input.length]; + + // Normalize + for (int i = 0; i < input.length; i++) { + tmp[i] = (0xFF & input[i]) / 255.0; + } + + filter(tmp, samplerate, cutoff, tmp.length); + + // De-normalize + for (int i = 0; i < input.length; i++) { + input[i] = (byte) (0xFF & (int) (tmp[i] * 255.0)); + } + + } + + /** Taken from here + * http://baumdevblog.blogspot.gr/2010/11/butterworth-lowpass-filter-coefficients.html + */ + private static void getLPCoefficientsButterworth2Pole(final int samplerate, final double cutoff, final double[] ax, final double[] by) { + double PI = 3.1415926535897932385; + double sqrt2 = 1.4142135623730950488; + + double QcRaw = (2 * PI * cutoff) / samplerate; // Find cutoff frequency in [0..PI] + double QcWarp = Math.tan(QcRaw); // Warp cutoff frequency + + double gain = 1 / (1 + sqrt2 / QcWarp + 2 / (QcWarp * QcWarp)); + by[2] = (1 - sqrt2 / QcWarp + 2 / (QcWarp * QcWarp)) * gain; + by[1] = (2 - 2 * 2 / (QcWarp * QcWarp)) * gain; + by[0] = 1; + ax[0] = 1 * gain; + ax[1] = 2 * gain; + ax[2] = 1 * gain; + } + + public static void filter(double[] samples, int smp, double cutoff, int count) { + // Essentially a 3-tap filter? + double[] ax = new double[3]; + double[] by = new double[3]; + double[] xv = new double[3]; + double[] yv = new double[3]; + + getLPCoefficientsButterworth2Pole(smp, cutoff, ax, by); + + for (int i = 0; i < count; i++) { + xv[2] = xv[1]; + xv[1] = xv[0]; + xv[0] = samples[i]; + + yv[2] = yv[1]; + yv[1] = yv[0]; + yv[0] = (ax[0] * xv[0] + ax[1] * xv[1] + ax[2] * xv[2] + - by[1] * yv[0] + - by[2] * yv[1]); + + samples[i] = yv[0]; + } + } + +} \ No newline at end of file diff --git a/doom/src/s/DavidMusicModule.java b/doom/src/s/DavidMusicModule.java new file mode 100644 index 0000000..559bdf7 --- /dev/null +++ b/doom/src/s/DavidMusicModule.java @@ -0,0 +1,194 @@ +package s; + +import java.io.ByteArrayInputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.SysexMessage; +import javax.sound.midi.Transmitter; +import mochadoom.Loggers; + +/** Concern separated from David Martel's MIDI & MUS player + * for Mocha Doom. Greatly improved upon by finnw, perfecting volume changes + * and MIDI device detection. + * + * @author David Martel + * @author velktron + * @author finnw + * + */ +public class DavidMusicModule implements IMusic { + + private static final Logger LOGGER = Loggers.getLogger(DavidMusicModule.class.getName()); + + public static final int CHANGE_VOLUME = 7; + public static final int CHANGE_VOLUME_FINE = 9; + + Sequencer sequencer; + VolumeScalingReceiver receiver; + Transmitter transmitter; + boolean songloaded; + + public DavidMusicModule() { + + } + + @Override + public void InitMusic() { + try { + + int x = -1; + MidiDevice.Info[] info = MidiSystem.getMidiDeviceInfo(); + for (int i = 0; i < info.length; i++) { + MidiDevice mdev = MidiSystem.getMidiDevice(info[i]); + if (mdev instanceof Sequencer) { + x = i; + } + // System.out.println(info[i].getName()+"\t\t\t"+ mdev.isOpen()+"\t"+mdev.hashCode()); + + } + + //System.out.printf("x %d y %d \n",x,y); + //--This sets the Sequencer and Synthesizer + //--The indices x and y correspond to the correct entries for the + //--default Sequencer and Synthesizer, as determined above + if (x != -1) { + sequencer = (Sequencer) MidiSystem.getMidiDevice(info[x]); + } else { + sequencer = (Sequencer) MidiSystem.getSequencer(false); + } + sequencer.open(); + + receiver = VolumeScalingReceiver.getInstance(); + // Configure General MIDI level 1 + sendSysexMessage(receiver, (byte) 0xf0, (byte) 0x7e, (byte) 0x7f, (byte) 9, (byte) 1, (byte) 0xf7); + transmitter = sequencer.getTransmitter(); + transmitter.setReceiver(receiver); + } catch (MidiUnavailableException e) { + LOGGER.log(Level.SEVERE, "InitMusic: midi unavilable", e); + } + } + + private static void sendControlChange(Receiver receiver, int midiChan, int ctrlId, int value) { + ShortMessage msg = new ShortMessage(); + try { + msg.setMessage(ShortMessage.CONTROL_CHANGE, midiChan, ctrlId, value); + } catch (InvalidMidiDataException ex) { + throw new RuntimeException(ex); + } + receiver.send(msg, -1); + } + + private static void sendSysexMessage(Receiver receiver, byte... message) { + SysexMessage msg = new SysexMessage(); + try { + msg.setMessage(message, message.length); + } catch (InvalidMidiDataException ex) { + throw new RuntimeException(ex); + } + receiver.send(msg, -1); + } + + @Override + public void ShutdownMusic() { + sequencer.stop(); + sequencer.close(); + } + + @Override + public void SetMusicVolume(int volume) { + LOGGER.log(Level.INFO, String.format("Midi volume set to %d", volume)); + receiver.setGlobalVolume(volume / 127f); + + } + + @Override + public void PauseSong(int handle) { + if (songloaded) { + sequencer.stop(); + } + } + + @Override + public void ResumeSong(int handle) { + if (songloaded) { + LOGGER.log(Level.FINE, "Resuming song"); + sequencer.start(); + } + + } + + @Override + public int RegisterSong(byte[] data) { + try { + Sequence sequence; + ByteArrayInputStream bis; + try { + // If data is a midi file, load it directly + bis = new ByteArrayInputStream(data); + sequence = MidiSystem.getSequence(bis); + } catch (InvalidMidiDataException ex) { + // Well, it wasn't. Dude. + bis = new ByteArrayInputStream(data); + sequence = MusReader.getSequence(bis); + } + sequencer.stop(); // stops current music if any + sequencer.setSequence(sequence); // Create a sequencer for the sequence + songloaded = true; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "RegisterSong failure", e); + return -1; + } + // In good old C style, we return 0 upon success? + return 0; + } + + @Override + public void PlaySong(int handle, boolean looping) { + if (songloaded) { + for (int midiChan = 0; midiChan < 16; ++midiChan) { + setPitchBendSensitivity(receiver, midiChan, 2); + } + if (looping) { + sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY); + } else { + sequencer.setLoopCount(0); + } + sequencer.start(); // Start playing + } + } + + private void setPitchBendSensitivity(Receiver receiver, int midiChan, int semitones) { + sendRegParamChange(receiver, midiChan, 0, 0, 2); + } + + private void sendRegParamChange(Receiver receiver, int midiChan, int paramMsb, int paramLsb, int valMsb) { + sendControlChange(receiver, midiChan, 101, paramMsb); + sendControlChange(receiver, midiChan, 100, paramLsb); + sendControlChange(receiver, midiChan, 6, valMsb); + } + + @Override + public void StopSong(int handle) { + sequencer.stop(); + + } + + @Override + public void UnRegisterSong(int handle) { + // In theory, we should ask the sequencer to "forget" about the song. + // However since we can register another without unregistering the first, + // this is practically a dummy. + + songloaded = false; + + } + +} \ No newline at end of file diff --git a/doom/src/s/DavidSFXModule.java b/doom/src/s/DavidSFXModule.java new file mode 100644 index 0000000..e5e3634 --- /dev/null +++ b/doom/src/s/DavidSFXModule.java @@ -0,0 +1,593 @@ +package s; + +import data.sounds; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import java.util.ArrayList; +import java.util.concurrent.Semaphore; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.FloatControl; +import javax.sound.sampled.FloatControl.Type; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; +import mochadoom.Loggers; + +/** David Martel's sound driver for Mocha Doom. Excellent work! + * + * However, it's based on Java Audiolines, and as such has a number + * of drawbacks: + * + * a) Sounds are forcibly blown to be stereo, 16-bit otherwise it's + * impossible to get panning controls. + * b) Volume, master gain, panning, pitch etc. controls are NOT guaranteed + * to be granted across different OSes , and your mileage may vary. It's + * fairly OK under Windows and OS X, but Linux is very clunky. The only + * control that is -somewhat- guaranteed is the volume one. + * c) Spawns as many threads as channels. Even if semaphore waiting it used, + * that can be taxing for slower systems. + + * + * @author David + * @author Velktron + * + */ +public class DavidSFXModule extends AbstractSoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(DavidSFXModule.class.getName()); + + ArrayList cachedSounds = new ArrayList<>(); + + public final float[] linear2db; + + private SoundWorker[] channels; + private Thread[] soundThread; + + public DavidSFXModule(DoomMain DM, int numChannels) { + super(DM, numChannels); + linear2db = computeLinear2DB(); + + } + + private float[] computeLinear2DB() { + + // Maximum volume is 0 db, minimum is ... -96 db. + // We rig this so that half-scale actually gives quarter power, + // and so is -6 dB. + float[] tmp = new float[VOLUME_STEPS]; + + for (int i = 0; i < VOLUME_STEPS; i++) { + float linear = (float) (10 * Math.log10((float) i / (float) VOLUME_STEPS)); + // Hack. The minimum allowed value as of now is -80 db. + if (linear < -36.0) { + linear = -36.0f; + } + tmp[i] = linear; + + } + + return tmp; + } + + @Override + public boolean InitSound() { + // Secure and configure sound device first. + LOGGER.log(Level.INFO, "I_InitSound"); + + // Initialize external data (all sounds) at start, keep static. + initSound16(); + + // Cache sounds internally so they can be "fed" to AudioLine threads later. + // These can be more than the usual built-in sounds. + for (int i = 0; i < sounds.S_sfx.length; i++) { + DoomSound tmp = new DoomSound(sounds.S_sfx[i], DoomSound.DEFAULT_SAMPLES_FORMAT); + cachedSounds.add(tmp); + } + + LOGGER.log(Level.INFO, "pre-cached all sound data"); + // Finished initialization. + LOGGER.log(Level.INFO, "I_InitSound: sound module ready"); + + return true; + + } + + @Override + public void UpdateSound() { + // In theory, we should update volume + panning for each active channel. + // Ouch. Ouch Ouch. + + } + + @Override + public void SubmitSound() { + // Sound should be submitted to the sound threads, which they pretty much + // do themselves. + + } + + @Override + public void ShutdownSound() { + // Wait till all pending sounds are finished. + boolean done = false; + int i; + + while (!done) { + for (i = 0; i < numChannels && !(channels[i].isPlaying()); i++); + if (i == numChannels) { + done = true; + } + } + + for (i = 0; i < numChannels; i++) { + channels[i].terminate = true; + channels[i].wait.release(); + try { + this.soundThread[i].join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + LOGGER.log(Level.SEVERE, "ShutDownSound failure", e); + } + } + // Done. + + } + + @Override + public void SetChannels(int numChannels) { + + channels = new SoundWorker[numChannels]; + soundThread = new Thread[numChannels]; + + // This is actually called from IDoomSound. + for (int i = 0; i < numChannels; i++) { + channels[i] = new SoundWorker(i); + soundThread[i] = new Thread(channels[i]); + soundThread[i].start(); + } + + } + + /** This one will only create datalines for common clip/audioline samples + * directly. + * + * @param c + * @param sfxid + */ + private final void createDataLineForChannel(int c, int sfxid) { + + // None? Make a new one. + if (channels[c].auline == null) { + try { + DoomSound tmp = cachedSounds.get(sfxid); + // Sorry, Charlie. Gotta make a new one. + DataLine.Info info = new DataLine.Info(SourceDataLine.class, DoomSound.DEFAULT_SAMPLES_FORMAT); + channels[c].auline = (SourceDataLine) AudioSystem.getLine(info); + channels[c].auline.open(tmp.format); + } catch (LineUnavailableException e) { + LOGGER.log(Level.INFO, "create data line for channel failure", e); + } + boolean errors = false; + // Add individual volume control. + if (channels[c].auline.isControlSupported(Type.MASTER_GAIN)) { + channels[c].vc = (FloatControl) channels[c].auline + .getControl(Type.MASTER_GAIN); + } else { + LOGGER.log(Level.FINE, "MASTER_GAIN"); + errors = true; + if (channels[c].auline.isControlSupported(Type.VOLUME)) { + channels[c].vc = (FloatControl) channels[c].auline + .getControl(Type.VOLUME); + } else { + LOGGER.log(Level.FINE, "VOLUME"); + } + } + + // Add individual pitch control. + if (channels[c].auline.isControlSupported(Type.SAMPLE_RATE)) { + channels[c].pc = (FloatControl) channels[c].auline + .getControl(Type.SAMPLE_RATE); + } else { + errors = true; + LOGGER.log(Level.FINE, "SAMPLE_RATE"); + } + + // Add individual pan control + if (channels[c].auline.isControlSupported(Type.BALANCE)) { + channels[c].bc = (FloatControl) channels[c].auline + .getControl(FloatControl.Type.BALANCE); + } else { + LOGGER.log(Level.FINE, "BALANCE"); + errors = true; + if (channels[c].auline.isControlSupported(Type.PAN)) { + channels[c].bc = (FloatControl) channels[c].auline + .getControl(FloatControl.Type.PAN); + } else { + LOGGER.log(Level.FINE, "PANNING"); + } + } + + if (errors) { + LOGGER.log(Level.SEVERE, String.format("for channel %d NOT supported!", c)); + } + + channels[c].auline.start(); + } + } + + /* UNUSED version, designed to work on any type of sample (in theory). + Requires a DoomSound container for separate format information. + + private final void createDataLineForChannel(int c, DoomSound sound){ + if (channels[c].auline == null) { + AudioFormat format = sound.ais.getFormat(); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + try { + channels[c].auline = (SourceDataLine) AudioSystem.getLine(info); + channels[c].auline.open(format); + } catch (LineUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // Add individual volume control. + if (channels[c].auline.isControlSupported(Type.MASTER_GAIN)) + channels[c].vc=(FloatControl) channels[c].auline + .getControl(Type.MASTER_GAIN); + else { + System.err.printf("MASTER_GAIN for channel %d NOT supported!\n",c); + if (channels[c].auline.isControlSupported(Type.VOLUME)) + channels[c].vc=(FloatControl) channels[c].auline + .getControl(Type.VOLUME); + else + System.err.printf("VOLUME for channel %d NOT supported!\n",c); + } + + + // Add individual pitch control. + if (channels[c].auline.isControlSupported(Type.SAMPLE_RATE)){ + channels[c].pc=(FloatControl) channels[c].auline + .getControl(Type.SAMPLE_RATE); + } else { + System.err.printf("SAMPLE_RATE for channel %d NOT supported!\n",c); + } + + // Add individual pan control (TODO: proper positioning). + if (channels[c].auline.isControlSupported(Type.BALANCE)){ + channels[c].bc=(FloatControl) channels[c].auline + .getControl(FloatControl.Type.BALANCE); + } else { + System.err.printf("BALANCE for channel %d NOT supported!\n",c); + if (channels[c].auline.isControlSupported(Type.PAN)){ + channels[c].bc=(FloatControl) channels[c].auline + .getControl(FloatControl.Type.PAN); + } else { + System.err.printf("PAN for channel %d NOT supported!\n",c); + } + } + + channels[c].auline.start(); + } + } + */ + @Override + protected int addsfx(int sfxid, int volume, int pitch, int seperation) { + int i; + int rc = -1; + + int oldest = DM.gametic; + int oldestnum = 0; + int slot; + + int rightvol; + int leftvol; + + // Chainsaw troubles. + // Play these sound effects only one at a time. + if (sfxid == sfxenum_t.sfx_sawup.ordinal() + || sfxid == sfxenum_t.sfx_sawidl.ordinal() + || sfxid == sfxenum_t.sfx_sawful.ordinal() + || sfxid == sfxenum_t.sfx_sawhit.ordinal() + || sfxid == sfxenum_t.sfx_stnmov.ordinal() + || sfxid == sfxenum_t.sfx_pistol.ordinal()) { + // Loop all channels, check. + for (i = 0; i < numChannels; i++) { + // Active, and using the same SFX? + if ((channels[i].isPlaying()) + && (channelids[i] == sfxid)) { + // Reset. + channels[i].stopSound(); + // We are sure that iff, + // there will only be one. + break; + } + } + } + + // Loop all channels to find oldest SFX. + for (i = 0; (i < numChannels) && (channels[i] != null); i++) { + if (channelstart[i] < oldest) { + oldestnum = i; + oldest = channelstart[i]; + } + } + + // Tales from the cryptic. + // If we found a channel, fine. + // If not, we simply overwrite the first one, 0. + // Probably only happens at startup. + if (i == numChannels) { + slot = oldestnum; + } else { + slot = i; + } + + // Okay, in the less recent channel, + // we will handle the new SFX. + // Set pointer to raw data. + // Create a dataline for the "lucky" channel, + // or reuse an existing one if it exists. + createDataLineForChannel(slot, sfxid); + + // Reset current handle number, limited to 0..100. + if (handlenums == 0) // was !handlenums, so it's actually 1...100? + { + handlenums = MAXHANDLES; + } + + // Assign current handle number. + // Preserved so sounds could be stopped (unused). + channelhandles[slot] = rc = handlenums--; + channelstart[slot] = DM.gametic; + + // Separation, that is, orientation/stereo. + // range is: 1 - 256 + seperation += 1; + + // Per left/right channel. + // x^2 seperation, + // adjust volume properly. + leftvol + = volume - ((volume * seperation * seperation) >> 16); ///(256*256); + seperation = seperation - 257; + rightvol + = volume - ((volume * seperation * seperation) >> 16); + + // Sanity check, clamp volume. + if (rightvol < 0 || rightvol > 127) { + DM.doomSystem.Error("rightvol out of bounds"); + } + + if (leftvol < 0 || leftvol > 127) { + DM.doomSystem.Error("leftvol out of bounds"); + } + + // Preserve sound SFX id, + // e.g. for avoiding duplicates of chainsaw. + channelids[slot] = sfxid; + + channels[slot].setVolume(volume); + channels[slot].setPanning(seperation + 256); + channels[slot].addSound(cachedSounds.get(sfxid).data, handlenums); + channels[slot].setPitch(pitch); + + if (D) { + LOGGER.log(Level.FINE, channelStatus()); + } + if (D) { + LOGGER.log(Level.FINE, String.format("Playing %d vol %d on channel %d", rc, volume, slot)); + } + // You tell me. + return rc; + } + + @Override + public void StopSound(int handle) { + // Which channel has it? + int hnd = getChannelFromHandle(handle); + if (hnd >= 0) { + channels[hnd].stopSound(); + } + } + + @Override + public boolean SoundIsPlaying(int handle) { + + return getChannelFromHandle(handle) != BUSY_HANDLE; + } + + @Override + public void UpdateSoundParams(int handle, int vol, int sep, int pitch) { + + // This should be called on sounds that are ALREADY playing. We really need + // to retrieve channels from their handles. + //System.err.printf("Updating sound with handle %d vol %d sep %d pitch %d\n",handle,vol,sep,pitch); + int i = getChannelFromHandle(handle); + // None has it? + if (i != BUSY_HANDLE) { + //System.err.printf("Updating sound with handle %d in channel %d\n",handle,i); + channels[i].setVolume(vol); + channels[i].setPitch(pitch); + channels[i].setPanning(sep); + } + + } + + /** Internal use. + * + * @param handle + * @return the channel that has the handle, or -2 if none has it. + */ + private int getChannelFromHandle(int handle) { + // Which channel has it? + for (int i = 0; i < numChannels; i++) { + if (channelhandles[i] == handle) { + return i; + } + } + + return BUSY_HANDLE; + } + + /** A Thread for playing digital sound effects. + * + * Obviously you need as many as channels? + * + * In order not to end up in a hell of effects, + * certain types of sounds must be limited to 1 per object. + * + */ + private class SoundWorker implements Runnable { + + public Semaphore wait; // Holds the worker still until there's a new sound + FloatControl vc; // linear volume control + FloatControl bc; // balance/panning control + FloatControl pc; // pitch control + byte[] currentSoundSync; + byte[] currentSound; + + public SoundWorker(int id) { + this.id = id; + this.handle = IDLE_HANDLE; + wait = new Semaphore(1); + } + + int id; + /** Used to find out whether the same object is continuously making + * sounds. E.g. the player, ceilings etc. In that case, they must be + * interrupted. + */ + int handle; + public boolean terminate; + SourceDataLine auline; + + /** This is how you tell the thread to play a sound, + * I suppose. */ + public void addSound(byte[] ds, int handle) { + + if (D) { + LOGGER.log(Level.INFO, String.format("Added handle %d to channel %d", handle, id)); + } + this.handle = handle; + this.currentSound = ds; + this.auline.stop(); + this.auline.start(); + this.wait.release(); + + } + + /** Accepts volume in "Doom" format (0-127). + * + * @param volume + */ + public void setVolume(int volume) { + if (vc != null) { + if (vc.getType() == FloatControl.Type.MASTER_GAIN) { + float vol = linear2db[volume]; + vc.setValue(vol); + } else if (vc.getType() == FloatControl.Type.VOLUME) { + float vol = vc.getMinimum() + (vc.getMaximum() - vc.getMinimum()) * (float) volume / 127f; + vc.setValue(vol); + } + } + } + + public void setPanning(int sep) { + // Q: how does Doom's sep map to stereo panning? + // A: Apparently it's 0-255 L-R. + if (bc != null) { + float pan = bc.getMinimum() + (bc.getMaximum() - bc.getMinimum()) * (float) (sep) / ISoundDriver.PANNING_STEPS; + //System.err.printf("Panning %d %f %f %f\n",sep,bc.getMinimum(),bc.getMaximum(),pan); + bc.setValue(pan); + } + } + + /** Expects a steptable value between 16K and 256K, with + * 64K being the middle. + * + * @param pitch + */ + public void setPitch(int pitch) { + if (pc != null) { + float pan = (float) (pc.getValue() * ((float) pitch / 65536.0)); + pc.setValue(pan); + } + } + + public void run() { + LOGGER.log(Level.INFO, String.format("Sound thread %d started", id)); + while (!terminate) { + currentSoundSync = currentSound; + if (currentSoundSync != null) { + + try { + auline.write(currentSoundSync, 0, currentSoundSync.length); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "SoundWorker run failure", e); + return; + } finally { + // The previous steps are actually VERY fast. + // However this one waits until the data has been + // consumed, Interruptions/signals won't reach here, + // so it's pointless trying to interrupt the actual filling. + //long a=System.nanoTime(); + auline.drain(); + //long b=System.nanoTime(); + //System.out.printf("Channel %d completed in %f.\n",id,(float)(b-a)/1000000000f); + } + // Report that this channel is free. + currentSound = null; + // Remove its handle. + + //System.out.printf("Channel %d with handle %d done. Marking as free\n",id,handle); + if (handle > 0) { + channelhandles[this.id] = IDLE_HANDLE; + } + this.handle = IDLE_HANDLE; + } + + // If we don't sleep at least a bit here, busy waiting becomes + // way too taxing. Waiting on a semaphore (triggered by adding a new sound) + // seems like a better method. + try { + wait.acquire(); + } catch (InterruptedException e) { + } + } + } + + public void stopSound() { + auline.stop(); + auline.flush(); + //System.out.printf("Channel %d with handle %d interrupted. Marking as free\n",id,handle); + channelhandles[this.id] = IDLE_HANDLE; + this.handle = IDLE_HANDLE; + currentSound = null; + auline.start(); + } + + public boolean isPlaying() { + //System.out.printf("Channel %d with handle %d queried\n",id,handle); + return (this.handle != IDLE_HANDLE || this.currentSound != null); + } + + } + + StringBuilder sb = new StringBuilder(); + + public String channelStatus() { + sb.setLength(0); + for (int i = 0; i < numChannels; i++) { + if (channels[i].isPlaying()) { + sb.append(i); + } else { + sb.append('-'); + } + } + + return sb.toString(); + + } + +} \ No newline at end of file diff --git a/doom/src/s/DoomIO.java b/doom/src/s/DoomIO.java new file mode 100644 index 0000000..6dc5578 --- /dev/null +++ b/doom/src/s/DoomIO.java @@ -0,0 +1,307 @@ +package s; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class DoomIO { + + private static final Logger LOGGER = Loggers.getLogger(DoomIO.class.getName()); + + InputStream is; + OutputStream os; + + public DoomIO(InputStream is, OutputStream os) { + this.is = is; + this.os = os; + } + + public static int toUnsigned(byte signed) { + int unsigned = (signed & 0xff); + unsigned = (signed >= 0 ? signed : 256 + signed); + unsigned = (256 + signed) % 256; + return unsigned; + } + + public static int fread(byte[] bytes, int size, int count, InputStream file) throws IOException { + int retour = 0; + do { + if (file.read(bytes, retour * size, size) < size) { + return retour; + } + retour++; + } while (--count > 0); + return retour; + } + + public static int freadint(InputStream file) throws IOException { + /*byte[] bytes = new byte[2]; + if (fread(bytes, 2, 1, file) < 1) + return -1; + int retour = toUnsigned(bytes[1])*256 + toUnsigned(bytes[0]); + return retour;*/ + return freadint(file, 2); + } + + public static int freadint(InputStream file, int nbBytes) throws IOException { + byte[] bytes = new byte[nbBytes]; + if (fread(bytes, nbBytes, 1, file) < 1) { + return -1; + } + long retour = 0; + for (int i = 0; i < nbBytes; i++) { + retour += toUnsigned(bytes[i]) * (long) Math.pow(256, i); + } + //toUnsigned(bytes[1])*256 + toUnsigned(bytes[0]); + + if (retour > (long) Math.pow(256, nbBytes) / 2) { + retour -= (long) Math.pow(256, nbBytes); + } + + return (int) retour; + } + + public static int fwrite2(byte[] ptr, int offset, int size, Object file) throws IOException { + fwrite(ptr, offset, size, 1, file); + return 0; + } + + public static int fwrite2(byte[] ptr, int size, Object file) throws IOException { + return fwrite2(ptr, 0, size, file); + } + + public static int fwrite2(byte[] ptr, Object file) throws IOException { + return fwrite2(ptr, 0, ptr.length, file); + } + + public static void fwrite(String bytes, int size, int count, Object file) throws IOException { + fwrite(toByteArray(bytes), size, count, file); + } + + public static void fwrite(byte[] bytes, int size, int count, Object file) throws IOException { + fwrite(bytes, 0, size, count, file); + } + + public static void fwrite(byte[] bytes, int offset, int size, int count, Object file) throws IOException { + if (file instanceof OutputStream) { + /*byte[] b = bytes; + if (bytes.length < size) { + b = new byte[size]; + copyBytes(from, to, offset) + }*/ + + ((OutputStream) file).write(bytes, offset, Math.min(bytes.length, size)); + for (int i = bytes.length; i < size; i++) { + ((OutputStream) file).write((byte) 0); // padding effect if size is bigger than byte array + } + } + if (file instanceof Writer) { + char[] ch = new char[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + ch[i] = (char) toUnsigned(bytes[i]); + } + + ((Writer) file).write(ch, offset, size); + } + } + + public static byte[] toByteArray(String str) { + byte[] retour = new byte[str.length()]; + for (int i = 0; i < str.length(); i++) { + retour[i] = (byte) (str.charAt(i) & 0xFF); + } + return retour; + } + + public static byte[] toByteArray(int str) { + return toByteArray(str, 2); + } + + public static enum Endian { + BIG, LITTLE + } + public static Endian writeEndian = Endian.LITTLE; + + static int byteIdx(int i, int nbBytes) { + return (writeEndian == Endian.BIG ? i : nbBytes - 1 - i); + } + + public static void copyBytes(byte[] from, byte[] to, int offset) { + for (byte b : from) { + to[offset++] = b; + } + } + + public static byte[] toByteArray(Long str, int nbBytes) { + return toByteArray(str.intValue(), nbBytes); + } + + public static byte[] toByteArray(Short str, int nbBytes) { + return toByteArray(str.intValue(), nbBytes); + } + + public static byte[] toByteArray(int[] str, int nbBytes) { + byte[] bytes = new byte[str.length * nbBytes]; + for (int i = 0; i < str.length; i++) { + copyBytes(toByteArray(str[i], nbBytes), bytes, i * nbBytes); + } + return bytes; + } + + /* + public static byte[] toByteArray(boolean[] bools, int nbBytes) { + byte[] bytes = new byte[bools.length*nbBytes]; + for (int i = 0; i < bools.length; i++) { + copyBytes(toByteArray(bools[i], nbBytes), bytes, i*nbBytes); + } + return bytes; + } */ + + /* + public static byte[] toByteArray(Boolean bool, int nbBytes) { + int val = (bool?1:0); + return toByteArray(val, nbBytes); + }*/ + public static byte[] toByteArray(Integer str, int nbBytes) { + Long val = str.longValue(); + if (val < 0) { + val = (long) Math.pow(256, nbBytes) + val; + } + + byte[] bytes = new byte[nbBytes]; + long tmp = val; + for (int i = 0; i < nbBytes - 1; i++) { + bytes[byteIdx(i, nbBytes)] = (byte) (tmp % 256); + tmp = tmp / 256; + } + + bytes[byteIdx(nbBytes - 1, nbBytes)] = (byte) (tmp); + return bytes; + } + + private static Field getField(Class clazz, String fieldName) throws NoSuchFieldException { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + throw e; + } else { + return getField(superClass, fieldName); + } + } + } + + public static void linkBA(Object obj, Object fieldName, Object stream, int size) { + if (stream instanceof OutputStream) { + try { + Object val = null; + if (fieldName instanceof String) { + val = getField(obj.getClass(), (String) fieldName).get(obj); + if (val instanceof Enum) { + val = ((Enum) val).ordinal(); + } + } + if (fieldName instanceof Integer) { + val = fieldName; + } + + Method method = DoomIO.class.getMethod("toByteArray", val.getClass(), int.class); + byte[] bytes = (byte[]) method.invoke(null, val, size); + ((OutputStream) stream).write(bytes); + + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "linkBA output failure", e); + } + } + + if (stream instanceof InputStream) { + try { + if (fieldName instanceof String) { + Field field = obj.getClass().getField((String) fieldName); + assigner(obj, field, (InputStream) stream, size); + } + if (fieldName instanceof Integer) { + ((InputStream) stream).read(new byte[size]); + } + + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "linkBA input failure", e); + } + } + +// public static int freadint(InputStream file, int nbBytes) throws IOException { + } + + public static void assigner(Object obj, Field field, InputStream is, int size) throws IOException, IllegalArgumentException, IllegalAccessException { + + Class c = field.getType(); + if (c.isArray()) { + Object a = field.get(obj); + int len = Array.getLength(a); + for (int i = 0; i < len; i++) { + int val = DoomIO.freadint((InputStream) is, size); + Object o = Array.get(a, i); + Array.set(a, i, assignValue(val, o, o.getClass())); + } + return; + } + + int val = DoomIO.freadint((InputStream) is, size); + Object v = assignValue(val, field.get(obj), field.getType()); + field.set(obj, v); + + /*Object[] enums = c.getEnumConstants(); + if (enums != null) { + int val = DoomIO.freadint((InputStream)is, size); + field.set(obj, enums[val]); + } + else { + int val = DoomIO.freadint((InputStream)is, size); + field.set(obj, val); + }*/ + } + + public static Object assignValue(int val, Object objToReplace, Class classe) { + if (classe.isAssignableFrom(Boolean.class) || classe.isAssignableFrom(boolean.class)) { + return (val != 0); + } + + Object[] enums = classe.getEnumConstants(); + if (enums != null) { + //int val = DoomIO.freadint((InputStream)is, size); + return enums[val]; + //field.set(obj, enums[val]); + } else { + //int val = DoomIO.freadint((InputStream)is, size); + //field.set(obj, val); + } + + return val; + } + + public static String baToString(byte[] bytes) { + String str = ""; + for (int i = 0; i < bytes.length && bytes[i] != 0; i++) { + str += (char) bytes[i]; + } + return str; + } + + public static int indexOfArray(Object[] a, Object o) { + for (int i = 0; i < a.length/* Array.getLength(a)*/; i++) { + if (/*Array.get(a, i)*/a[i] == o) { + return i; + } + } + return -1; + } + +} \ No newline at end of file diff --git a/doom/src/s/DoomSound.java b/doom/src/s/DoomSound.java new file mode 100644 index 0000000..1d5ed48 --- /dev/null +++ b/doom/src/s/DoomSound.java @@ -0,0 +1,42 @@ +package s; + +import data.sfxinfo_t; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFormat.Encoding; + +/** A class representing a sample in memory + * Convenient for wrapping/mirroring it regardless of what it represents. + * */ +class DoomSound extends sfxinfo_t { + + /** This audio format is the one used by internal samples (16 bit, 11KHz, Stereo) + * for Clips and AudioLines. Sure, it's not general enough... who cares though? + */ + public final static AudioFormat DEFAULT_SAMPLES_FORMAT = new AudioFormat(Encoding.PCM_SIGNED, ISoundDriver.SAMPLERATE, 16, 2, 4, ISoundDriver.SAMPLERATE, true); + + public final static AudioFormat DEFAULT_DOOM_FORMAT = new AudioFormat(Encoding.PCM_UNSIGNED, ISoundDriver.SAMPLERATE, 8, 1, 1, ISoundDriver.SAMPLERATE, true); + + public AudioFormat format; + + public DoomSound(AudioFormat format) { + this.format = format; + } + + public DoomSound() { + this.format = DEFAULT_DOOM_FORMAT; + } + + public DoomSound(sfxinfo_t sfx, AudioFormat format) { + this(format); + this.data = sfx.data; + this.pitch = sfx.pitch; + this.link = sfx.link; + this.lumpnum = sfx.lumpnum; + this.name = sfx.name; + this.priority = sfx.priority; + this.singularity = sfx.singularity; + this.usefulness = sfx.usefulness; + this.volume = sfx.volume; + } + +} \ No newline at end of file diff --git a/doom/src/s/DoomToWave.java b/doom/src/s/DoomToWave.java new file mode 100644 index 0000000..087bfd6 --- /dev/null +++ b/doom/src/s/DoomToWave.java @@ -0,0 +1,265 @@ +package s; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import utils.C2JUtils; + +public class DoomToWave { + + private static final Logger LOGGER = Loggers.getLogger(DoomToWave.class.getName()); + + static int MEMORYCACHE = 0x8000; + + static class RIFFHEAD { + + byte[] riff = new byte[4]; + int length; + byte[] wave = new byte[4]; + + public void pack(ByteBuffer b) { + b.put(riff); + b.putInt(length); + b.put(wave); + } + + public int size() { + return 12; + } + + } + + RIFFHEAD headr = new RIFFHEAD(); + + static class CHUNK { + + byte[] name = new byte[4]; + int size; + + public void pack(ByteBuffer b) { + b.put(name); + b.putInt(size); + } + + public int size() { + return 8; + } + } + + CHUNK headc = new CHUNK(); + + static class WAVEFMT { + + byte[] fmt = new byte[4]; + /* "fmt " */ + int fmtsize; + /*0x10*/ + int tag; + /*format tag. 1=PCM*/ + int channel; + /*1*/ + int smplrate; + int bytescnd; + /*average bytes per second*/ + int align; + /*block alignment, in bytes*/ + int nbits; + + /*specific to PCM format*/ + public void pack(ByteBuffer b) { + b.put(fmt); + b.putInt(fmtsize); + b.putChar((char) tag); + b.putChar((char) channel); + b.putInt(smplrate); + b.putInt(bytescnd); + b.putChar((char) align); + b.putChar((char) nbits); + } + + public int size() { + return 24; + } + } + + WAVEFMT headf = new WAVEFMT(); + int SIZEOF_WAVEFMT = 24; + + static class WAVEDATA /*data*/ { + + byte[] data = new byte[4]; + /* "data" */ + int datasize; + + public void pack(ByteBuffer b) { + b.put(data); + b.putInt(datasize); + } + } + WAVEDATA headw = new WAVEDATA(); + int SIZEOF_WAVEDATA = 8; + + public void SNDsaveSound(InputStream is, OutputStream os) throws IOException { + int type = DoomIO.freadint(is, 2);// peek_i16_le (buffer); + int speed = DoomIO.freadint(is, 2);//peek_u16_le (buffer + 2); + int datasize = DoomIO.freadint(is, 4);//peek_i32_le (buffer + 4); + if (type != 3) { + LOGGER.log(Level.FINE, String.format("Sound: weird type %d. Extracting anyway.", type)); + } + + int headsize = 2 + 2 + 4; + /*- headsize*/ int phys_size = is.available(); + if (datasize > phys_size) { + LOGGER.log(Level.INFO, "Sound: declared sample size greater than lump size"/*, + lump_name (name), (unsigned long) datasize, (unsigned long) phys_size*/); + LOGGER.log(Level.INFO, "Sound: truncating to lump size."/*, lump_name (name)*/); + datasize = phys_size; + } /* Sometimes the size of sound lump is greater + than the declared sound size. */ else if (datasize < phys_size) { + if (/*fullSND == TRUE*/true) /* Save entire lump */ { + datasize = phys_size; + } else { + /*Warning ( + "Sound %s: lump size %lu greater than declared sample size %lu ;", + lump_name (name), (unsigned long) datasize, (unsigned long) phys_size); + Warning ("Sound %s: truncating to declared sample size.", + lump_name (name));*/ + } + } + + DoomIO.writeEndian = DoomIO.Endian.BIG; + + SNDsaveWave(is, os, speed, datasize); + } + + public byte[] DMX2Wave(byte[] DMXSound) throws IOException { + ByteBuffer is = ByteBuffer.wrap(DMXSound); + is.order(ByteOrder.LITTLE_ENDIAN); + int type = 0x0000FFFF & is.getShort();// peek_i16_le (buffer); + int speed = 0x0000FFFF & is.getShort();//peek_u16_le (buffer + 2); + int datasize = is.getInt();//peek_i32_le (buffer + 4); + if (type != 3) { + LOGGER.log(Level.INFO, String.format("Sound: weird type %d. Extracting anyway.", type)); + } + + int headsize = 2 + 2 + 4; + /*- headsize*/ int phys_size = is.remaining(); + if (datasize > phys_size) { + LOGGER.log(Level.INFO, "Sound: declared sample size greater than lump size"/*, + lump_name (name), (unsigned long) datasize, (unsigned long) phys_size*/); + LOGGER.log(Level.INFO, "Sound: truncating to lump size."/*, lump_name (name)*/); + datasize = phys_size; + } /* Sometimes the size of sound lump is greater + than the declared sound size. */ else if (datasize < phys_size) { + if (/*fullSND == TRUE*/true) /* Save entire lump */ { + datasize = phys_size; + } else { + /*Warning ( + "Sound %s: lump size %lu greater than declared sample size %lu ;", + lump_name (name), (unsigned long) datasize, (unsigned long) phys_size); + Warning ("Sound %s: truncating to declared sample size.", + lump_name (name));*/ + } + } + + return SNDsaveWave(is, speed, datasize); + } + + protected byte[] SNDsaveWave(ByteBuffer is, int speed, int size) throws IOException { + + // Size with header and data etc. + byte[] output = new byte[headr.size() + headf.size() + SIZEOF_WAVEDATA + 2 * size]; + ByteBuffer os = ByteBuffer.wrap(output); + os.order(ByteOrder.LITTLE_ENDIAN); + os.position(0); + headr.riff = ("RIFF").getBytes(); + int siz = 4 + SIZEOF_WAVEFMT + SIZEOF_WAVEDATA + 2 * size; + headr.length = siz; + headr.wave = C2JUtils.toByteArray("WAVE"); + + headr.pack(os); + + headf.fmt = C2JUtils.toByteArray("fmt "); + headf.fmtsize = SIZEOF_WAVEFMT - 8; + headf.tag = 1; + headf.channel = 2; // Maes: HACK to force stereo lines. + headf.smplrate = speed; + headf.bytescnd = 2 * speed; // Ditto. + headf.align = 1; + headf.nbits = 8; + + headf.pack(os); + + headw.data = C2JUtils.toByteArray("data"); + headw.datasize = 2 * size; + //byte[] wtf=DoomIO.toByteArray(headw.datasize, 4); + + headw.pack(os); + + byte tmp; + + for (int i = 0; i < size; i++) { + tmp = is.get(); + os.put(tmp); + os.put(tmp); + } + + return os.array(); + } + + void SNDsaveWave(InputStream is, OutputStream os, int speed, int size) throws IOException { + int wsize, sz = 0; + headr.riff = DoomIO.toByteArray("RIFF"); + int siz = 4 + SIZEOF_WAVEFMT + SIZEOF_WAVEDATA + size; + headr.length = siz; + headr.wave = DoomIO.toByteArray("WAVE"); + + DoomIO.fwrite2(headr.riff, os); + DoomIO.fwrite2(DoomIO.toByteArray(headr.length, 4), os); + DoomIO.fwrite2(headr.wave, os); + + headf.fmt = DoomIO.toByteArray("fmt "); + headf.fmtsize = SIZEOF_WAVEFMT - 8; + headf.tag = 1; + headf.channel = 1; // Maes: HACK to force stereo lines. + headf.smplrate = speed; + headf.bytescnd = speed; + headf.align = 1; + headf.nbits = 8; + + DoomIO.fwrite2(headf.fmt, os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.fmtsize, 4), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.tag, 2), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.channel, 2), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.smplrate, 4), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.bytescnd, 4), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.align, 2), os); + DoomIO.fwrite2(DoomIO.toByteArray(headf.nbits, 2), os); + + headw.data = DoomIO.toByteArray("data"); + headw.datasize = size; + + DoomIO.fwrite2(headw.data, os); + DoomIO.fwrite2(DoomIO.toByteArray(headw.datasize, 4), os); + + ByteArrayOutputStream shit = (ByteArrayOutputStream) os; + + byte[] crap = shit.toByteArray(); + + byte[] bytes = new byte[MEMORYCACHE]; + for (wsize = 0; wsize < size; wsize += sz) { + sz = (size - wsize > MEMORYCACHE) ? MEMORYCACHE : (size - wsize); + is.read(bytes, 0, sz); + os.write(bytes, 0, sz); + //if(fwrite((buffer+(wsize)),(size_t)sz,1,fp)!=1) + // ProgError("%s: write error (%s)", fname (file), strerror (errno)); + } + } + +} \ No newline at end of file diff --git a/doom/src/s/DummyMusic.java b/doom/src/s/DummyMusic.java new file mode 100644 index 0000000..776d124 --- /dev/null +++ b/doom/src/s/DummyMusic.java @@ -0,0 +1,59 @@ +package s; + +public class DummyMusic implements IMusic { + + @Override + public void InitMusic() { + // TODO Auto-generated method stub + + } + + @Override + public void ShutdownMusic() { + // TODO Auto-generated method stub + + } + + @Override + public void SetMusicVolume(int volume) { + // TODO Auto-generated method stub + + } + + @Override + public void PauseSong(int handle) { + // TODO Auto-generated method stub + + } + + @Override + public void ResumeSong(int handle) { + // TODO Auto-generated method stub + + } + + @Override + public int RegisterSong(byte[] data) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void PlaySong(int handle, boolean looping) { + // TODO Auto-generated method stub + + } + + @Override + public void StopSong(int handle) { + // TODO Auto-generated method stub + + } + + @Override + public void UnRegisterSong(int handle) { + // TODO Auto-generated method stub + + } + +} \ No newline at end of file diff --git a/doom/src/s/DummySFX.java b/doom/src/s/DummySFX.java new file mode 100644 index 0000000..7805056 --- /dev/null +++ b/doom/src/s/DummySFX.java @@ -0,0 +1,67 @@ +package s; + +import data.sfxinfo_t; + +public class DummySFX implements ISoundDriver { + + @Override + public boolean InitSound() { + // Dummy is super-reliable ;-) + return true; + } + + @Override + public void UpdateSound() { + // TODO Auto-generated method stub + + } + + @Override + public void SubmitSound() { + // TODO Auto-generated method stub + + } + + @Override + public void ShutdownSound() { + // TODO Auto-generated method stub + + } + + @Override + public int GetSfxLumpNum(sfxinfo_t sfxinfo) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int StartSound(int id, int vol, int sep, int pitch, int priority) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void StopSound(int handle) { + // TODO Auto-generated method stub + + } + + @Override + public boolean SoundIsPlaying(int handle) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void UpdateSoundParams(int handle, int vol, int sep, int pitch) { + // TODO Auto-generated method stub + + } + + @Override + public void SetChannels(int numChannels) { + // TODO Auto-generated method stub + + } + +} \ No newline at end of file diff --git a/doom/src/s/DummySoundDriver.java b/doom/src/s/DummySoundDriver.java new file mode 100644 index 0000000..c55ae79 --- /dev/null +++ b/doom/src/s/DummySoundDriver.java @@ -0,0 +1,112 @@ +package s; + +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import p.mobj_t; + +/** Does nothing. Just allows me to code without + * commenting out ALL sound-related code. Hopefully + * it will be superseded by a real sound driver one day. + * + * @author Velktron + * + */ +public class DummySoundDriver implements IDoomSound { + + @Override + public void Init(int sfxVolume, int musicVolume) { + // TODO Auto-generated method stub + + } + + @Override + public void Start() { + // TODO Auto-generated method stub + + } + + @Override + public void StartSound(ISoundOrigin origin, int sound_id) { + // TODO Auto-generated method stub + + } + + @Override + public void StartSound(ISoundOrigin origin, sfxenum_t sound_id) { + // TODO Auto-generated method stub + + } + + @Override + public void StartSoundAtVolume(ISoundOrigin origin, int sound_id, int volume) { + // TODO Auto-generated method stub + + } + + @Override + public void StopSound(ISoundOrigin origin) { + // TODO Auto-generated method stub + + } + + @Override + public void ChangeMusic(int musicnum, boolean looping) { + // TODO Auto-generated method stub + + } + + @Override + public void StopMusic() { + // TODO Auto-generated method stub + + } + + @Override + public void PauseSound() { + // TODO Auto-generated method stub + + } + + @Override + public void ResumeSound() { + // TODO Auto-generated method stub + + } + + @Override + public void UpdateSounds(mobj_t listener) { + // TODO Auto-generated method stub + + } + + @Override + public void SetMusicVolume(int volume) { + // TODO Auto-generated method stub + + } + + @Override + public void SetSfxVolume(int volume) { + // TODO Auto-generated method stub + + } + + @Override + public void StartMusic(int music_id) { + // TODO Auto-generated method stub + + } + + @Override + public void StartMusic(musicenum_t music_id) { + // TODO Auto-generated method stub + + } + + @Override + public void ChangeMusic(musicenum_t musicnum, boolean looping) { + // TODO Auto-generated method stub + + } + +} \ No newline at end of file diff --git a/doom/src/s/FinnwMusicModule.java b/doom/src/s/FinnwMusicModule.java new file mode 100644 index 0000000..f4cac51 --- /dev/null +++ b/doom/src/s/FinnwMusicModule.java @@ -0,0 +1,854 @@ +package s; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiEvent; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; +import javax.sound.midi.Sequence; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.SysexMessage; +import javax.sound.midi.Track; +import javax.sound.midi.Transmitter; +import mochadoom.Loggers; + +/** A music driver that bypasses Sequences and sends events from a MUS lump + * directly to a MIDI device. + * + * Some songs (e.g. D_E1M8) vary individual channel volumes dynamically. This + * driver multiplies the dynamic volume by the music volume set in the menu. + * This does not work well with a {@link Sequence} because changes to events + * (e.g. channel volume change events) do not take effect while the sequencer + * is running. + * + * Disadvantages of this driver: + *
  • Supports MUS lumps only (no MID, OGG etc.)
  • + *
  • Creates its own thread
  • + *
  • Pausing is not implemented yet
+ * + * @author finnw + * + */ +public class FinnwMusicModule implements IMusic { + + private static final Logger LOGGER = Loggers.getLogger(FinnwMusicModule.class.getName()); + + public FinnwMusicModule() { + this.lock = new ReentrantLock(); + this.channels = new ArrayList<>(15); + this.songs = new ArrayList<>(1); + for (int midiChan = 0; midiChan < 16; ++midiChan) { + if (midiChan != 9) { + channels.add(new Channel(midiChan)); + } + } + channels.add(new Channel(9)); + } + + @Override + public void InitMusic() { + try { + receiver = getReceiver(); + EventGroup genMidiEG = new EventGroup(1f); + genMidiEG.generalMidi(1); + genMidiEG.sendTo(receiver); + sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + } catch (MidiUnavailableException ex) { + LOGGER.log(Level.WARNING, "InitMusic: midi unavailable", ex); + receiver = null; + } + exec = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl()); + } + + /** Not yet implemented */ + @Override + public void PauseSong(int handle) { + } + + @Override + public void PlaySong(int handle, boolean looping) { + lock.lock(); + try { + if (currentTransmitter != null) { + currentTransmitter.stop(); + } + currentTransmitter = null; + if (0 <= handle && handle < songs.size()) { + prepare(receiver); + Song song = songs.get(handle); + currentTransmitter + = new ScheduledTransmitter(song.getScoreBuffer(), looping); + currentTransmitter.setReceiver(receiver); + } + } finally { + lock.unlock(); + } + } + + @Override + public int RegisterSong(byte[] data) { + return RegisterSong(ByteBuffer.wrap(data)); + } + + public int RegisterSong(ByteBuffer data) { + Song song = new Song(data); + lock.lock(); + try { + int result = songs.indexOf(null); + if (result >= 0) { + songs.set(result, song); + } else { + result = songs.size(); + songs.add(song); + } + return result; + } finally { + lock.unlock(); + } + } + + @Override + public void ResumeSong(int handle) { + } + + @Override + public void SetMusicVolume(int volume) { + float fVol = volume * (1 / 127f); + fVol = Math.max(0f, Math.min(fVol, 1f)); + lock.lock(); + try { + this.volume = fVol; + if (currentTransmitter != null) { + currentTransmitter.volumeChanged(); + } + } finally { + lock.unlock(); + } + } + + @Override + public void ShutdownMusic() { + exec.shutdown(); + } + + @Override + public void StopSong(int handle) { + lock.lock(); + try { + if (currentTransmitter != null) { + currentTransmitter.stop(); + currentTransmitter = null; + } + } finally { + lock.unlock(); + } + } + + @Override + public void UnRegisterSong(int handle) { + lock.lock(); + try { + if (0 <= handle && handle < songs.size()) { + songs.set(handle, null); + } + } finally { + lock.unlock(); + } + } + + static boolean hasMusMagic(ByteBuffer magicBuf) { + return magicBuf.get(0) == 'M' + && magicBuf.get(1) == 'U' + && magicBuf.get(2) == 'S' + && magicBuf.get(3) == 0x1a; + } + + EventGroup nextEventGroup(ByteBuffer scoreBuffer, boolean looping) { + EventGroup result = new EventGroup(volume); + boolean last; + do { + if (!scoreBuffer.hasRemaining()) { + if (looping) { + scoreBuffer.flip(); + } else { + return result.emptyToNull(); + } + } + int descriptor = scoreBuffer.get() & 0xff; + last = (descriptor & 0x80) != 0; + int eventType = (descriptor >> 4) & 7; + int chanIndex = descriptor & 15; + Channel channel = channels.get(chanIndex); + switch (eventType) { + case 0: { + int note = scoreBuffer.get() & 0xff; + if ((note & 0x80) != 0) { + throw new IllegalArgumentException("Invalid note byte"); + } + checkChannelExists("note off", channel).noteOff(note, result); + } + break; + case 1: { + int note = scoreBuffer.get() & 0xff; + boolean hasVelocity = (note & 0x80) != 0; + if (hasVelocity) { + int velocity = scoreBuffer.get() & 0xff; + if ((velocity & 0x80) != 0) { + throw new IllegalArgumentException("Invalid velocity byte"); + } + checkChannelExists("note on", channel).noteOn(note & 127, velocity, result); + } else { + checkChannelExists("note on", channel).noteOn(note, result); + } + } + break; + case 2: { + int wheelVal = scoreBuffer.get() & 0xff; + checkChannelExists("pitch bend", channel).pitchBend(wheelVal, result); + } + break; + case 3: { + int sysEvt = scoreBuffer.get() & 0xff; + switch (sysEvt) { + case 10: + checkChannelExists("all sounds off", channel).allSoundsOff(result); + break; + case 11: + checkChannelExists("all notes off", channel).allNotesOff(result); + break; + case 14: + checkChannelExists("reset all controllers", channel).resetAll(result); + break; + default: + String msg = String.format("Invalid system event (%d)", sysEvt); + throw new IllegalArgumentException(msg); + } + } + break; + case 4: + int cNum = scoreBuffer.get() & 0xff; + if ((cNum & 0x80) != 0) { + throw new IllegalArgumentException("Invalid controller number "); + } + int cVal = scoreBuffer.get() & 0xff; + if (cNum == 3 && 133 <= cVal && cVal <= 135) { + // workaround for some TNT.WAD tracks + cVal = 127; + } + if ((cVal & 0x80) != 0) { + String msg = String.format("Invalid controller value (%d; cNum=%d)", cVal, cNum); + throw new IllegalArgumentException(msg); + } + switch (cNum) { + case 0: + checkChannelExists("patch change", channel).patchChange(cVal, result); + break; + case 1: + // Don't forward this to the MIDI device. Some devices + // react badly to banks that are undefined in GM Level 1 + checkChannelExists("bank switch", channel); + break; + case 2: + checkChannelExists("vibrato change", channel).vibratoChange(cVal, result); + break; + case 3: + checkChannelExists("volume", channel).volume(cVal, result); + break; + case 4: + checkChannelExists("pan", channel).pan(cVal, result); + break; + case 5: + checkChannelExists("expression", channel).expression(cVal, result); + break; + case 6: + checkChannelExists("reverb depth", channel).reverbDepth(cVal, result); + break; + case 7: + checkChannelExists("chorus depth", channel).chorusDepth(cVal, result); + break; + default: + throw new AssertionError("Controller number " + cNum + ": not yet implemented"); + } + break; + case 6: + if (looping) { + scoreBuffer.flip(); + } else { + return result.emptyToNull(); + } + break; + default: + String msg = String.format("Unknown event type: last=%5s eventType=%d chanIndex=%d%n", last, eventType, chanIndex); + throw new IllegalArgumentException(msg); + } + } while (!last); + int qTics = readTime(scoreBuffer); + result.addDelay(qTics); + return result; + } + + static class EventGroup { + + EventGroup(float volScale) { + this.messages = new ArrayList<>(); + this.volScale = volScale; + } + + void addDelay(int tics) { + delay += tics; + } + private static final int CHM_ALL_NOTES_OFF = 123; + private static final int CHM_ALL_SOUND_OFF = 120; + private static final int CTRL_CHORUS_DEPTH = 93; + private static final int CTRL_EXPRESSION_POT = 11; + private static final int CTRL_PAN = 10; + private static final int RPM_PITCH_BEND_SENSITIVITY = 0; + private static final int RPL_PITCH_BEND_SENSITIVITY = 0; + private static final int CHM_RESET_ALL = 121; + private static final int CTRL_REVERB_DEPTH = 91; + private static final int CTRL_MODULATION_POT = 1; + private static final int CTRL_VOLUME = 7; + + void allNotesOff(int midiChan) { + addControlChange(midiChan, CHM_ALL_NOTES_OFF, 0); + } + + void allSoundsOff(int midiChan) { + addControlChange(midiChan, CHM_ALL_SOUND_OFF, 0); + } + + long appendTo(Sequence sequence, int trackNum, long pos) { + Track track = sequence.getTracks()[trackNum]; + for (MidiMessage msg : messages) { + track.add(new MidiEvent(msg, pos)); + } + return pos + delay * 3; + } + + long appendTo(Track track, long pos, int scale) { + for (MidiMessage msg : messages) { + track.add(new MidiEvent(msg, pos)); + } + return pos + delay * scale; + } + + void chorusDepth(int midiChan, int depth) { + addControlChange(midiChan, CTRL_CHORUS_DEPTH, depth); + } + + void generalMidi(int mode) { + addSysExMessage(0xf0, (byte) 0x7e, (byte) 0x7f, (byte) 9, (byte) mode, (byte) 0xf7); + } + + EventGroup emptyToNull() { + if (messages.isEmpty()) { + return null; + } else { + return this; + } + } + + void expression(int midiChan, int expr) { + addControlChange(midiChan, CTRL_EXPRESSION_POT, expr); + } + + int getDelay() { + return delay; + } + + void noteOn(int midiChan, int note, int velocity) { + addShortMessage(midiChan, ShortMessage.NOTE_ON, note, velocity); + } + + void noteOff(int midiChan, int note) { + addShortMessage(midiChan, ShortMessage.NOTE_OFF, note, 0); + } + + void pan(int midiChan, int pan) { + addControlChange(midiChan, CTRL_PAN, pan); + } + + void patchChange(int midiChan, int patchId) { + addShortMessage(midiChan, ShortMessage.PROGRAM_CHANGE, patchId, 0); + } + + void pitchBend(int midiChan, int wheelVal) { + int pb14 = wheelVal * 64; + addShortMessage(midiChan, ShortMessage.PITCH_BEND, pb14 % 128, pb14 / 128); + } + + void pitchBendSensitivity(int midiChan, int semitones) { + addRegParamChange(midiChan, RPM_PITCH_BEND_SENSITIVITY, RPL_PITCH_BEND_SENSITIVITY, semitones); + } + + void resetAllControllern(int midiChan) { + addControlChange(midiChan, CHM_RESET_ALL, 0); + } + + void reverbDepth(int midiChan, int depth) { + addControlChange(midiChan, CTRL_REVERB_DEPTH, depth); + } + + void sendTo(Receiver receiver) { + for (MidiMessage msg : messages) { + receiver.send(msg, -1); + } + } + + void vibratoChange(int midiChan, int depth) { + addControlChange(midiChan, CTRL_MODULATION_POT, depth); + } + + void volume(int midiChan, int vol) { + vol = (int) Math.round(vol * volScale); + addControlChange(midiChan, CTRL_VOLUME, vol); + } + + private void addControlChange(int midiChan, int ctrlId, int ctrlVal) { + addShortMessage(midiChan, ShortMessage.CONTROL_CHANGE, ctrlId, ctrlVal); + } + + private void addRegParamChange(int midiChan, int paramMsb, int paramLsb, int valMsb) { + addControlChange(midiChan, 101, paramMsb); + addControlChange(midiChan, 100, paramLsb); + addControlChange(midiChan, 6, valMsb); + } + + private void addShortMessage(int midiChan, int cmd, int data1, int data2) { + try { + ShortMessage msg = new ShortMessage(); + msg.setMessage(cmd, midiChan, data1, data2); + messages.add(msg); + } catch (InvalidMidiDataException ex) { + throw new RuntimeException(ex); + } + } + + private void addSysExMessage(int status, byte... data) { + try { + SysexMessage msg = new SysexMessage(); + msg.setMessage(status, data, data.length); + messages.add(msg); + } catch (InvalidMidiDataException ex) { + throw new RuntimeException(ex); + } + } + private int delay; + private final List messages; + private final float volScale; + } + + /** A collection of kludges to pick a MIDI output device until cvars are implemented */ + static class MidiDeviceComparator implements Comparator { + + @Override + public int compare(MidiDevice.Info o1, MidiDevice.Info o2) { + float score1 = score(o1), score2 = score(o2); + if (score1 < score2) { + return 1; + } else if (score1 > score2) { + return -1; + } else { + return 0; + } + } + + private float score(MidiDevice.Info info) { + String lcName = info.getName().toLowerCase(Locale.ENGLISH); + float result = 0f; + if (lcName.contains("mapper")) { + // "Midi Mapper" is ideal, because the user can select the default output device in the control panel + result += 100; + } else { + if (lcName.contains("synth")) { + // A synthesizer is usually better than a sequencer or USB MIDI port + result += 50; + if (lcName.contains("java")) { + // "Java Sound Synthesizer" has a low sample rate; Prefer another software synth + result -= 20; + } + if (lcName.contains("microsoft")) { + // "Microsoft GS Wavetable Synth" is notoriously unpopular, but sometimes it's the only one + // with a decent sample rate. + result -= 7; + } + } + } + return result; + } + } + + static class ThreadFactoryImpl implements ThreadFactory { + + @Override + public Thread newThread(final Runnable r) { + Thread thread + = new Thread(r, String.format("FinnwMusicModule-%d", NEXT_ID.getAndIncrement())); + thread.setPriority(Thread.MAX_PRIORITY - 1); + return thread; + } + private static final AtomicInteger NEXT_ID + = new AtomicInteger(1); + } + + final Lock lock; + + static final long nanosPerTick = 1000000000 / 140; + + /** Channels in MUS order (0-14 = instruments, 15 = percussion) */ + final List channels; + + ScheduledExecutorService exec; + + float volume; + + private static Receiver getReceiver() throws MidiUnavailableException { + List dInfos + = new ArrayList<>(Arrays.asList(MidiSystem.getMidiDeviceInfo())); + for (Iterator it = dInfos.iterator(); + it.hasNext();) { + MidiDevice.Info dInfo = it.next(); + MidiDevice dev = MidiSystem.getMidiDevice(dInfo); + if (dev.getMaxReceivers() == 0) { + // We cannot use input-only devices + it.remove(); + } + } + if (dInfos.isEmpty()) { + return null; + } + Collections.sort(dInfos, new MidiDeviceComparator()); + MidiDevice.Info dInfo = dInfos.get(0); + MidiDevice dev = MidiSystem.getMidiDevice((MidiDevice.Info) dInfo); + dev.open(); + return dev.getReceiver(); + } + + private void prepare(Receiver receiver) { + EventGroup setupEG = new EventGroup(volume); + for (Channel chan : channels) { + chan.allSoundsOff(setupEG); + chan.resetAll(setupEG); + chan.pitchBendSensitivity(2, setupEG); + chan.volume(127, setupEG); + } + setupEG.sendTo(receiver); + } + + private static void sleepUninterruptibly(int timeout, TimeUnit timeUnit) { + boolean interrupted = false; + long now = System.nanoTime(); + final long expiry = now + timeUnit.toNanos(timeout); + long remaining; + while ((remaining = expiry - now) > 0L) { + try { + TimeUnit.NANOSECONDS.sleep(remaining); + } catch (InterruptedException ex) { + interrupted = true; + } finally { + now = System.nanoTime(); + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + + private static Channel checkChannelExists(String type, Channel channel) + throws IllegalArgumentException { + if (channel == null) { + String msg = String.format("Invalid channel for %s message", type); + throw new IllegalArgumentException(msg); + } else { + return channel; + } + } + + private int readTime(ByteBuffer scoreBuffer) { + int result = 0; + boolean last; + do { + int digit = scoreBuffer.get() & 0xff; + last = (digit & 0x80) == 0; + result <<= 7; + result |= digit & 127; + } while (!last); + return result; + } + + private static class Channel { + + Channel(int midiChan) { + this.midiChan = midiChan; + } + + void allNotesOff(EventGroup eventGroup) { + eventGroup.allNotesOff(midiChan); + } + + void allSoundsOff(EventGroup eventGroup) { + eventGroup.allSoundsOff(midiChan); + } + + void chorusDepth(int depth, EventGroup eventGroup) { + eventGroup.chorusDepth(midiChan, depth); + } + + void expression(int expr, EventGroup eventGroup) { + eventGroup.expression(midiChan, expr); + } + + void noteOff(int note, EventGroup eventGroup) { + eventGroup.noteOff(midiChan, note); + } + + void noteOn(int note, EventGroup eventGroup) { + eventGroup.noteOn(midiChan, note, lastVelocity); + } + + void noteOn(int note, int velocity, EventGroup eventGroup) { + lastVelocity = velocity; + noteOn(note, eventGroup); + } + + void pan(int pan, EventGroup eventGroup) { + eventGroup.pan(midiChan, pan); + } + + void patchChange(int patchId, EventGroup eventGroup) { + eventGroup.patchChange(midiChan, patchId); + } + + void pitchBend(int wheelVal, EventGroup eventGroup) { + eventGroup.pitchBend(midiChan, wheelVal); + } + + void pitchBendSensitivity(int semitones, EventGroup eventGroup) { + eventGroup.pitchBendSensitivity(midiChan, semitones); + } + + void resetAll(EventGroup eventGroup) { + eventGroup.resetAllControllern(midiChan); + } + + void reverbDepth(int depth, EventGroup eventGroup) { + eventGroup.reverbDepth(midiChan, depth); + } + + void vibratoChange(int depth, EventGroup eventGroup) { + eventGroup.vibratoChange(midiChan, depth); + } + + void volume(int vol, EventGroup eventGroup) { + eventGroup.volume(midiChan, vol); + lastVolume = vol; + } + + void volumeChanged(EventGroup eventGroup) { + eventGroup.volume(midiChan, lastVolume); + } + private int lastVelocity; + private int lastVolume; + private final int midiChan; + } + + private class ScheduledTransmitter implements Transmitter { + + @Override + public void close() { + lock.lock(); + try { + if (autoShutdown && exec != null) { + exec.shutdown(); + } + autoShutdown = false; + exec = null; + } finally { + lock.unlock(); + } + } + + @Override + public Receiver getReceiver() { + return receiver; + } + + @Override + public void setReceiver(Receiver receiver) { + EventGroup currentGroup = null; + lock.lock(); + try { + if (this.receiver != null) { + if (this.future.cancel(false)) { + currentGroup = triggerTask.eventGroup; + } + } else { + nextGroupTime = System.nanoTime(); + } + this.receiver = receiver; + scheduleIfRequired(receiver, currentGroup); + } finally { + lock.unlock(); + } + } + + ScheduledTransmitter(ByteBuffer scoreBuffer, boolean looping) { + this.exec = FinnwMusicModule.this.exec; + this.looping = looping; + this.scoreBuffer = scoreBuffer; + } + + void scheduleIfRequired(Receiver receiver, + EventGroup currentGroup) { + assert (((ReentrantLock) lock).isHeldByCurrentThread()); + if (currentGroup == null) { + try { + currentGroup = nextEventGroup(scoreBuffer, looping); + if (currentGroup != null) { + triggerTask = new TriggerTask(currentGroup, receiver); + long delay = Math.max(0, nextGroupTime - System.nanoTime()); + future + = exec.schedule(triggerTask, delay, TimeUnit.NANOSECONDS); + nextGroupTime += currentGroup.getDelay() * nanosPerTick; + } else { + triggerTask = null; + future = null; + } + } catch (RejectedExecutionException ex) { + // This is normal when shutting down + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "schedule failure", ex); + } + } + } + + void stop() { + assert (((ReentrantLock) lock).isHeldByCurrentThread()); + if (future != null) { + future.cancel(false); + try { + future.get(); + } catch (InterruptedException | ExecutionException | CancellationException ex) { + } + future = null; + } + EventGroup cleanup = new EventGroup(0f); + for (Channel chan : channels) { + chan.allNotesOff(cleanup); + } + cleanup.sendTo(receiver); + } + + void volumeChanged() { + assert (((ReentrantLock) lock).isHeldByCurrentThread()); + EventGroup adjust = new EventGroup(volume); + for (Channel chan : channels) { + chan.volumeChanged(adjust); + } + adjust.sendTo(receiver); + } + TriggerTask triggerTask; + + private class TriggerTask implements Runnable { + + @Override + public void run() { + boolean shouldSend = false; + lock.lock(); + try { + if (triggerTask == this) { + shouldSend = true; + scheduleIfRequired(receiver, null); + } + } finally { + lock.unlock(); + } + if (shouldSend) { + eventGroup.sendTo(receiver); + } + } + + TriggerTask(EventGroup eventGroup, Receiver receiver) { + this.eventGroup = eventGroup; + this.receiver = receiver; + } + + final EventGroup eventGroup; + final Receiver receiver; + } + + private boolean autoShutdown; + + private ScheduledExecutorService exec; + + private ScheduledFuture future; + + private final boolean looping; + + private long nextGroupTime; + + private Receiver receiver; + + private final ByteBuffer scoreBuffer; + } + + /** Contains unfiltered MUS data */ + private class Song { + + Song(ByteBuffer data) { + this.data = data.asReadOnlyBuffer(); + this.data.order(ByteOrder.LITTLE_ENDIAN); + byte[] magic = new byte[4]; + this.data.get(magic); + ByteBuffer magicBuf = ByteBuffer.wrap(magic); + if (!hasMusMagic(magicBuf)) { + throw new IllegalArgumentException("Expected magic string \"MUS\\x1a\" but found " + Arrays.toString(magic)); + } + this.scoreLen = this.data.getShort() & 0xffff; + this.scoreStart = this.data.getShort() & 0xffff; + } + + /** Get only the score part of the data (skipping the header) */ + ByteBuffer getScoreBuffer() { + ByteBuffer scoreBuffer = this.data.duplicate(); + scoreBuffer.position(scoreStart); + scoreBuffer.limit(scoreStart + scoreLen); + return scoreBuffer.slice(); + } + private final ByteBuffer data; + private final int scoreLen; + private final int scoreStart; + } + + private ScheduledTransmitter currentTransmitter; + + private Receiver receiver; + + /** Songs indexed by handle */ + private final List songs; + +} \ No newline at end of file diff --git a/doom/src/s/IDoomSound.java b/doom/src/s/IDoomSound.java new file mode 100644 index 0000000..c20bc0c --- /dev/null +++ b/doom/src/s/IDoomSound.java @@ -0,0 +1,191 @@ +package s; + +import data.sfxinfo_t; +import data.sounds.musicenum_t; +import data.sounds.sfxenum_t; +import doom.CVarManager; +import doom.CommandVariable; +import doom.DoomMain; +import p.mobj_t; + +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: IDoomSound.java,v 1.5 2011/08/24 15:55:12 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// The not so system specific sound interface (s_sound.*) +// Anything high-level like e.g. handling of panning, sound origin, +// sound multiplicity etc. should be handled here, but not e.g. actual +// sound playback, sound threads, etc. +// That's the job of ISound and IMusic (roughly equivelnt to i_sound.*, but +// with separate concerns for SFX and MUSIC. +// +//----------------------------------------------------------------------------- +public interface IDoomSound { + + static IDoomSound chooseSoundIsPresent(DoomMain DM, CVarManager CVM, ISoundDriver ISND) { + if (!CVM.bool(CommandVariable.NOSOUND) || (ISND instanceof DummySFX && DM.music instanceof DummyMusic)) { + return new AbstractDoomAudio(DM, DM.numChannels); + } else { + /** + * Saves a lot of distance calculations, + * if we're not to output any sound at all. + * TODO: create a Dummy that can handle music alone. + */ + return new DummySoundDriver(); + } + } + + class channel_t { + + // sound information (if null, channel avail.) + sfxinfo_t sfxinfo; + + // origin of sound + ISoundOrigin origin; + + // handle of the sound being played + int handle; + } + + /** Convenience hack */ + public static final int NUMSFX = sfxenum_t.NUMSFX.ordinal(); + + // Purpose? + public static final char snd_prefixen[] + = {'P', 'P', 'A', 'S', 'S', 'S', 'M', 'M', 'M', 'S', 'S', 'S'}; + + public static final int S_MAX_VOLUME = 127; + + // when to clip out sounds + // Does not fit the large outdoor areas. + public static final int S_CLIPPING_DIST = (1200 * 0x10000); + + // Distance tp origin when sounds should be maxed out. + // This should relate to movement clipping resolution + // (see BLOCKMAP handling). + // Originally: (200*0x10000). + public static final int S_CLOSE_DIST = (160 * 0x10000); + + public static final int S_ATTENUATOR = ((S_CLIPPING_DIST - S_CLOSE_DIST) >> m.fixed_t.FRACBITS); + + // Adjustable by menu. + //protected final int NORM_VOLUME snd_MaxVolume + public static final int NORM_PITCH = 128; + public final static int NORM_PRIORITY = 64; + public final static int NORM_SEP = 128; + + public final static int S_PITCH_PERTURB = 1; + public final static int S_STEREO_SWING = (96 * 0x10000); + + // percent attenuation from front to back + public final static int S_IFRACVOL = 30; + + public final static int NA = 0; + public final static int S_NUMCHANNELS = 2; + + /** + * Initializes sound stuff, including volume Sets channels, SFX and music + * volume, allocates channel buffer, sets S_sfx lookup. + */ + void Init(int sfxVolume, int musicVolume); + + /** + * Per level startup code. Kills playing sounds at start of level, + * determines music if any, changes music. + */ + public void Start(); + + /** + * Start sound for thing at using from sounds.h + */ + public void StartSound(ISoundOrigin origin, int sound_id); + + /** + * Start sound for thing at using from sounds.h + * Convenience method using sfxenum_t instead. Delegated to int version. + * + */ + public void StartSound(ISoundOrigin origin, sfxenum_t sound_id); + + /** Will start a sound at a given volume. */ + public void StartSoundAtVolume(ISoundOrigin origin, int sound_id, int volume); + + /** Stop sound for thing at */ + public void StopSound(ISoundOrigin origin); + + /** + * Start music using from sounds.h, and set whether looping + * + * @param musicnum + * @param looping + */ + public void ChangeMusic(int musicnum, boolean looping); + + public void ChangeMusic(musicenum_t musicnum, boolean looping); + + /** Stops the music fer sure. */ + public void StopMusic(); + + /** Stop and resume music, during game PAUSE. */ + public void PauseSound(); + + public void ResumeSound(); + + /** + * Updates music & sounds + * + * @param listener + */ + public void UpdateSounds(mobj_t listener); + + public void SetMusicVolume(int volume); + + public void SetSfxVolume(int volume); + + /** Start music using from sounds.h */ + public void StartMusic(int music_id); + + /** Start music using from sounds.h + * Convenience method using musicenum_t. + */ + public void StartMusic(musicenum_t music_id); + + // + // Internals. + // + // MAES: these appear to be only of value for internal implementation, + // and are never called externally. Thus, they might as well + // not be part of the interface, even though it's convenient to reuse them. + // + /* + int + S_getChannel + ( mobj_t origin, + sfxinfo_t sfxinfo ); + + + int + S_AdjustSoundParams + ( mobj_t listener, + mobj_t source, + int vol, + int sep, + int pitch ); + + void S_StopChannel(int cnum); + */ +} \ No newline at end of file diff --git a/doom/src/s/IMusic.java b/doom/src/s/IMusic.java new file mode 100644 index 0000000..d9045c2 --- /dev/null +++ b/doom/src/s/IMusic.java @@ -0,0 +1,50 @@ +package s; + +// +import doom.CVarManager; +import doom.CommandVariable; + +// MUSIC I/O +// +public interface IMusic { + + void InitMusic(); + + void ShutdownMusic(); + // Volume. + + void SetMusicVolume(int volume); + + /** PAUSE game handling. */ + void PauseSong(int handle); + + void ResumeSong(int handle); + + /** Registers a song handle to song data. + * This should handle any conversions from MUS/MIDI/OPL/etc. + * + * */ + int RegisterSong(byte[] data); + + /** Called by anything that wishes to start music. + plays a song, and when the song is done, + starts playing it again in an endless loop. + Horrible thing to do, considering. */ + void + PlaySong(int handle, + boolean looping); + + /** Stops a song over 3 seconds. */ + void StopSong(int handle); + + /** See above (register), then think backwards */ + void UnRegisterSong(int handle); + + public static IMusic chooseModule(CVarManager CVM) { + if (CVM.bool(CommandVariable.NOMUSIC) || CVM.bool(CommandVariable.NOSOUND)) { + return new DummyMusic(); + } else { + return new DavidMusicModule(); + } + } +} \ No newline at end of file diff --git a/doom/src/s/ISoundDriver.java b/doom/src/s/ISoundDriver.java new file mode 100644 index 0000000..6d361aa --- /dev/null +++ b/doom/src/s/ISoundDriver.java @@ -0,0 +1,155 @@ +package s; + +import data.sfxinfo_t; +import data.sounds.sfxenum_t; +import doom.CVarManager; +import doom.CommandVariable; +import doom.DoomMain; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +//Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: ISoundDriver.java,v 1.1 2012/11/08 17:12:42 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// +// DESCRIPTION: +// System interface, sound. Anything implementation-specific should +// implement this. +// +//----------------------------------------------------------------------------- +public interface ISoundDriver { + + static final Logger LOGGER = Loggers.getLogger(ISoundDriver.class.getName()); + + public static final int VOLUME_STEPS = 128; + public static final int PANNING_STEPS = 256; + public static final int IDLE_HANDLE = -1; + public static final int BUSY_HANDLE = -2; + // Needed for calling the actual sound output + // We mix 1024 samples each time, but we only call UpdateSound() + // 1 time out of three. + + public static final int NUM_CHANNELS = 8; + // It is 2 for 16bit, and 2 for two channels. + public static final int BUFMUL = 4; + + public static final int SAMPLERATE = 22050; // Hz + + // Update all 30 millisecs, approx. 30fps synchronized. + // Linux resolution is allegedly 10 millisecs, + // scale is microseconds. + public static final int SOUND_INTERVAL = 500; + + /** Yes, it's possible to select a different sound frame rate */ + public static final int SND_FRAME_RATE = 21; + // Was 512, but if you mix that many samples per tic you will + // eventually outrun the buffer :-/ I fail to see the mathematical + // justification behind this, unless they simply wanted the buffer to + // be a nice round number in size. + public static final int SAMPLECOUNT = SAMPLERATE / SND_FRAME_RATE; + public static final int MIXBUFFERSIZE = (SAMPLECOUNT * BUFMUL); + public static final int SAMPLESIZE = 16; // 16bit + public static final int NUMSFX = sfxenum_t.NUMSFX.ordinal(); + public static final int MAXHANDLES = 100; + /** How many audio chunks/frames to mix before submitting them to + * the output. + */ + public static final int BUFFER_CHUNKS = 5; + + /** Ths audio buffer size of the audioline itself. + * Increasing this is the only effective way to combat output stuttering on + * slower machines. + */ + public static final int AUDIOLINE_BUFFER = 2 * BUFFER_CHUNKS * MIXBUFFERSIZE; + public static final int SOUND_PERIOD = 1000 / SND_FRAME_RATE; // in ms + + public static ISoundDriver chooseModule(DoomMain DM, CVarManager CVM) { + final ISoundDriver driver; + if (CVM.bool(CommandVariable.NOSFX) || CVM.bool(CommandVariable.NOSOUND)) { + driver = new DummySFX(); + } else { + // Switch between possible sound drivers. + if (CVM.bool(CommandVariable.AUDIOLINES)) { // Crudish. + driver = new DavidSFXModule(DM, DM.numChannels); + } else if (CVM.bool(CommandVariable.SPEAKERSOUND)) { // PC Speaker emulation + driver = new SpeakerDoomSoundDriver(DM, DM.numChannels); + } else if (CVM.bool(CommandVariable.CLIPSOUND)) { + driver = new ClipSFXModule(DM, DM.numChannels); + } else if (CVM.bool(CommandVariable.CLASSICSOUND)) { // This is the default + driver = new ClassicDoomSoundDriver(DM, DM.numChannels); + } else { // This is the default + driver = new SuperDoomSoundDriver(DM, DM.numChannels); + } + } + // Check for sound init failure and revert to dummy + if (!driver.InitSound()) { + LOGGER.log(Level.WARNING, "S_InitSound: failed. Reverting to dummy..."); + return new DummySFX(); + } + return driver; + } + + /** Init at program start. Return false if device invalid, + * so that caller can decide best course of action. + * The suggested one is to swap the sound "driver" for a dummy. + * + * @return + */ + boolean InitSound(); + + // ... update sound buffer and audio device at runtime... + void UpdateSound(); + + void SubmitSound(); + + // ... shut down and relase at program termination. + void ShutdownSound(); + + // + // SFX I/O + // + // Initialize channels? + void SetChannels(int numChannels); + + // Get raw data lump index for sound descriptor. + int GetSfxLumpNum(sfxinfo_t sfxinfo); + + // Starts a sound in a particular sound channel. + int StartSound(int id, + int vol, + int sep, + int pitch, + int priority); + + // Stops a sound channel. + void StopSound(int handle); + + /** Called by S_*() functions to see if a channel is still playing. + Returns false if no longer playing, true if playing. This is + a relatively "high level" function, so its accuracy relies on + what the "system specific" sound code reports back */ + boolean SoundIsPlaying(int handle); + + /* Updates the volume, separation, + and pitch of a sound channel. */ + void UpdateSoundParams(int handle, + int vol, + int sep, + int pitch); + +} \ No newline at end of file diff --git a/doom/src/s/ISoundOrigin.java b/doom/src/s/ISoundOrigin.java new file mode 100644 index 0000000..e3cde42 --- /dev/null +++ b/doom/src/s/ISoundOrigin.java @@ -0,0 +1,11 @@ +package s; + +/** Use this instead of that degemobj_t crud */ +public interface ISoundOrigin { + + public int getX(); + + public int getY(); + + public int getZ(); +} \ No newline at end of file diff --git a/doom/src/s/MusReader.java b/doom/src/s/MusReader.java new file mode 100644 index 0000000..46103ca --- /dev/null +++ b/doom/src/s/MusReader.java @@ -0,0 +1,309 @@ +package s; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MetaMessage; +import javax.sound.midi.MidiEvent; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.Sequence; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Track; +import m.Swap; + +/** + * A MUS lump reader that loads directly to a Sequence. + * + * Unlike QMusToMid, does not keep the MIDI version in a temporary file. + * + * @author finnw + * + */ +public class MusReader { + + /** Create a sequence from an InputStream. + * This is the counterpart of {@link MidiSystem#getSequence(InputStream)} + * for MUS format. + * + * @param is MUS data (this method does not try to auto-detect the format.) + */ + public static Sequence getSequence(InputStream is) + throws IOException, InvalidMidiDataException { + DataInputStream dis = new DataInputStream(is); + dis.skip(6); + int rus = dis.readUnsignedShort(); + short scoreStart = Swap.SHORT((char) rus); + dis.skip(scoreStart - 8); + Sequence sequence = new Sequence(Sequence.SMPTE_30, 14, 1); + Track track = sequence.getTracks()[0]; + int[] chanVelocity = new int[16]; + Arrays.fill(chanVelocity, 100); + EventGroup eg; + long tick = 0; + while ((eg = nextEventGroup(dis, chanVelocity)) != null) { + tick = eg.appendTo(track, tick); + } + MetaMessage endOfSequence = new MetaMessage(); + endOfSequence.setMessage(47, new byte[]{0}, 1); + track.add(new MidiEvent(endOfSequence, tick)); + return sequence; + } + + private static EventGroup + nextEventGroup(InputStream is, int[] channelVelocity) throws IOException { + EventGroup result = new EventGroup(); + boolean last; + do { + int b = is.read(); + if (b < 0) { + return result.emptyToNull(); + } + int descriptor = b & 0xff; + last = (descriptor & 0x80) != 0; + int eventType = (descriptor >> 4) & 7; + int chanIndex = descriptor & 15; + final int midiChan; + if (chanIndex < 9) { + midiChan = chanIndex; + } else if (chanIndex < 15) { + midiChan = chanIndex + 1; + } else { + midiChan = 9; + } + switch (eventType) { + case 0: { + int note = is.read() & 0xff; + if ((note & 0x80) != 0) { + throw new IllegalArgumentException("Invalid note byte"); + } + result.noteOff(midiChan, note); + } + break; + case 1: { + int note = is.read() & 0xff; + boolean hasVelocity = (note & 0x80) != 0; + final int velocity; + if (hasVelocity) { + velocity = is.read() & 0xff; + if ((velocity & 0x80) != 0) { + throw new IllegalArgumentException("Invalid velocity byte"); + } + channelVelocity[midiChan] = velocity; + } else { + velocity = channelVelocity[midiChan]; + } + result.noteOn(midiChan, note & 0x7f, velocity); + } + break; + case 2: { + int wheelVal = is.read() & 0xff; + result.pitchBend(midiChan, wheelVal); + } + break; + case 3: { + int sysEvt = is.read() & 0xff; + switch (sysEvt) { + case 10: + result.allSoundsOff(midiChan); + break; + case 11: + result.allNotesOff(midiChan); + break; + case 14: + result.resetAllControllers(midiChan); + break; + default: + String msg = String.format("Invalid system event (%d)", sysEvt); + throw new IllegalArgumentException(msg); + } + } + break; + case 4: + int cNum = is.read() & 0xff; + if ((cNum & 0x80) != 0) { + throw new IllegalArgumentException("Invalid controller number "); + } + int cVal = is.read() & 0xff; + if (cNum == 3 && 133 <= cVal && cVal <= 135) { + // workaround for some TNT.WAD tracks + cVal = 127; + } + if ((cVal & 0x80) != 0) { + String msg = String.format("Invalid controller value (%d; cNum=%d)", cVal, cNum); + throw new IllegalArgumentException(msg); + } + switch (cNum) { + case 0: + result.patchChange(midiChan, cVal); + break; + case 1: + // Don't forward this to the MIDI device. Some synths if + // in GM level 1 mode will react badly to banks that are + // undefined in GM Level 1 + break; + case 2: + result.vibratoChange(midiChan, cVal); + break; + case 3: + result.volume(midiChan, cVal); + break; + case 4: + result.pan(midiChan, cVal); + break; + case 5: + result.expression(midiChan, cVal); + break; + case 6: + result.reverbDepth(midiChan, cVal); + break; + case 7: + result.chorusDepth(midiChan, cVal); + break; + case 8: + result.sustain(midiChan, cVal); + break; + default: + throw new AssertionError("Unknown controller number: " + cNum + "(value: " + cVal + ")"); + } + break; + case 6: + return result.emptyToNull(); + default: + String msg = String.format("Unknown event type: %d", eventType); + throw new IllegalArgumentException(msg); + } + } while (!last); + int qTics = readVLV(is); + result.addDelay(qTics); + return result; + } + + private static int readVLV(InputStream is) throws IOException { + int result = 0; + boolean last; + do { + int digit = is.read() & 0xff; + last = (digit & 0x80) == 0; + result <<= 7; + result |= digit & 127; + } while (!last); + return result; + } + + private static class EventGroup { + + EventGroup() { + this.messages = new ArrayList<>(); + } + + void addDelay(long ticks) { + delay += ticks; + } + + void allNotesOff(int midiChan) { + addControlChange(midiChan, CHM_ALL_NOTES_OFF, 0); + } + + void allSoundsOff(int midiChan) { + addControlChange(midiChan, CHM_ALL_SOUND_OFF, 0); + } + + long appendTo(Track track, long tick) { + for (MidiMessage msg : messages) { + track.add(new MidiEvent(msg, tick)); + } + return tick + delay * 3; + } + + void chorusDepth(int midiChan, int depth) { + addControlChange(midiChan, CTRL_CHORUS_DEPTH, depth); + } + + EventGroup emptyToNull() { + if (messages.isEmpty()) { + return null; + } else { + return this; + } + } + + void expression(int midiChan, int expr) { + addControlChange(midiChan, CTRL_EXPRESSION_POT, expr); + } + + void noteOn(int midiChan, int note, int velocity) { + addShortMessage(midiChan, ShortMessage.NOTE_ON, note, velocity); + } + + void noteOff(int midiChan, int note) { + addShortMessage(midiChan, ShortMessage.NOTE_OFF, note, 0); + } + + void pan(int midiChan, int pan) { + addControlChange(midiChan, CTRL_PAN, pan); + } + + void patchChange(int midiChan, int patchId) { + addShortMessage(midiChan, ShortMessage.PROGRAM_CHANGE, patchId, 0); + } + + void pitchBend(int midiChan, int wheelVal) { + int pb14 = wheelVal * 64; + addShortMessage(midiChan, ShortMessage.PITCH_BEND, pb14 % 128, pb14 / 128); + } + + void resetAllControllers(int midiChan) { + addControlChange(midiChan, CHM_RESET_ALL, 0); + } + + void reverbDepth(int midiChan, int depth) { + addControlChange(midiChan, CTRL_REVERB_DEPTH, depth); + } + + void sustain(int midiChan, int on) { + addControlChange(midiChan, CTRL_SUSTAIN, on); + } + + void vibratoChange(int midiChan, int depth) { + addControlChange(midiChan, CTRL_MODULATION_POT, depth); + } + + void volume(int midiChan, int vol) { + addControlChange(midiChan, CTRL_VOLUME, vol); + } + + private void addControlChange(int midiChan, int ctrlId, int ctrlVal) { + addShortMessage(midiChan, ShortMessage.CONTROL_CHANGE, ctrlId, ctrlVal); + } + + private void addShortMessage(int midiChan, int cmd, int data1, int data2) { + try { + ShortMessage msg = new ShortMessage(); + msg.setMessage(cmd, midiChan, data1, data2); + messages.add(msg); + } catch (InvalidMidiDataException ex) { + throw new RuntimeException(ex); + } + } + + private static final int CHM_ALL_NOTES_OFF = 123; + private static final int CHM_ALL_SOUND_OFF = 120; + private static final int CTRL_CHORUS_DEPTH = 93; + private static final int CTRL_EXPRESSION_POT = 11; + private static final int CTRL_PAN = 10; + private static final int CTRL_SUSTAIN = 64; + private static final int CHM_RESET_ALL = 121; + private static final int CTRL_REVERB_DEPTH = 91; + private static final int CTRL_MODULATION_POT = 1; + private static final int CTRL_VOLUME = 7; + + private long delay; + private final List messages; + } + +} \ No newline at end of file diff --git a/doom/src/s/QMusToMid.java b/doom/src/s/QMusToMid.java new file mode 100644 index 0000000..caecc5c --- /dev/null +++ b/doom/src/s/QMusToMid.java @@ -0,0 +1,838 @@ +package s; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class QMusToMid { + + private static final Logger LOGGER = Loggers.getLogger(QMusToMid.class.getName()); + + public static final int NOTMUSFILE = 1; + /* Not a MUS file */ + public static final int COMUSFILE = 2; + /* Can't open MUS file */ + public static final int COTMPFILE = 3; + /* Can't open TMP file */ + public static final int CWMIDFILE = 4; + /* Can't write MID file */ + public static final int MUSFILECOR = 5; + /* MUS file corrupted */ + public static final int TOOMCHAN = 6; + /* Too many channels */ + public static final int MEMALLOC = 7; + /* Memory allocation error */ + + /* some (old) compilers mistake the "MUS\x1A" construct (interpreting + it as "MUSx1A") */ + public static final String MUSMAGIC = "MUS\032"; + /* this seems to work */ + public static final String MIDIMAGIC = "MThd\000\000\000\006\000\001"; + public static final String TRACKMAGIC1 = "\000\377\003\035"; + public static final String TRACKMAGIC2 = "\000\377\057\000"; + public static final String TRACKMAGIC3 = "\000\377\002\026"; + public static final String TRACKMAGIC4 = "\000\377\131\002\000\000"; + public static final String TRACKMAGIC5 = "\000\377\121\003\011\243\032"; + public static final String TRACKMAGIC6 = "\000\377\057\000"; + + public static final int EOF = -1; + + public static class Ptr { + + a val; + + public Ptr(a val) { + this.val = val; + } + + public a get() { + return val; + } + + public void set(a newval) { + val = newval; + } + } + + public static class MUSheader { + + byte[] ID = new byte[4]; + /* identifier "MUS" 0x1A */ + int ScoreLength; + int ScoreStart; + int channels; + /* count of primary channels */ + int SecChannels; + /* count of secondary channels (?) */ + int InstrCnt; + int dummy; + /* variable-length part starts here */ + int[] instruments; + } + + public static class Track { + + long current; + byte vel; + long DeltaTime; + byte LastEvent; + byte[] data; + /* Primary data */ + } + + long TRACKBUFFERSIZE = 65536L; + + /* 64 Ko */ + void TWriteByte(int MIDItrack, byte byte_, Track track[]) { + long pos; + + pos = track[MIDItrack].current; + if (pos < TRACKBUFFERSIZE) { + track[MIDItrack].data[(int) pos] = byte_; + } else { + LOGGER.log(Level.SEVERE, "ERROR: Track buffer full. Increase the track buffer size (option -size)."); + System.exit(1); + } + track[MIDItrack].current++; + } + + void TWriteVarLen(int tracknum, long value, + Track[] track) { + long buffer; + + buffer = value & 0x7f; + while ((value >>= 7) != 0) { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7f); + } + while (true) { + TWriteByte(tracknum, (byte) buffer, track); + if ((buffer & 0x80) != 0) { + buffer >>= 8; + } else { + break; + } + } + } + + int ReadMUSheader(MUSheader MUSh, InputStream file) { + try { + if (DoomIO.fread(MUSh.ID, 4, 1, file) != 1) { + return COMUSFILE; + } + + /*if( strncmp( MUSh->ID, MUSMAGIC, 4 ) ) + return NOTMUSFILE ;*/ + if ((MUSh.ScoreLength = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + if ((MUSh.ScoreStart = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + if ((MUSh.channels = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + if ((MUSh.SecChannels = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + if ((MUSh.InstrCnt = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + if ((MUSh.dummy = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + + MUSh.instruments = new int[MUSh.InstrCnt]; + for (int i = 0; i < MUSh.InstrCnt; i++) { + if ((MUSh.instruments[i] = DoomIO.freadint(file)) == -1) { + return COMUSFILE; + } + } + + return 0; + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not read MUS header", e); + return COMUSFILE; + } + } + + int WriteMIDheader(int ntrks, int division, Object file) { + try { + //_D_: those two lines for testing purposes only + //fisTest.close(); + //fisTest = new FileInputStream("C:\\Users\\David\\Desktop\\qmus2mid\\test.mid"); + + DoomIO.fwrite(MIDIMAGIC, 10, 1, file); + DoomIO.fwrite2(DoomIO.toByteArray(ntrks), 2, file); + DoomIO.fwrite2(DoomIO.toByteArray(division), 2, file); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Could not write MID header", e); + } + + return 0; + } + + byte last(int e) { + return (byte) (e & 0x80); + } + + byte event_type(int e) { + return (byte) ((e & 0x7F) >> 4); + } + + byte channel(int e) { + return (byte) (e & 0x0F); + } + + void TWriteString(char tracknum, String string, int length, + Track[] track) { + int i; + + for (i = 0; i < length; i++) { + TWriteByte(tracknum, (byte) string.charAt(i), track); + } + } + + void WriteTrack(int tracknum, Object file, Track[] track) { + long size; + int quot, rem; + + try { + /* Do we risk overflow here ? */ + size = track[tracknum].current + 4; + DoomIO.fwrite("MTrk", 4, 1, file); + if (tracknum == 0) { + size += 33; + } + + DoomIO.fwrite2(DoomIO.toByteArray((int) size, 4), 4, file); + if (tracknum == 0) { + DoomIO.fwrite(TRACKMAGIC1 + "Quick MUS->MID ! by S.Bacquet", 33, 1, file); + } + + quot = (int) (track[tracknum].current / 4096); + rem = (int) (track[tracknum].current - quot * 4096); + + DoomIO.fwrite(track[tracknum].data, (int) track[tracknum].current, 1, file); + DoomIO.fwrite(TRACKMAGIC2, 4, 1, file); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not write track", e); + } + } + + void WriteFirstTrack(Object file) { + try { + byte[] size = DoomIO.toByteArray(43, 4); + + DoomIO.fwrite("MTrk", 4, 1, file); + DoomIO.fwrite2(size, 4, file); + DoomIO.fwrite(TRACKMAGIC3, 4, 1, file); + DoomIO.fwrite("QMUS2MID (C) S.Bacquet", 22, 1, file); + DoomIO.fwrite(TRACKMAGIC4, 6, 1, file); + DoomIO.fwrite(TRACKMAGIC5, 7, 1, file); + DoomIO.fwrite(TRACKMAGIC6, 4, 1, file); + + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not write first track", e); + } + } + + long ReadTime(InputStream file) throws IOException { + long time = 0; + int byte_; + + do { + byte_ = getc(file); + if (byte_ != EOF) { + time = (time << 7) + (byte_ & 0x7F); + } + } while ((byte_ != EOF) && ((byte_ & 0x80) != 0)); + + return time; + } + + byte FirstChannelAvailable(byte[] MUS2MIDchannel) { + int i; + byte old15 = MUS2MIDchannel[15], max = -1; + + MUS2MIDchannel[15] = -1; + for (i = 0; i < 16; i++) { + if (MUS2MIDchannel[i] > max) { + max = MUS2MIDchannel[i]; + } + } + MUS2MIDchannel[15] = old15; + + return (max == 8 ? 10 : (byte) (max + 1)); + } + + int getc(InputStream is) throws IOException { + return is.read(); + } + + int qmus2mid(InputStream mus, Object mid, boolean nodisplay, + int division, int BufferSize, boolean nocomp) throws IOException { + Track[] track = new Track[16]; + for (int i = 0; i < track.length; i++) { + track[i] = new Track(); + } + + int TrackCnt = 0; + byte et, MUSchannel, MIDIchannel, MIDItrack, NewEvent; + int i, event, data, r; + MUSheader MUSh = new MUSheader(); + long DeltaTime, TotalTime = 0, time, min, n = 0; + byte[] MUS2MIDcontrol = new byte[]{ + 0, /* Program change - not a MIDI control change */ + 0x00, /* Bank select */ + 0x01, /* Modulation pot */ + 0x07, /* Volume */ + 0x0A, /* Pan pot */ + 0x0B, /* Expression pot */ + 0x5B, /* Reverb depth */ + 0x5D, /* Chorus depth */ + 0x40, /* Sustain pedal */ + 0x43, /* Soft pedal */ + 0x78, /* All sounds off */ + 0x7B, /* All notes off */ + 0x7E, /* Mono */ + 0x7F, /* Poly */ + 0x79 /* Reset all controllers */}; + byte[] MIDIchan2track = new byte[16]; + byte[] MUS2MIDchannel = new byte[16]; + char ouch = 0, sec; + + DoomIO.writeEndian = DoomIO.Endian.LITTLE; + + r = ReadMUSheader(MUSh, mus); + if (r != 0) { + return r; + } + /* if( fseek( file_mus, MUSh.ScoreStart, SEEK_SET ) ) + { + Close() ; + return MUSFILECOR ; + }*/ + if (!nodisplay) { + LOGGER.log(Level.FINE, String.format("stream (%d bytes) contains %d melodic channel(s)", mus.available(), MUSh.channels)); + } + + if (MUSh.channels > 15) /* <=> MUSchannels+drums > 16 */ { + return TOOMCHAN; + } + + for (i = 0; i < 16; i++) { + MUS2MIDchannel[i] = -1; + track[i].current = 0; + track[i].vel = 64; + track[i].DeltaTime = 0; + track[i].LastEvent = 0; + track[i].data = null; + } + if (BufferSize != 0) { + TRACKBUFFERSIZE = ((long) BufferSize) << 10; + if (!nodisplay) { + LOGGER.log(Level.FINE, String.format("Track buffer size set to %d KB.", BufferSize)); + } + } + + if (!nodisplay) { + LOGGER.log(Level.FINE, "Converting..."); + } + event = getc(mus); + et = event_type(event); + MUSchannel = channel(event); + while ((et != 6) && mus.available() > 0 && (event != EOF)) { + if (MUS2MIDchannel[MUSchannel] == -1) { + MIDIchannel = MUS2MIDchannel[MUSchannel] + = (MUSchannel == 15 ? 9 : FirstChannelAvailable(MUS2MIDchannel)); + MIDItrack = MIDIchan2track[MIDIchannel] = (byte) TrackCnt++; + if ((track[MIDItrack].data = new byte[(int) TRACKBUFFERSIZE]) == null) { + return MEMALLOC; + } + } else { + MIDIchannel = MUS2MIDchannel[MUSchannel]; + MIDItrack = MIDIchan2track[MIDIchannel]; + } + TWriteVarLen(MIDItrack, track[MIDItrack].DeltaTime, track); + track[MIDItrack].DeltaTime = 0; + switch (et) { + case 0: + /* release note */ + NewEvent = (byte) (0x90 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + data = getc(mus); + TWriteByte(MIDItrack, (byte) data, track); + TWriteByte(MIDItrack, (byte) 0, track); + break; + case 1: + NewEvent = (byte) (0x90 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + data = getc(mus); + TWriteByte(MIDItrack, (byte) (data & 0x7F), track); + if ((data & 0x80) != 0) { + track[MIDItrack].vel = (byte) getc(mus); + } + TWriteByte(MIDItrack, (byte) track[MIDItrack].vel, track); + break; + case 2: + NewEvent = (byte) (0xE0 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + data = getc(mus); + TWriteByte(MIDItrack, (byte) ((data & 1) << 6), track); + TWriteByte(MIDItrack, (byte) (data >> 1), track); + break; + case 3: + NewEvent = (byte) (0xB0 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + data = getc(mus); + TWriteByte(MIDItrack, MUS2MIDcontrol[data], track); + if (data == 12) { + TWriteByte(MIDItrack, (byte) (MUSh.channels + 1), track); + } else { + TWriteByte(MIDItrack, (byte) 0, track); + } + break; + case 4: + data = getc(mus); + if (data != 0) { + NewEvent = (byte) (0xB0 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + TWriteByte(MIDItrack, MUS2MIDcontrol[data], track); + } else { + NewEvent = (byte) (0xC0 | MIDIchannel); + if ((NewEvent != track[MIDItrack].LastEvent) || (nocomp)) { + TWriteByte(MIDItrack, NewEvent, track); + track[MIDItrack].LastEvent = NewEvent; + } else { + n++; + } + } + data = getc(mus); + TWriteByte(MIDItrack, (byte) data, track); + break; + case 5: + case 7: + return MUSFILECOR; + default: + break; + } + if (last(event) != 0) { + DeltaTime = ReadTime(mus); + TotalTime += DeltaTime; + for (i = 0; i < (int) TrackCnt; i++) { + track[i].DeltaTime += DeltaTime; + } + } + event = getc(mus); + if (event != EOF) { + et = event_type(event); + MUSchannel = channel(event); + } else { + ouch = 1; + } + } + if (!nodisplay) { + LOGGER.log(Level.FINE, "done!"); + } + if (ouch != 0) { + LOGGER.log(Level.WARNING, "WARNING: There are bytes missing at the end of stream. The end of the MIDI file might not fit the original one."); + } + if (division == 0) { + division = 89; + } else if (!nodisplay) { + LOGGER.log(Level.FINE, String.format("Ticks per quarter note set to %d", division)); + } + if (!nodisplay) { + if (division != 89) { + time = TotalTime / 140; + min = time / 60; + sec = (char) (time - min * 60); + //System.out.println( "Playing time of the MUS file : %u'%.2u''.\n", min, sec ) ; + } + time = (TotalTime * 89) / (140 * division); + min = time / 60; + sec = (char) (time - min * 60); + if (division != 89) { + LOGGER.log(Level.FINE, "MID file"); + } else { + LOGGER.log(Level.FINE, String.format("Playing time: %dmin %dsec", (int) min, (int) sec)); + } + } + if (!nodisplay) { + LOGGER.log(Level.FINE, "Writing..."); + } + WriteMIDheader(TrackCnt + 1, division, mid); + WriteFirstTrack(mid); + for (i = 0; i < (int) TrackCnt; i++) { + WriteTrack(i, mid, track); + } + if (!nodisplay) { + LOGGER.log(Level.FINE, "done !"); + } + //if (!nodisplay && (!nocomp)) { + // System.out.println("Compression : %u%%.\n"/*, + // (100 * n) / (n+ (long) ftell( mid ))*/); + //} + return 0; + } + + int convert(String mus, String mid, boolean nodisplay, int div, + int size, boolean nocomp, Ptr ow) throws IOException { + InputStream is = new BufferedInputStream(new FileInputStream(new File(mid))); + OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(mid))); + + int error; + //struct stat file_data ; + char[] buffer = new char[30]; + + + /* we don't need _all_ that checking, do we ? */ + /* Answer : it's more user-friendly */ + /*#ifdef MSDOG + + if( access( mus, 0 ) ) + { + System.out.println( "ERROR : %s does not exist.\n", mus ) ; + return 1 ; + } + + if( !access( mid, 0 ) ) + { + if( !*ow ) + { + System.out.println( "Can't overwrite %s.\n", mid ) ; + return 2 ; + } + if( *ow == 1 ) + { + System.out.println( "%s exists : overwrite (Y=Yes,N=No,A=yes for All,Q=Quit)" + " ? [Y]\b\b", mid ) ; + fflush( stdout ) ; + do + n = toupper( getxkey() ) ; + while( (n != 'Y') && (n != 'N') && (n != K_Return) && (n != 'A') + && (n != 'Q')) ; + switch( n ) + { + case 'N' : + System.out.println( "N\n%s NOT converted.\n", mus ) ; + return 3 ; + case 'A' : + System.out.println( "A" ) ; + *ow = 2 ; + break ; + case 'Q' : + System.out.println( "Q\nQMUS2MID aborted.\n" ) ; + exit( 0 ) ; + break ; + default : break ; + } + System.out.println( "\n" ) ; + } + } + #else*/ + /*if ( ow.get() == 0 ) { + file = fopen(mid, "r"); + if ( file ) { + fclose(file); + System.out.println( "qmus2mid: file %s exists, not removed.\n", mid ) ; + return 2 ; + } + }*/ + /*#endif*/ + return convert(is, os, nodisplay, div, size, nocomp, ow); + } + + int convert(InputStream mus, Object mid, boolean nodisplay, int div, + int size, boolean nocomp, Ptr ow) throws IOException { + int error = qmus2mid(mus, mid, nodisplay, div, size, nocomp); + + if (error != 0) { + switch (error) { + case NOTMUSFILE: + LOGGER.log(Level.SEVERE, "ERROR: stream is not a MUS file."/*, mus*/); + break; + case COMUSFILE: + LOGGER.log(Level.SEVERE, "ERROR: Can't open stream for read."/*, mus*/); + break; + case COTMPFILE: + LOGGER.log(Level.SEVERE, "ERROR: Can't open temp file."); + break; + case CWMIDFILE: + LOGGER.log(Level.SEVERE, "ERROR: Can't write stream (?)."/*, mid */); + break; + case MUSFILECOR: + LOGGER.log(Level.SEVERE, "ERROR: stream is corrupted."/*, mus*/); + break; + case TOOMCHAN: + LOGGER.log(Level.SEVERE, "ERROR: stream contains more than 16 channels."/*, mus*/); + break; + case MEMALLOC: + LOGGER.log(Level.SEVERE, "ERROR: Not enough memory."); + break; + default: + break; + } + return 4; + } + + if (!nodisplay) { + LOGGER.log(Level.FINE, "stream converted successfully."); + /*if( (file = fopen( mid, "rb" )) != NULL ) + { + //stat( mid, &file_data ) ; + fclose( file ) ; + sSystem.out.println( buffer, " : %lu bytes", (long) file_data.st_size ) ; + }*/ + + /*System.out.println( "%s (%scompressed) written%s.\n", mid, nocomp ? "NOT " : "", + file ? buffer : "" ) ;*/ + } + + return 0; + } + +// int CheckParm( char[] check, int argc, char *argv[] ) +// { +// int i; +// +// for ( i = 1 ; iMID v2.0 ! (C) 1995,96 Sebastien Bacquet\n" +// " E-mail : bacquet@iie.cnam.fr\n" +// "===============================================================================\n" ) ; + } + + void PrintSyntax() { +// PrintHeader() ; +// System.out.println( +// #ifdef MSDOG +// "\nSyntax : QMUS2MID musfile1[.mus] {musfile2[.mus] ... | " +// "midifile.mid} [options]\n" +// " Wildcards are accepted.\n" +// " Options are :\n" +// " -query : Query before processing\n" +// " -ow : OK, overwrite (without query)\n" +// #else +// "\nSyntax : QMUS2MID musfile midifile [options]\n" +// " Options are :\n" +// #endif +// " -noow : Don't overwrite !\n" +// " -nodisp : Display nothing ! (except errors)\n" +// " -nocomp : Don't compress !\n" +// " -size ### : Set the track buffer size to ### (in KB). " +// "Default = 64 KB\n" +// " -t ### : Ticks per quarter note. Default = 89\n" +// ) ; + } + + int main(int argc, char[] argv) { + int div = 0, ow = 1, nocomp = 0, size = 0, n; + boolean nodisplay = false; + /*#ifdef MSDOG + int FileCount, query = 0, i, line = 0 ; + char mus[MAXPATH], mid[MAXPATH], drive[MAXDRIVE], middrive[MAXDRIVE], + dir[MAXDIR], middir[MAXDIR], musname[MAXFILE], midname[MAXFILE], + ext[MAXEXT] ; + struct stat s ; + #else*/ + String mus, mid; + /*#endif*/ + + + /*#ifndef MSDOG + if ( !LittleEndian() ) { + System.out.println("\nSorry, this program presently only works on " + "little-endian machines... \n\n"); + exit( EXIT_FAILURE ) ; + } + #endif*/ + + /*#ifdef MSDOG + if( (argc == 1) || (argv[1][0] == '-') ) + #else + if( argc < 3 ) + #endif + { + PrintSyntax() ; + exit( EXIT_FAILURE ) ; + }*/ + + /*#ifdef MSDOG + if( (strrchr( argv[1], '*' ) != NULL) || (strrchr( argv[1], '?' ) != NULL) ) + { + PrintHeader() ; + System.out.println( "Sorry, there is nothing matching %s...\n", argv[1] ) ; + exit( EXIT_FAILURE ) ; + } + strncpy( mus, argv[1], MAXPATH ) ; + strupr( mus ) ; + if( !(fnsplit( mus, drive, dir, musname, NULL ) & FILENAME) ) + { + PrintSyntax() ; + exit( EXIT_FAILURE ) ; + } + #else*/ + //strncpy( mus, argv[1], FILENAME_MAX ) ; + //strncpy( mid, argv[2], FILENAME_MAX ) ; + /*#endif*/ + + /*#ifdef MSDOG + if( CheckParm( "-query", argc, argv ) ) + query = 1 ; + #endif*/ + + /* if( CheckParm( "-nodisp", argc, argv ) ) + nodisplay = 1 ; + */ + if (!nodisplay) { + PrintHeader(); + } + + /*if( (n = CheckParm( "-size", argc, argv )) != 0 ) + size = atoi( argv[n+1] ) ;*/ + /*#ifdef MSDOG + if( CheckParm( "-ow", argc, argv ) ) + ow += 1 ; + #endif + if( CheckParm( "-noow", argc, argv ) ) + ow -= 1 ; + if( (n = CheckParm( "-t", argc, argv )) != 0 ) + div = atoi( argv[n+1] ) ; + if( CheckParm( "-nocomp", argc, argv ) ) + nocomp = 1 ;*/ + + /*#ifdef MSDOG + for( FileCount = 1 ; (FileCount < argc) && (argv[FileCount][0] != '-') ; + FileCount++ ) ; + FileCount-- ; + midname[0] = middrive[0] = middir[0] = 0 ; + if( FileCount == 2 ) + { + if( fnsplit( argv[FileCount], middrive, middir, midname, ext ) + & FILENAME ) + { + if( stricmp( ext, ".MID" ) ) + midname[0] = middrive[0] = middir[0] = 0 ; + else + { + strcpy( mid, argv[FileCount--] ) ; + strupr( mid ) ; + } + } + else + FileCount-- ; + } + if( FileCount > 2 ) + { + if( fnsplit( argv[FileCount], middrive, middir, NULL, NULL ) & FILENAME ) + midname[0] = middrive[0] = middir[0] = 0 ; + else + FileCount-- ; + } + for( i = 0 ; i < FileCount ; i++ ) + { + strupr( argv[i+1] ) ; + n = fnsplit( argv[i+1], drive, dir, musname, ext ) ; + if( !(n & EXTENSION) || !stricmp( ext, ".MUS" ) ) + { + stat( argv[i+1], &s ) ; + if( !S_ISDIR( s.st_mode ) ) + { + fnmerge( mus, drive, dir, musname, ".MUS" ) ; + if( line && !nodisplay ) + System.out.println( "\n" ) ; + if( query ) + { + System.out.println( "Convert %s ? (Y=Yes,N=No,A=yes for All,Q=Quit)" + " [Y]\b\b", mus ) ; + fflush( stdout ) ; + do + n = toupper( getxkey() ) ; + while( (n != 'Y') && (n != 'N') && (n != K_Return) + && (n != 'A') && (n != 'Q')) ; + switch( n ) + { + case 'N' : + System.out.println( "N\n%s NOT converted.\n", mus ) ; + line = 1 ; + continue ; + break ; + case 'Q' : + System.out.println( "Q\nQMUS2MID aborted.\n" ) ; + exit( 0 ) ; + break ; + case 'A' : + query = 0 ; + System.out.println( "A\n" ) ; + break ; + default : + System.out.println( "\n" ) ; + break ; + } + } + if( !midname[0] ) + { + fnmerge( mid, middrive, middir, musname, ".MID" ) ; + strupr( mid ) ; + } + convert( mus, mid, nodisplay, div, size, nocomp, &ow ) ; + line = 1 ; + } + } + } + if( !line && !nodisplay && !query ) + System.out.println( "Sorry, there is no MUS file matching...\n" ) ; + + #else*/ + //convert( mus, mid, nodisplay, div, size, nocomp, ow ) ; +/* #endif*/ + return 0; + } + +} \ No newline at end of file diff --git a/doom/src/s/SpeakerDoomSoundDriver.java b/doom/src/s/SpeakerDoomSoundDriver.java new file mode 100644 index 0000000..eb27e23 --- /dev/null +++ b/doom/src/s/SpeakerDoomSoundDriver.java @@ -0,0 +1,97 @@ +package s; + +import doom.DoomMain; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** A variation of the Classic Sound Driver, decoding the DP-lumps + * instead of the DS. A better way would be to build-in an + * automatic "WAV to SPEAKER" conversion, but that can wait... + * + * @author Maes + * + */ +public class SpeakerDoomSoundDriver extends ClassicDoomSoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(SpeakerDoomSoundDriver.class.getName()); + + public SpeakerDoomSoundDriver(DoomMain DM, int numChannels) { + super(DM, numChannels); + // TODO Auto-generated constructor stub + } + + /** Rigged so it gets SPEAKER sounds instead of regular ones */ + @Override + protected byte[] getsfx(String sfxname, + int[] len, int index) { + byte[] sfx; + byte[] paddedsfx; + int i; + int size; + int paddedsize; + String name; + int sfxlump; + + // Get the sound data from the WAD, allocate lump + // in zone memory. + name = String.format("dp%s", sfxname).toUpperCase(); + + // Now, there is a severe problem with the + // sound handling, in it is not (yet/anymore) + // gamemode aware. That means, sounds from + // DOOM II will be requested even with DOOM + // shareware. + // The sound list is wired into sounds.c, + // which sets the external variable. + // I do not do runtime patches to that + // variable. Instead, we will use a + // default sound for replacement. + if (DM.wadLoader.CheckNumForName(name) == -1) { + sfxlump = DM.wadLoader.GetNumForName("dppistol"); + } else { + sfxlump = DM.wadLoader.GetNumForName(name); + } + + // We must first load and convert it to raw samples. + SpeakerSound SP = (SpeakerSound) DM.wadLoader.CacheLumpNum(sfxlump, 0, SpeakerSound.class); + + sfx = SP.toRawSample(); + + size = sfx.length; + + // MAES: A-ha! So that's how they do it. + // SOund effects are padded to the highest multiple integer of + // the mixing buffer's size (with silence) + paddedsize = ((size - 8 + (SAMPLECOUNT - 1)) / SAMPLECOUNT) * SAMPLECOUNT; + + // Allocate from zone memory. + paddedsfx = new byte[paddedsize]; + + // Now copy and pad. The first 8 bytes are header info, so we discard them. + System.arraycopy(sfx, 8, paddedsfx, 0, size - 8); + + for (i = size - 8; i < paddedsize; i++) { + paddedsfx[i] = (byte) 127; + } + + // Hmm....silence? + for (i = size - 8; i < paddedsize; i++) { + paddedsfx[i] = (byte) 127; + } + + // Remove the cached lump. + DM.wadLoader.UnlockLumpNum(sfxlump); + + if (D) { + LOGGER.log(Level.FINE, String.format("SFX %d size %d padded to %d", index, size, paddedsize)); + } + // Preserve padded length. + len[index] = paddedsize; + + // Return allocated padded data. + // So the first 8 bytes are useless? + return paddedsfx; + } + +} \ No newline at end of file diff --git a/doom/src/s/SpeakerSound.java b/doom/src/s/SpeakerSound.java new file mode 100644 index 0000000..bb5dbe1 --- /dev/null +++ b/doom/src/s/SpeakerSound.java @@ -0,0 +1,115 @@ +package s; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Hashtable; +import w.CacheableDoomObject; + +/** Blatantly ripping off Chocolate Doom */ +public class SpeakerSound implements CacheableDoomObject { + + public short header; + public short length; + public byte[] data; + + public static int[] timer_values = new int[]{0, + 6818, 6628, 6449, 6279, + 6087, 5906, 5736, 5575, + 5423, 5279, 5120, 4971, + 4830, 4697, 4554, 4435, + 4307, 4186, 4058, 3950, + 3836, 3728, 3615, 3519, + 3418, 3323, 3224, 3131, + 3043, 2960, 2875, 2794, + 2711, 2633, 2560, 2485, + 2415, 2348, 2281, 2213, + 2153, 2089, 2032, 1975, + 1918, 1864, 1810, 1757, + 1709, 1659, 1612, 1565, + 1521, 1478, 1435, 1395, + 1355, 1316, 1280, 1242, + 1207, 1173, 1140, 1107, + 1075, 1045, 1015, 986, + 959, 931, 905, 879, + 854, 829, 806, 783, + 760, 739, 718, 697, + 677, 658, 640, 621, + 604, 586, 570, 553, + 538, 522, 507, 493, + 479, 465, 452}; + + /* From analysis of fraggle's PC Speaker timings, it was found + * that their natural logarithm had the following intercept + * (starting at x=1) and slope. Therefore, it's possible + * to go beyong the original 95 hardcoded values. + */ + public static final double INTERCEPT = 8.827321453; + public static final double SLOPE = -0.028890647; + public static final int CIA_8543_FREQ = 1193182; + + public static float[] f = new float[256]; + + static { + f[0] = 0; + + for (int x = 1; x < f.length; x++) { + + //f[x] = CIA_8543_FREQ/timer_values[x]; + f[x] = (float) (CIA_8543_FREQ / Math.exp(INTERCEPT + SLOPE * (x - 1))); + + } + } + + /** Will return a very basic, 8-bit 11.025 KHz rendition of the sound + * This ain't no CuBase or MatLab, so if you were expecting perfect + * sound and solid DSP, go elsewhere. + * + */ + public byte[] toRawSample() { + // Length is in 1/140th's of a second + byte[] chunk = new byte[this.length * 11025 / 140]; + + int counter = 0; + for (int i = 0; i < this.length; i++) { + byte[] tmp = getPhoneme(this.data[i]); + System.arraycopy(tmp, 0, chunk, counter, tmp.length); + counter += tmp.length; + } + return chunk; + } + + private static Hashtable phonemes = new Hashtable<>(); + + public static byte[] getPhoneme(int phoneme) { + + if (!phonemes.containsKey(phoneme)) { + + // Generate a square wave with a duration of 1/140th of a second + int samples = 11025 / 140; + byte[] tmp = new byte[samples]; + + float frequency = f[phoneme]; + for (int i = 0; i < samples; i++) { + tmp[i] = (byte) (127 + 127 * Math.signum(Math.sin(frequency * Math.PI * 2 * (i / 11025f)))); + } + + phonemes.put(phoneme, tmp); + } + + return phonemes.get(phoneme); + + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + buf.order(ByteOrder.LITTLE_ENDIAN); + header = buf.getShort(); + length = buf.getShort(); + data = new byte[length]; + buf.get(data); + + } + +} \ No newline at end of file diff --git a/doom/src/s/SuperDoomSoundDriver.java b/doom/src/s/SuperDoomSoundDriver.java new file mode 100644 index 0000000..255f744 --- /dev/null +++ b/doom/src/s/SuperDoomSoundDriver.java @@ -0,0 +1,925 @@ +package s; + +import static data.sounds.S_sfx; +import data.sounds.sfxenum_t; +import doom.DoomMain; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import mochadoom.Loggers; +import pooling.AudioChunkPool; + +/** + * A spiffy new sound system, based on the Classic sound driver. + * It is entirely asynchronous (runs in its own thread) and even has its own timer. + * This allows it to continue mixing even when the main loop is not responding + * (something which, arguably, could be achieved just with a timer calling + * UpdateSound and SubmitSound). Uses message passing to deliver channel status + * info, and mixed audio directly without using an intermediate buffer, + * saving memory bandwidth. + * + * PROS: + * a) All those of ClassicSoundDriver plus: + * b) Continues normal playback even under heavy CPU load, works smoother + * even on lower powered CPUs. + * c) More efficient due to less copying of audio blocks. + * c) Fewer audio glitches compared to ClassicSoundDriver. + * + * CONS: + * a) All those of ClassicSoundDriver plus regarding timing accuracy. + * + * @author Maes + */ +public class SuperDoomSoundDriver extends AbstractSoundDriver { + + private static final Logger LOGGER = Loggers.getLogger(SuperDoomSoundDriver.class.getName()); + + protected final Semaphore produce; + + protected final Semaphore consume; + + protected final Semaphore update_mixer; + + protected int chunk = 0; + + //protected FileOutputStream fos; + //protected DataOutputStream dao; + // The one and only line + protected SourceDataLine line = null; + + protected HashMap cachedSounds + = new HashMap<>(); + + protected final Timer MIXTIMER; + + public SuperDoomSoundDriver(DoomMain DM, int numChannels) { + super(DM, numChannels); + channels = new boolean[numChannels]; + produce = new Semaphore(1); + consume = new Semaphore(1); + update_mixer = new Semaphore(1); + produce.drainPermits(); + update_mixer.drainPermits(); + this.MIXSRV = new MixServer(numChannels); + MIXTIMER = new Timer(true); + // Sound tics every 1/35th of a second. Grossly + // inaccurate under Windows though, will get rounded + // down to the closest multiple of 15 or 16 ms. + MIXTIMER.schedule(new SoundTimer(), 0, SOUND_PERIOD); + } + + /** These are still defined here to decouple them from the mixer's + * ones, however they serve more as placeholders/status indicators; + */ + protected volatile boolean[] channels; + + protected volatile boolean mixed = false; + + /** + * This function loops all active (internal) sound channels, retrieves a + * given number of samples from the raw sound data, modifies it according to + * the current (internal) channel parameters, mixes the per channel samples + * into the global mixbuffer, clamping it to the allowed range, and sets up + * everything for transferring the contents of the mixbuffer to the (two) + * hardware channels (left and right, that is). This function currently + * supports only 16bit. + */ + public void UpdateSound() { + // This is pretty much a dummy. + // The mixing thread goes on by itself, guaranteeing that it will + // carry out at least currently enqueued mixing messages, regardless + // of how badly the engine lags. + + } + + /** + * SFX API Note: this was called by S_Init. However, whatever they did in + * the old DPMS based DOS version, this were simply dummies in the Linux + * version. See soundserver initdata(). + */ + @Override + public void SetChannels(int numChannels) { + // Init internal lookups (raw data, mixing buffer, channels). + // This function sets up internal lookups used during + // the mixing process. + + int steptablemid = 128; + + // Okay, reset internal mixing channels to zero. + for (int i = 0; i < this.numChannels; i++) { + channels[i] = false; + } + + generateStepTable(steptablemid); + + generateVolumeLUT(); + } + + protected PlaybackServer SOUNDSRV; + protected final MixServer MIXSRV; + + protected Thread MIXTHREAD; + protected Thread SOUNDTHREAD; + + @Override + public boolean InitSound() { + + // Secure and configure sound device first. + LOGGER.log(Level.INFO, "I_InitSound"); + + // We only need a single data line. + // PCM, signed, 16-bit, stereo, 22025 KHz, 2048 bytes per "frame", + // maximum of 44100/2048 "fps" + AudioFormat format = new AudioFormat(SAMPLERATE, 16, 2, true, true); + + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + + if (AudioSystem.isLineSupported(info)) + try { + line = (SourceDataLine) AudioSystem.getSourceDataLine(format); + line.open(format, AUDIOLINE_BUFFER); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Could not play signed 16 data", e); + return false; + } + + if (line != null) { + LOGGER.log(Level.INFO, "configured audio device"); + line.start(); + } else { + LOGGER.log(Level.SEVERE, "could not configure audio device"); + return false; + } + + SOUNDSRV = new PlaybackServer(line); + SOUNDTHREAD = new Thread(SOUNDSRV); + SOUNDTHREAD.setDaemon(true); + SOUNDTHREAD.start(); + // Vroom! + MIXTHREAD = new Thread(MIXSRV); + MIXTHREAD.setDaemon(true); + MIXTHREAD.start(); + + // Initialize external data (all sounds) at start, keep static. + LOGGER.log(Level.INFO, "I_InitSound"); + + super.initSound8(); + + LOGGER.log(Level.INFO, "pre-cached all sound data"); + + // Finished initialization. + LOGGER.log(Level.INFO, "I_InitSound: sound module ready"); + + return true; + + } + + @Override + protected int addsfx(int sfxid, int volume, int step, int seperation) { + int i; + int rc = -1; + + int oldest = DM.gametic; + int oldestnum = 0; + int slot; + + int rightvol; + int leftvol; + + int broken = -1; + + // Chainsaw troubles. + // Play these sound effects only one at a time. + if (sfxid == sfxenum_t.sfx_sawup.ordinal() + || sfxid == sfxenum_t.sfx_sawidl.ordinal() + || sfxid == sfxenum_t.sfx_sawful.ordinal() + || sfxid == sfxenum_t.sfx_sawhit.ordinal() + || sfxid == sfxenum_t.sfx_stnmov.ordinal() + || sfxid == sfxenum_t.sfx_pistol.ordinal()) { + // Loop all channels, check. + for (i = 0; i < numChannels; i++) { + // Active, and using the same SFX? + if (channels[i] && (channelids[i] == sfxid)) { + // Reset. + + MixMessage m = new MixMessage(); + m.stop = true; + + // We are sure that iff, + // there will only be one. + broken = i; + break; + } + } + } + + // Loop all channels to find oldest SFX. + if (broken >= 0) { + i = broken; + oldestnum = broken; + } else { + for (i = 0; (i < numChannels) && channels[i]; i++) { + if (channelstart[i] < oldest) { + oldestnum = i; + } + } + } + + oldest = channelstart[oldestnum]; + + // Tales from the cryptic. + // If we found a channel, fine. + // If not, we simply overwrite the first one, 0. + // Probably only happens at startup. + if (i == numChannels) { + slot = oldestnum; + } else { + slot = i; + } + + MixMessage m = new MixMessage(); + + // Okay, in the less recent channel, + // we will handle the new SFX. + // Set pointer to raw data. + channels[slot] = true; + m.channel = slot; + m.data = S_sfx[sfxid].data; + + // MAES: if you don't zero-out the channel pointer here, it gets ugly + m.pointer = 0; + + // Set pointer to end of raw data. + m.end = lengths[sfxid]; + + // Reset current handle number, limited to 0..100. + if (handlenums == 0) // was !handlenums, so it's actually 1...100? + { + handlenums = 100; + } + + // Assign current handle number. + // Preserved so sounds could be stopped (unused). + // Maes: this should really be decreasing, otherwide handles + // should start at 0 and go towards 100. Just saying. + channelhandles[slot] = rc = handlenums--; + + // Set stepping??? + // Kinda getting the impression this is never used. + // MAES: you're wrong amigo. + m.step = step; + // ??? + m.remainder = 0; + // Should be gametic, I presume. + channelstart[slot] = DM.gametic; + + // Separation, that is, orientation/stereo. + // range is: 1 - 256 + seperation += 1; + + // Per left/right channel. + // x^2 seperation, + // adjust volume properly. + leftvol = volume - ((volume * seperation * seperation) >> 16); // /(256*256); + seperation = seperation - 257; + rightvol = volume - ((volume * seperation * seperation) >> 16); + + // Sanity check, clamp volume. + if (rightvol < 0 || rightvol > 127) { + DM.doomSystem.Error("rightvol out of bounds"); + } + + if (leftvol < 0 || leftvol > 127) { + DM.doomSystem.Error("leftvol out of bounds"); + } + + // Get the proper lookup table piece + // for this volume level??? + m.leftvol_lookup = vol_lookup[leftvol]; + m.rightvol_lookup = vol_lookup[rightvol]; + + // Preserve sound SFX id, + // e.g. for avoiding duplicates of chainsaw. + channelids[slot] = sfxid; + + if (D) { + LOGGER.log(Level.FINE, String.valueOf(channelStatus())); + } + if (D) { + LOGGER.log(Level.FINE, String.format( + "Playing sfxid %d handle %d length %d vol %d on channel %d", + sfxid, rc, S_sfx[sfxid].data.length, volume, slot)); + } + + MIXSRV.submitMixMessage(m); + + // You tell me. + return rc; + } + + @Override + public void ShutdownSound() { + + boolean done; + + // Unlock sound thread if it's waiting. + produce.release(); + update_mixer.release(); + + int i = 0; + do { + done = true; + for (i = 0; i < numChannels; i++) { + // If even one channel is playing, loop again. + done &= !channels[i]; + } + //System.out.println(done+" "+this.channelStatus()); + + } while (!done); + + this.line.flush(); + + SOUNDSRV.terminate = true; + MIXSRV.terminate = true; + produce.release(); + update_mixer.release(); + try { + SOUNDTHREAD.join(); + MIXTHREAD.join(); + } catch (InterruptedException e) { + // Well, I don't care. + } + //System.err.printf("3\n"); + line.close(); + //System.err.printf("4\n"); + + } + + protected class PlaybackServer + implements Runnable { + + public boolean terminate = false; + + public PlaybackServer(SourceDataLine line) { + this.auline = line; + } + + private SourceDataLine auline; + + private ArrayBlockingQueue audiochunks + = new ArrayBlockingQueue<>(BUFFER_CHUNKS * 2); + + public void addChunk(AudioChunk chunk) { + audiochunks.offer(chunk); + } + + public volatile int currstate = 0; + + public void run() { + + while (!terminate) { + + // while (timing[mixstate]<=mytime){ + // Try acquiring a produce permit before going on. + try { + //System.err.print("Waiting for a permit..."); + produce.acquire(); + //System.err.print("...got it\n"); + } catch (InterruptedException e) { + // Well, ouch. + LOGGER.log(Level.SEVERE, "PlaybackServer run failure", e); + } + + int chunks = 0; + + // System.err.printf("Audio queue has %d chunks\n",audiochunks.size()); + // Play back only at most a given number of chunks once you reach + // this spot. + int atMost = Math.min(ISoundDriver.BUFFER_CHUNKS, audiochunks.size()); + + while (atMost-- > 0) { + + AudioChunk chunk = null; + try { + chunk = audiochunks.take(); + } catch (InterruptedException e1) { + // Should not block + } + // Play back all chunks present in a buffer ASAP + auline.write(chunk.buffer, 0, MIXBUFFERSIZE); + chunks++; + // No matter what, give the chunk back! + chunk.free = true; + audiochunkpool.checkIn(chunk); + } + + //System.err.println(">>>>>>>>>>>>>>>>> CHUNKS " +chunks); + // Signal that we consumed a whole buffer and we are ready for + // another one. + consume.release(); + } + } + } + + /** A single channel does carry a lot of crap, figuratively speaking. + * Instead of making updates to ALL channel parameters, it makes more + * sense having a "mixing queue" with instructions that tell the + * mixer routine to do so-and-so with a certain channel. The mixer + * will then "empty" the queue when it has completed a complete servicing + * of all messages and mapped them to its internal status. + * + */ + protected class MixMessage { + + /** If this is set, the mixer considers that channel "muted" */ + public boolean stop; + + /** This signals an update of a currently active channel. + * Therefore pointer, remainder and data should remain untouched. + * However volume and step of a particular channel can change. + */ + public boolean update; + + public int remainder; + public int end; + public int channel; + public byte[] data; + public int step; + public int stepremainder; + public int[] leftvol_lookup; + public int[] rightvol_lookup; + + public int pointer; + + } + + /** Mixing thread. Mixing and submission must still go on even if + * the engine lags behind due to excessive CPU load. + * + * @author Maes + * + */ + protected class MixServer + implements Runnable { + + private final ArrayBlockingQueue mixmessages; + + /** + * MAES: we'll have to use this for actual pointing. channels[] holds just + * the data. + */ + protected int[] p_channels; + + /** + * The second one is supposed to point at "the end", so I'll make it an int. + */ + protected int[] channelsend; + + private final byte[][] channels; + /** The channel step amount... */ + protected final int[] channelstep; + + /** ... and a 0.16 bit remainder of last step. */ + protected final int[] channelstepremainder; + + protected final int[][] channelrightvol_lookup; + protected final int[][] channelleftvol_lookup; + + private volatile boolean update = false; + + public MixServer(int numChannels) { + // We can put only so many messages "on hold" + mixmessages = new ArrayBlockingQueue<>(35 * numChannels); + this.p_channels = new int[numChannels]; + this.channels = new byte[numChannels][]; + this.channelstepremainder = new int[numChannels]; + this.channelsend = new int[numChannels]; + this.channelstep = new int[numChannels]; + this.channelleftvol_lookup = new int[numChannels][]; + this.channelrightvol_lookup = new int[numChannels][]; + } + + /** Adds a channel mixing message to the queue */ + public void submitMixMessage(MixMessage m) { + try { + this.mixmessages.add(m); + } catch (IllegalStateException e) { + // Queue full. Force clear (VERY rare). + mixmessages.clear(); + mixmessages.add(m); + } + } + + public boolean terminate = false; + + @Override + public void run() { + + // Mix current sound data. + // Data, from raw sound, for right and left. + int sample = 0; + int dl; + int dr; + + // Pointers in global mixbuffer, left, right, end. + // Maes: those were explicitly signed short pointers... + int leftout; + int rightout; + + // Step in mixbuffer, left and right, thus two. + final int step = 4; + + // Mixing channel index. + int chan; + + // Determine end, for left channel only + // (right channel is implicit). + // MAES: this implies that the buffer will only mix + // that many samples at a time, and that the size is just right. + // Thus, it must be flushed (p_mixbuffer=0) before reusing it. + final int leftend = SAMPLECOUNT * step; + + // Mix the next chunk, regardless of what the rest of the game is doing. + while (!terminate) { + + // POINTERS to Left and right channel + // which are in global mixbuffer, alternating. + leftout = 0; + rightout = 2; + + // Wait on interrupt semaphore anyway before draining queue. + // This allows continuing mixing even if the main game loop + // is stalled. This will result in continuous sounds, + // rather than choppy interruptions. + try { + //System.err.print("Waiting on semaphore..."); + update_mixer.acquire(); + //System.err.print("...broke free\n"); + } catch (InterruptedException e) { + // Nothing to do. Suck it down. + } + + // Get current number of element in queue. + // At worse, there will be none. + int messages = mixmessages.size(); + + // Drain the queue, applying changes to currently + // looping channels, if applicable. This may result in new channels, + // older ones being stopped, or current ones being altered. Changes + // will be applied with priority either way. + if (messages > 0) { + drainAndApply(messages); + } + + // This may have changed in the mean. + mixed = activeChannels(); + + if (mixed) {// Avoid mixing entirely if no active channel. + + // Get audio chunk NOW + gunk = audiochunkpool.checkOut(); + // Ha ha you're ass is mine! + gunk.free = false; + mixbuffer = gunk.buffer; + + while (leftout < leftend) { + // Reset left/right value. + dl = 0; + dr = 0; + + // Love thy L2 chache - made this a loop. + // Now more channels could be set at compile time + // as well. Thus loop those channels. + for (chan = 0; chan < numChannels; chan++) { + + // Check channel, if active. + // MAES: this means that we must point to raw data here. + if (channels[chan] != null) { + int channel_pointer = p_channels[chan]; + + // Get the raw data from the channel. + // Maes: this is supposed to be an 8-bit unsigned value. + sample = 0x00FF & channels[chan][channel_pointer]; + + // Add left and right part for this channel (sound) + // to the current data. Adjust volume accordingly. + // Q: could this be optimized by converting samples to 16-bit + // at load time, while also allowing for stereo samples? + // A: Only for the stereo part. You would still look a lookup + // for the CURRENT volume level. + dl += channelleftvol_lookup[chan][sample]; + dr += channelrightvol_lookup[chan][sample]; + + // This should increment the index inside a channel, but is + // expressed in 16.16 fixed point arithmetic. + channelstepremainder[chan] += channelstep[chan]; + + // The actual channel pointer is increased here. + // The above trickery allows playing back different pitches. + // The shifting retains only the integer part. + channel_pointer += channelstepremainder[chan] >> 16; + + // This limits it to the "decimal" part in order to + // avoid undue accumulation. + channelstepremainder[chan] &= 0xFFFF; + + // Check whether we are done. Also to avoid overflows. + if (channel_pointer >= channelsend[chan]) { + // Reset pointer for a channel. + if (D) { + LOGGER.log(Level.FINE, String.format( + "Channel %d handle %d pointer %d thus done, stopping", + chan, channelhandles[chan], + channel_pointer)); + } + channels[chan] = null; + + // Communicate back to driver. + SuperDoomSoundDriver.this.channels[chan] = false; + channel_pointer = 0; + } + + // Write pointer back, so we know where a certain channel + // is the next time UpdateSounds is called. + p_channels[chan] = channel_pointer; + } + + } // for all channels. + + // MAES: at this point, the actual values for a single sample + // (YIKES!) are in d1 and d2. We must use the leftout/rightout + // pointers to write them back into the mixbuffer. + // Clamp to range. Left hardware channel. + // Remnant of 8-bit mixing code? That must have raped ears + // and made them bleed. + // if (dl > 127) *leftout = 127; + // else if (dl < -128) *leftout = -128; + // else *leftout = dl; + if (dl > 0x7fff) { + dl = 0x7fff; + } else if (dl < -0x8000) { + dl = -0x8000; + } + + // Write left channel + mixbuffer[leftout] = (byte) ((dl & 0xFF00) >>> 8); + mixbuffer[leftout + 1] = (byte) (dl & 0x00FF); + + // Same for right hardware channel. + if (dr > 0x7fff) { + dr = 0x7fff; + } else if (dr < -0x8000) { + dr = -0x8000; + } + + // Write right channel. + mixbuffer[rightout] = (byte) ((dr & 0xFF00) >>> 8); + mixbuffer[rightout + 1] = (byte) (dr & 0x00FF); + + // Increment current pointers in mixbuffer. + leftout += step; + rightout += step; + } // End leftend/leftout while + + // for (chan = 0; chan < numChannels; chan++) { + // if (channels[chan]!=null){ + // System.err.printf("Channel %d pointer %d\n",chan,this.p_channels[chan]); + // } + // } + } // if-mixed + + // After an entire buffer has been mixed, we can apply any updates. + // This includes silent updates. + submitSound(); + + } // terminate loop + } + + private AudioChunk gunk; + + private final void submitSound() { + // It's possible to stay entirely silent and give the audio + // queue a chance to get drained. without sending any data. + // Saves BW and CPU cycles. + if (mixed) { + silence = 0; + + // System.err.printf("Submitted sound chunk %d to buffer %d \n",chunk,mixstate); + // Copy the currently mixed chunk into its position inside the + // master buffer. + // System.arraycopy(mixbuffer, 0, gunk.buffer, 0, MIXBUFFERSIZE); + SOUNDSRV.addChunk(gunk); + + // System.err.println(chunk++); + chunk++; + // System.err.println(chunk); + + if (consume.tryAcquire()) { + produce.release(); + } + + } else { + silence++; + // MAES: attempt to fix lingering noise error + if (silence > ISoundDriver.BUFFER_CHUNKS) { + line.flush(); + silence = 0; + } + } + } + + /** Drains message queue and applies to individual channels. + * More recently enqueued messages will trump older ones. This method + * only changes the STATUS of channels, and actual message submissions + * can occur at most every sound frame. + * + * @param messages + */ + private void drainAndApply(int messages) { + MixMessage m; + for (int i = 0; i < messages; i++) { + // There should be no problems, in theory. + m = this.mixmessages.remove(); + if (m.stop) { + stopChannel(m.channel); + } else if (m.update) { + updateChannel(m); + } else { + insertChannel(m); + } + } + } + + private final void stopChannel(int channel) { + //System.err.printf("Stopping channel %d\n",channel); + this.channels[channel] = null; + this.p_channels[channel] = 0; + } + + private final void updateChannel(MixMessage m) { + //System.err.printf("Updating channel %d\n",m.channel); + this.channelleftvol_lookup[m.channel] = m.leftvol_lookup; + this.channelrightvol_lookup[m.channel] = m.rightvol_lookup; + this.channelstep[m.channel] = m.step; + this.channelsend[m.channel] = m.end; + } + + private final void insertChannel(MixMessage m) { + int ch = m.channel; + //System.err.printf("Inserting channel %d\n",ch); + this.channels[ch] = m.data; + this.p_channels[ch] = m.pointer; + this.channelsend[ch] = m.end; + this.channelstepremainder[ch] = m.remainder; + this.channelleftvol_lookup[ch] = m.leftvol_lookup; + this.channelrightvol_lookup[ch] = m.rightvol_lookup; + this.channelstep[ch] = m.step; + } + + private final boolean activeChannels() { + for (int chan = 0; chan < numChannels; chan++) { + if (channels[chan] != null) // SOME mixing has taken place. + { + return true; + } + } + + return false; + } + + public final boolean channelIsPlaying(int num) { + return (channels[num] != null); + } + + } + + @Override + public boolean SoundIsPlaying(int handle) { + + int c = getChannelFromHandle(handle); + return (c != -2 && channels[c]); + + } + + /** + * Internal use. + * + * @param handle + * @return the channel that has the handle, or -2 if none has it. + */ + protected int getChannelFromHandle(int handle) { + // Which channel has it? + for (int i = 0; i < numChannels; i++) { + if (channelhandles[i] == handle) { + return i; + } + } + + return BUSY_HANDLE; + } + + @Override + public void StopSound(int handle) { + // Which channel has it? + int hnd = getChannelFromHandle(handle); + if (hnd >= 0) { + + channels[hnd] = false; + + this.channelhandles[hnd] = IDLE_HANDLE; + + MixMessage m = new MixMessage(); + m.channel = hnd; + m.stop = true; + // We can only "ask" the mixer to stop at the next + //chunk. + MIXSRV.submitMixMessage(m); + } + } + + @Override + public void SubmitSound() { + + // Also a dummy. The mixing thread is in a better position to + // judge when sound should be submitted. + } + + private int silence = 0; + + @Override + public void UpdateSoundParams(int handle, int vol, int sep, int pitch) { + + int chan = this.getChannelFromHandle(handle); + // Per left/right channel. + // x^2 seperation, + // adjust volume properly. + int leftvol = vol - ((vol * sep * sep) >> 16); // /(256*256); + sep = sep - 257; + int rightvol = vol - ((vol * sep * sep) >> 16); + + // Sanity check, clamp volume. + if (rightvol < 0 || rightvol > 127) { + DM.doomSystem.Error("rightvol out of bounds"); + } + + if (leftvol < 0 || leftvol > 127) { + DM.doomSystem.Error("leftvol out of bounds"); + } + + MixMessage m = new MixMessage(); + + // We are updating a currently active channel + m.update = true; + m.channel = chan; + + // Get the proper lookup table piece + // for this volume level??? + m.leftvol_lookup = vol_lookup[leftvol]; + m.rightvol_lookup = vol_lookup[rightvol]; + + // Well, if you can get pitch to change too... + m.step = steptable[pitch]; + + // Oddly enough, we could be picking a different channel here? :-S + m.end = lengths[channelids[chan]]; + + MIXSRV.submitMixMessage(m); + } + + protected StringBuilder sb = new StringBuilder(); + + public String channelStatus() { + sb.setLength(0); + for (int i = 0; i < numChannels; i++) { + if (MIXSRV.channelIsPlaying(i)) { + sb.append(i); + } else { + sb.append('-'); + } + } + + return sb.toString(); + + } + + // Schedule this to release the sound thread at regular intervals + // so that it doesn't outrun the audioline's buffer and game updates. + protected class SoundTimer extends TimerTask { + + public void run() { + update_mixer.release(); + } + } + + protected final AudioChunk SILENT_CHUNK = new AudioChunk(); + + protected final AudioChunkPool audiochunkpool = new AudioChunkPool(); +} \ No newline at end of file diff --git a/doom/src/s/VolumeScalingReceiver.java b/doom/src/s/VolumeScalingReceiver.java new file mode 100644 index 0000000..d5408c0 --- /dev/null +++ b/doom/src/s/VolumeScalingReceiver.java @@ -0,0 +1,206 @@ +package s; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; +import javax.sound.midi.Sequencer; +import javax.sound.midi.ShortMessage; +import javax.sound.midi.Synthesizer; +import mochadoom.Loggers; + +/** A {@link Receiver} that scales channel volumes. + * + * Works by recognising channel volume change events and scaling the new volume + * by the global music volume setting before forwarding the event to the + * synthesizer. + * + * @author finnw + * + */ +public class VolumeScalingReceiver implements Receiver { + + private static final Logger LOGGER = Loggers.getLogger(VolumeScalingReceiver.class.getName()); + + /** Guess which is the "best" available synthesizer & create a + * VolumeScalingReceiver that forwards to it. + * + * @return a VolumeScalingReceiver connected to a semi- + * intelligently-chosen synthesizer. + * + */ + public static VolumeScalingReceiver getInstance() { + try { + List dInfos + = new ArrayList<>(Arrays.asList(MidiSystem.getMidiDeviceInfo())); + for (Iterator it = dInfos.iterator(); + it.hasNext();) { + MidiDevice.Info dInfo = it.next(); + MidiDevice dev = MidiSystem.getMidiDevice(dInfo); + if (dev.getMaxReceivers() == 0) { + // We cannot use input-only devices + it.remove(); + } + } + if (dInfos.isEmpty()) { + return null; + } + Collections.sort(dInfos, new MidiDeviceComparator()); + MidiDevice.Info dInfo = dInfos.get(0); + MidiDevice dev = MidiSystem.getMidiDevice((MidiDevice.Info) dInfo); + dev.open(); + return new VolumeScalingReceiver(dev.getReceiver()); + } catch (MidiUnavailableException ex) { + return null; + } + } + + /** Create a VolumeScalingReceiver connected to a specific receiver. */ + public VolumeScalingReceiver(Receiver delegate) { + this.channelVolume = new int[16]; + this.synthReceiver = delegate; + Arrays.fill(this.channelVolume, 127); + } + + @Override + public void close() { + synthReceiver.close(); + } + + /** Set the scaling factor to be applied to all channel volumes */ + public synchronized void setGlobalVolume(float globalVolume) { + this.globalVolume = globalVolume; + for (int chan = 0; chan < 16; ++chan) { + int volScaled = (int) Math.round(channelVolume[chan] * globalVolume); + sendVolumeChange(chan, volScaled, -1); + } + } + + /** A collection of kludges to pick a synthesizer until cvars are implemented */ + static class MidiDeviceComparator implements Comparator { + + @Override + public int compare(MidiDevice.Info o1, MidiDevice.Info o2) { + float score1 = score(o1), score2 = score(o2); + if (score1 < score2) { + return 1; + } else if (score1 > score2) { + return -1; + } else { + return 0; + } + } + + /** Guess how suitable a MidiDevice is for music output. */ + private float score(MidiDevice.Info info) { + String lcName = info.getName().toLowerCase(Locale.ENGLISH); + float result = 0f; + try { + MidiDevice dev = MidiSystem.getMidiDevice(info); + dev.open(); + try { + if (dev instanceof Sequencer) { + // The sequencer cannot be the same device as the synthesizer - that would create an infinite loop. + return Float.NEGATIVE_INFINITY; + } else if (lcName.contains("mapper")) { + // "Midi Mapper" is ideal, because the user can select the default output device in the control panel + result += 100; + } else { + if (dev instanceof Synthesizer) { + // A synthesizer is usually better than a sequencer or USB MIDI port + result += 50; + if (lcName.contains("java")) { + // "Java Sound Synthesizer" often has a low sample rate or no default soundbank; Prefer another software synth + if (((Synthesizer) dev).getDefaultSoundbank() != null) { + result -= 10; + } else { + // Probably won't be audible + result -= 500; + } + } + if (lcName.contains("microsoft")) { + // "Microsoft GS Wavetable Synth" is notoriously unpopular, but sometimes it's the only one + // with a decent sample rate. + result -= 7; + } + } + } + return result; + } finally { + dev.close(); + } + } catch (MidiUnavailableException ex) { + // Cannot use this one + return Float.NEGATIVE_INFINITY; + } + } + } + + /** Forward a message to the synthesizer. + * + * If message is a volume change message, the volume is + * first multiplied by the global volume. Otherwise, the message is + * passed unmodified to the synthesizer. + */ + @Override + public synchronized void send(MidiMessage message, long timeStamp) { + int chan = getVolumeChangeChannel(message); + if (chan < 0) { + synthReceiver.send(message, timeStamp); + } else { + int newVolUnscaled = message.getMessage()[2]; + channelVolume[chan] = newVolUnscaled; + int newVolScaled = (int) Math.round(newVolUnscaled * globalVolume); + sendVolumeChange(chan, newVolScaled, timeStamp); + } + } + + /** Send a volume update to a specific channel. + * + * This is used for both local & global volume changes. + */ + private void sendVolumeChange(int chan, int newVolScaled, long timeStamp) { + newVolScaled = Math.max(0, Math.min(newVolScaled, 127)); + ShortMessage message = new ShortMessage(); + try { + message.setMessage(0xb0 | (chan & 15), 7, newVolScaled); + synthReceiver.send(message, timeStamp); + } catch (InvalidMidiDataException ex) { + LOGGER.log(Level.SEVERE, "sendVolumeChange failure", ex); + } + } + + /** Determine if the given message is a channel volume change. + * + * @return Channel number for which volume is being changed, or -1 if not a + * channel volume change command. + */ + private int getVolumeChangeChannel(MidiMessage message) { + if (message.getLength() >= 3) { + byte[] mBytes = message.getMessage(); + if ((byte) 0xb0 <= mBytes[0] && mBytes[0] < (byte) 0xc0 + && mBytes[1] == 7) { + return mBytes[0] & 15; + } + } + return -1; + } + + private final int[] channelVolume; + + private float globalVolume; + + private final Receiver synthReceiver; + +} \ No newline at end of file diff --git a/doom/src/s/channel_t.java b/doom/src/s/channel_t.java new file mode 100644 index 0000000..7df6c1d --- /dev/null +++ b/doom/src/s/channel_t.java @@ -0,0 +1,30 @@ +package s; + +import data.sfxinfo_t; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.SourceDataLine; +import p.mobj_t; + +public class channel_t { + + public channel_t() { + sfxinfo = new sfxinfo_t(); + } + + /** Currently playing sound. If null, then it's free */ + DoomSound currentSound = null; + + sfxinfo_t sfxinfo; + + // origin of sound (usually a mobj_t). + mobj_t origin; + + // handle of the sound being played + int handle; + + AudioFormat format; + + public int sfxVolume; + + SourceDataLine auline = null; +} \ No newline at end of file diff --git a/doom/src/s/degenmobj_t.java b/doom/src/s/degenmobj_t.java new file mode 100644 index 0000000..b632c90 --- /dev/null +++ b/doom/src/s/degenmobj_t.java @@ -0,0 +1,35 @@ +package s; + +public final class degenmobj_t + implements ISoundOrigin { + + private final int x, y, z; + + public degenmobj_t(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public degenmobj_t(int x, int y) { + this.x = x; + this.y = y; + this.z = 0; + } + + @Override + public final int getX() { + return x; + } + + @Override + public final int getY() { + return y; + } + + @Override + public final int getZ() { + return z; + } + +} \ No newline at end of file diff --git a/doom/src/savegame/IDoomSaveGame.java b/doom/src/savegame/IDoomSaveGame.java new file mode 100644 index 0000000..506df44 --- /dev/null +++ b/doom/src/savegame/IDoomSaveGame.java @@ -0,0 +1,18 @@ +package savegame; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import p.ThinkerList; + +public interface IDoomSaveGame { + + void setThinkerList(ThinkerList li); + + boolean doLoad(DataInputStream f); + + IDoomSaveGameHeader getHeader(); + + void setHeader(IDoomSaveGameHeader header); + + boolean doSave(DataOutputStream f); +} \ No newline at end of file diff --git a/doom/src/savegame/IDoomSaveGameHeader.java b/doom/src/savegame/IDoomSaveGameHeader.java new file mode 100644 index 0000000..724428a --- /dev/null +++ b/doom/src/savegame/IDoomSaveGameHeader.java @@ -0,0 +1,48 @@ +package savegame; + +import defines.skill_t; + +/** A Save Game Header should be able to be loaded quickly and return + * some basic info about it (name, version, game time, etc.) in an unified + * manner, no matter what actual format you use for saving. + * + * @author admin + * + */ +public interface IDoomSaveGameHeader { + + String getName(); + + void setName(String name); + + skill_t getGameskill(); + + void setGameskill(skill_t gameskill); + + String getVersion(); + + void setVersion(String vcheck); + + int getGameepisode(); + + void setGameepisode(int gameepisode); + + boolean isProperend(); + + void setWrongversion(boolean wrongversion); + + boolean isWrongversion(); + + void setLeveltime(int leveltime); + + int getLeveltime(); + + void setPlayeringame(boolean[] playeringame); + + boolean[] getPlayeringame(); + + void setGamemap(int gamemap); + + int getGamemap(); + +} \ No newline at end of file diff --git a/doom/src/savegame/ILoadSaveGame.java b/doom/src/savegame/ILoadSaveGame.java new file mode 100644 index 0000000..0e06499 --- /dev/null +++ b/doom/src/savegame/ILoadSaveGame.java @@ -0,0 +1,12 @@ +package savegame; + +import p.ThinkerList; + +public interface ILoadSaveGame { + + void setThinkerList(ThinkerList li); + + void doSave(ThinkerList li); + + void doLoad(ThinkerList li); +} \ No newline at end of file diff --git a/doom/src/savegame/VanillaDSG.java b/doom/src/savegame/VanillaDSG.java new file mode 100644 index 0000000..5d3f5b3 --- /dev/null +++ b/doom/src/savegame/VanillaDSG.java @@ -0,0 +1,879 @@ +package savegame; + +import static data.Limits.MAXCEILINGS; +import static data.Limits.MAXPLAYERS; +import data.info; +import doom.DoomMain; +import doom.SourceCode.P_SaveG; +import static doom.SourceCode.P_SaveG.P_ArchivePlayers; +import static doom.SourceCode.P_SaveG.P_ArchiveSpecials; +import static doom.SourceCode.P_SaveG.P_ArchiveThinkers; +import static doom.SourceCode.P_SaveG.P_ArchiveWorld; +import static doom.SourceCode.P_SaveG.P_UnArchivePlayers; +import static doom.SourceCode.P_SaveG.P_UnArchiveSpecials; +import static doom.SourceCode.P_SaveG.P_UnArchiveThinkers; +import static doom.SourceCode.P_SaveG.P_UnArchiveWorld; +import doom.player_t; +import doom.thinker_t; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.Settings; +import mochadoom.Engine; +import mochadoom.Loggers; +import p.Actions.ActionsLights.glow_t; +import p.Actions.ActionsLights.lightflash_t; +import static p.ActiveStates.NOP; +import static p.ActiveStates.P_MobjThinker; +import static p.ActiveStates.T_Glow; +import static p.ActiveStates.T_LightFlash; +import static p.ActiveStates.T_MoveCeiling; +import static p.ActiveStates.T_MoveFloor; +import static p.ActiveStates.T_PlatRaise; +import static p.ActiveStates.T_StrobeFlash; +import static p.ActiveStates.T_VerticalDoor; +import p.ThinkerList; +import p.ceiling_t; +import p.floormove_t; +import p.mobj_t; +import p.plat_t; +import p.strobe_t; +import p.vldoor_t; +import rr.line_t; +import rr.sector_t; +import rr.side_t; +import utils.C2JUtils; + +public class VanillaDSG implements IDoomSaveGame { + + private static final Logger LOGGER = Loggers.getLogger(VanillaDSG.class.getName()); + + VanillaDSGHeader header; + final DoomMain DOOM; + + public VanillaDSG(DoomMain DOOM) { + this.DOOM = DOOM; + } + + @Override + public void setThinkerList(ThinkerList li) { + // TODO Auto-generated method stub + + } + + @Override + public IDoomSaveGameHeader getHeader() { + return header; + } + + @Override + public void setHeader(IDoomSaveGameHeader header) { + this.header = (VanillaDSGHeader) header; + + } + + private DataInputStream f; + private DataOutputStream fo; + private int maxsize; + + @Override + public boolean doLoad(DataInputStream f) { + try { + this.f = f; + maxsize = f.available(); + LOGGER.log(Level.FINE, String.format("Max size %d", maxsize)); + this.header = new VanillaDSGHeader(); + header.read(f); + UnArchivePlayers(); + UnArchiveWorld(); + UnArchiveThinkers(); + UnArchiveSpecials(); + byte terminator = f.readByte(); + return terminator == 0x1D; + } catch (IOException e) { + LOGGER.log(Level.WARNING, e, () + -> String.format("Error while loading savegame! Cause: %s", e.getMessage())); + return false; // Needed to shut up compiler. + } + + } + + /** + * P_UnArchivePlayers + * + * @throws IOException + */ + @P_SaveG.C(P_UnArchivePlayers) + protected void UnArchivePlayers() throws IOException { + int i; + int j; + + for (i = 0; i < MAXPLAYERS; i++) { + // Multiplayer savegames are different! + if (!DOOM.playeringame[i]) { + continue; + } + PADSAVEP(f, maxsize); // this will move us on the 52th byte, instead of 50th. + DOOM.players[i].read(f); + + //memcpy (&players[i],save_p, sizeof(player_t)); + //save_p += sizeof(player_t); + // will be set when unarc thinker + DOOM.players[i].mo = null; + DOOM.players[i].message = null; + DOOM.players[i].attacker = null; + + for (j = 0; j < player_t.NUMPSPRITES; j++) { + if (C2JUtils.eval(DOOM.players[i].psprites[j].state)) { + // MAES HACK to accomoadate state_t type punning a-posteriori + DOOM.players[i].psprites[j].state + = info.states[DOOM.players[i].psprites[j].readstate]; + } + } + } + } + + /** + * P_ArchivePlayers + * + * @throws IOException + */ + @P_SaveG.C(P_ArchivePlayers) + protected void ArchivePlayers() throws IOException { + for (int i = 0; i < MAXPLAYERS; i++) { + // Multiplayer savegames are different! + if (!DOOM.playeringame[i]) { + continue; + } + + PADSAVEP(fo); // this will move us on the 52th byte, instead of 50th. + + // State will have to be serialized when saving. + DOOM.players[i].write(fo); + + //System.out.printf("Player %d has mobj hashcode %d",(1+i),DS.players[i].mo.hashCode()); + } + } + + // + //P_ArchiveWorld + // + @P_SaveG.C(P_ArchiveWorld) + protected void ArchiveWorld() throws IOException { + int i; + int j; + sector_t sec; + line_t li; + side_t si; + + // do sectors (allocate 14 bytes per sector) + ByteBuffer buffer = ByteBuffer.allocate(DOOM.levelLoader.numsectors * 14); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + deAdaptSectors(); + for (i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // MAES: sectors are actually carefully + // marshalled, so we don't just read/write + // their entire memory footprint to disk. + sec.pack(buffer); + } + + adaptSectors(); + fo.write(buffer.array(), 0, buffer.position()); + + // do lines + // Allocate for the worst-case scenario (6+20 per line) + buffer = ByteBuffer.allocate(DOOM.levelLoader.numlines * (6 + 20)); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + + //final side_t test1=new side_t(0x11111111,0x11111111,(short) 0x1111,(short)0x1111,(short)0x1111,null); + //final side_t test2=new side_t(0x22222222,0x22222222,(short) 0x2222,(short)0x2222,(short)0x2222,null); + for (i = 0; i < DOOM.levelLoader.numlines; i++) { + li = DOOM.levelLoader.lines[i]; + li.pack(buffer); + + for (j = 0; j < 2; j++) { + if (li.sidenum[j] == line_t.NO_INDEX) { + continue; + } + si = DOOM.levelLoader.sides[li.sidenum[j]]; + si.pack(buffer); + //if (j==0) test1.pack(buffer); + //else test2.pack(buffer); + + } + } + + int write = buffer.position(); + fo.write(buffer.array(), 0, write); + } + + // + //P_UnArchiveWorld + // + @P_SaveG.C(P_UnArchiveWorld) + protected final void UnArchiveWorld() throws IOException { + int i; + int j; + sector_t sec; + line_t li; + side_t si; + // short get; + //get = (short *)save_p; + + //List sectors=new ArrayList(); + // do sectors + for (i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // MAES: sectors were actually carefully + // unmarshalled, so we don't just read/write + // their entire memory footprint to disk. + sec.read(f); + sec.specialdata = null; + sec.soundtarget = null; + } + adaptSectors(); + // do lines + for (i = 0; i < DOOM.levelLoader.numlines; i++) { + li = DOOM.levelLoader.lines[i]; + // MAES: something similar occurs with lines, too. + li.read(f); + //System.out.println("Line "+i+": "+li); + //System.out.print(i+ " {"); + for (j = 0; j < 2; j++) { + // System.out.print(li.sidenum[j]); + // if (j<2) System.out.print(","); + // System.out.printf("Skipped sidenum %d for line %d\n",j,i); + if (li.sidenum[j] == line_t.NO_INDEX) { + // System.out.printf("Skipped sidenum %d for line %d\n",j,i); + continue; + } + // Similarly, sides also get a careful unmarshalling even + // in vanilla. No "dumb" block reads here. + si = DOOM.levelLoader.sides[li.sidenum[j]]; + si.read(f); + + } + //System.out.printf("Position at end of WORLD: %d\n",f.getFilePointer()); + } + + } + + /** + * Convert loaded sectors from vanilla savegames into the internal, + * continuous index progression, by intercepting breaks corresponding to markers. + */ + protected void adaptSectors() { + sector_t sec; + switch (DOOM.getGameMode()) { + case registered: + case shareware: + for (int i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // Between the F1_START and F1_END mark (in vanilla) + if (sec.floorpic <= 54) { + sec.floorpic -= 1; + } else { + // Between the F2_START and F2_END mark (in vanilla) + sec.floorpic -= 3; + } + if (sec.ceilingpic <= 54) { + sec.ceilingpic -= 1; + } else { + // Between the F2_START and F2_END mark (in vanilla) + sec.ceilingpic -= 3; + } + + } + break; + case commercial: + case pack_plut: + case pack_tnt: + for (int i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // Between the F1_START and F1_END mark (in vanilla) + if (sec.floorpic <= 54) { + sec.floorpic -= 1; + } else if (sec.floorpic <= 99) { + // Between the F2_START and F2_END mark (in vanilla) + sec.floorpic -= 3; + } else { + sec.floorpic -= 5; + } + + if (sec.ceilingpic <= 54) { + sec.ceilingpic -= 1; + } else if (sec.ceilingpic <= 99) { + // Between the F2_START and F2_END mark (in vanilla) + sec.ceilingpic -= 3; + } else { + sec.ceilingpic -= 5; + } + + } + default: + break; + } + } + + /** + * De-convert sectors from an absolute to a vanilla-like index + * progression, by adding proper skips + */ + protected void deAdaptSectors() { + sector_t sec; + switch (DOOM.getGameMode()) { + case registered: + case shareware: + for (int i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // Between the F1_START and F1_END mark (in vanilla) + if (sec.floorpic < 54) { + sec.floorpic += 1; + } else { + // Between the F2_START and F2_END mark (in vanilla) + sec.floorpic += 3; + } + if (sec.ceilingpic < 54) { + sec.ceilingpic += 1; + } else { + // Between the F2_START and F2_END mark (in vanilla) + sec.ceilingpic += 3; + } + + } + break; + case commercial: + case pack_plut: + case pack_tnt: + for (int i = 0; i < DOOM.levelLoader.numsectors; i++) { + sec = DOOM.levelLoader.sectors[i]; + // Between the F1_START and F1_END mark (in vanilla) + if (sec.floorpic < 54) { + sec.floorpic += 1; + } else if (sec.floorpic < 99) { + // Between the F2_START and F2_END mark (in vanilla) + sec.floorpic += 3; + } else { + sec.floorpic += 5; + } + + if (sec.ceilingpic < 54) { + sec.ceilingpic += 1; + } else if (sec.ceilingpic < 99) { + // Between the F2_START and F2_END mark (in vanilla) + sec.ceilingpic += 3; + } else { + sec.ceilingpic += 5; + } + + } + default: + break; + } + } + + // + //Thinkers + // + protected enum thinkerclass_t { + tc_end, + tc_mobj; + } + + List TL = new ArrayList<>(); + + // + //P_ArchiveThinkers + // + @P_SaveG.C(P_ArchiveThinkers) + protected void ArchiveThinkers() throws IOException { + thinker_t th; + mobj_t mobj; + + // save off the current thinkers + for (th = DOOM.actions.getThinkerCap().next; th != DOOM.actions.getThinkerCap(); th = th.next) { + if (th.thinkerFunction == P_MobjThinker) { + // Indicate valid thinker + fo.writeByte(thinkerclass_t.tc_mobj.ordinal()); + // Pad... + PADSAVEP(fo); + mobj = (mobj_t) th; + mobj.write(fo); + + // MAES: state is explicit in state.id + // save_p += sizeof(*mobj); + // mobj->state = (state_t *)(mobj->state - states); + // MAES: player is automatically generated at runtime and handled by the writer. + //if (mobj->player) + //mobj->player = (player_t *)((mobj->player-players) + 1); + } + + // I_Error ("P_ArchiveThinkers: Unknown thinker function"); + } + + // add a terminating marker + fo.writeByte(thinkerclass_t.tc_end.ordinal()); + + } + + // + //P_UnArchiveThinkers + // + @P_SaveG.C(P_UnArchiveThinkers) + protected void UnArchiveThinkers() throws IOException { + thinkerclass_t tclass; // was "byte", therefore unsigned + thinker_t currentthinker; + thinker_t next; + mobj_t mobj; + int id = 0; + + // remove all the current thinkers + currentthinker = DOOM.actions.getThinkerCap().next; + while (currentthinker != null && currentthinker != DOOM.actions.getThinkerCap()) { + next = currentthinker.next; + + if (currentthinker.thinkerFunction == P_MobjThinker) { + DOOM.actions.RemoveMobj((mobj_t) currentthinker); + }// else { + //currentthinker.next.prev=currentthinker.prev; + //currentthinker.prev.next=currentthinker.next; + //currentthinker = null; + //} + + currentthinker = next; + } + + DOOM.actions.InitThinkers(); + + // read in saved thinkers + boolean end = false; + while (!end) { + int tmp = f.readUnsignedByte(); + tclass = thinkerclass_t.values()[tmp]; + switch (tclass) { + case tc_end: + // That's how we know when to stop. + end = true; + break; // end of list + + case tc_mobj: + PADSAVEP(f, maxsize); + mobj = mobj_t.createOn(DOOM); + mobj.read(f); + mobj.id = ++id; + TL.add(mobj); + mobj.mobj_state = info.states[mobj.stateid]; + mobj.target = null; + if (mobj.playerid != 0) { + mobj.player = DOOM.players[mobj.playerid - 1]; + mobj.player.mo = mobj; + + } + DOOM.levelLoader.SetThingPosition(mobj); + mobj.info = info.mobjinfo[mobj.type.ordinal()]; + mobj.floorz = mobj.subsector.sector.floorheight; + mobj.ceilingz = mobj.subsector.sector.ceilingheight; + mobj.thinkerFunction = P_MobjThinker; + DOOM.actions.AddThinker(mobj); + break; + + default: + DOOM.doomSystem.Error("Unknown tclass %d in savegame", tclass); + } + } + + if (Engine.getConfig().equals(Settings.reconstruct_savegame_pointers, Boolean.TRUE)) { + reconstructPointers(); + rewirePointers(); + } + } + + final HashMap pointindex = new HashMap<>(); + + /** + * Allows reconstructing infighting targets from stored pointers/indices. + * Works even with vanilla savegames as long as whatever it is that you + * store is unique. A good choice would be progressive indices or hash values. + * + */ + protected void reconstructPointers() { + + int player = 0; + + for (mobj_t th : TL) { + + if (th.player != null) { + player = th.id; + // Player found, so that's our first key. + pointindex.put(th.player.p_mobj, th); + } + } + + if (player == 0) { + LOGGER.log(Level.WARNING, + "Player not found, cannot reconstruct pointers!"); + return; + } + + int curr; // next or prev index + + // We start from the player's index, if found. + // We subtract -1 so it matches that inside the thinkers list. + for (int i = (player - 1); i < TL.size() - 1; i++) { + // Get "next" pointer. + curr = TL.get(i).nextid; + pointindex.put(curr, TL.get(i + 1)); + } + + // We also search backwards, in case player wasn't first object + // (can this even happen, in vanilla?) + // -1 so it matches that of the TL list. + for (int i = (player - 1); i > 0; i--) { + // Get "prev" pointer. + curr = TL.get(i).previd; + pointindex.put(curr, TL.get(i - 1)); + } + + } + + /** + * Allows reconstructing infighting targets from stored pointers/indices from + * the hashtable created by reconstructPointers. + * + */ + protected void rewirePointers() { + TL.forEach(th -> { + if (th.p_target != 0) { + th.target = pointindex.get(th.p_target); + th.tracer = pointindex.get(th.p_tracer); + // System.out.printf("Object %s has target %s\n",th.type.toString(),th.target.type.toString()); + } + }); + } + + protected enum specials_e { + tc_ceiling, + tc_door, + tc_floor, + tc_plat, + tc_flash, + tc_strobe, + tc_glow, + tc_endspecials + + }; + + // + //P_ArchiveSpecials + // + @P_SaveG.C(P_ArchiveSpecials) + protected void ArchiveSpecials() throws IOException { + ceiling_t ceiling; + vldoor_t door; + floormove_t floor; + plat_t plat; + lightflash_t flash; + strobe_t strobe; + glow_t glow; + int i; + + // Most of these objects are quite hefty, but estimating 128 bytes tops + // for each should do (largest one is 56); + ByteBuffer buffer = ByteBuffer.allocate(128); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // save off the current thinkers + for (thinker_t th = DOOM.actions.getThinkerCap().next; th != DOOM.actions.getThinkerCap(); th = th.next) { + + // Write out any pending objects. + if (buffer.position() > 0) { + fo.write(buffer.array(), 0, buffer.position()); + //System.out.println("Wrote out "+buffer.position()+" bytes"); + + } + + // Back to the beginning. + buffer.position(0); + + // So ceilings don't think? + if (th.thinkerFunction == NOP) { + // i maintains status between iterations + for (i = 0; i < DOOM.actions.getMaxCeilings(); i++) { + if ((th instanceof ceiling_t) && (DOOM.actions.getActiveCeilings()[i] == (ceiling_t) th)) { + break; + } + } + + if (i < MAXCEILINGS) { + fo.writeByte(specials_e.tc_ceiling.ordinal()); + PADSAVEP(fo); + // Set id for saving + ceiling = (ceiling_t) th; + ceiling.sectorid = ceiling.sector.id; + ceiling.pack(buffer); + } + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_MoveCeiling) { + + fo.writeByte(specials_e.tc_ceiling.ordinal()); + PADSAVEP(fo); + ceiling = (ceiling_t) th; + ceiling.sectorid = ceiling.sector.id; + ceiling.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_VerticalDoor) { + + fo.writeByte(specials_e.tc_door.ordinal()); + PADSAVEP(fo); + door = (vldoor_t) th; + door.sectorid = door.sector.id; + door.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_MoveFloor) { + fo.writeByte(specials_e.tc_floor.ordinal()); + PADSAVEP(fo); + floor = (floormove_t) th; + floor.sectorid = floor.sector.id; + floor.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_PlatRaise) { + fo.writeByte(specials_e.tc_plat.ordinal()); + PADSAVEP(fo); + plat = (plat_t) th; + plat.sectorid = plat.sector.id; + plat.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_LightFlash) { + fo.writeByte(specials_e.tc_flash.ordinal()); + PADSAVEP(fo); + flash = (lightflash_t) th; + flash.sectorid = flash.sector.id; + flash.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_StrobeFlash) { + fo.writeByte(specials_e.tc_strobe.ordinal()); + PADSAVEP(fo); + strobe = (strobe_t) th; + strobe.sectorid = strobe.sector.id; + strobe.pack(buffer); + continue; + } + + // Well, apparently some do. + if (th.thinkerFunction == T_Glow) { + fo.writeByte(specials_e.tc_glow.ordinal()); + PADSAVEP(fo); + glow = (glow_t) th; + glow.sectorid = glow.sector.id; + glow.pack(buffer); + } + } + + if (buffer.position() > 0) { + fo.write(buffer.array(), 0, buffer.position()); + } + + // Finito! + fo.writeByte((byte) specials_e.tc_endspecials.ordinal()); + } + + // + //P_UnArchiveSpecials + // + @P_SaveG.C(P_UnArchiveSpecials) + protected void UnArchiveSpecials() throws IOException { + specials_e tclass; + ceiling_t ceiling; + vldoor_t door; + floormove_t floor; + plat_t plat; + lightflash_t flash; + strobe_t strobe; + glow_t glow; + + //List A=new ArrayList(); + DOOM.actions.ClearPlatsBeforeLoading(); + DOOM.actions.ClearCeilingsBeforeLoading(); + + // read in saved thinkers + while (true) { + int tmp = f.readUnsignedByte(); + //tmp&=0x00ff; // To "unsigned byte" + tclass = specials_e.values()[tmp]; + switch (tclass) { + case tc_endspecials: + return; // end of list + + case tc_ceiling: + PADSAVEP(f, maxsize); + ceiling = new ceiling_t(); + ceiling.read(f); + ceiling.sector = DOOM.levelLoader.sectors[ceiling.sectorid]; + ceiling.sector.specialdata = ceiling; + + if (ceiling.functionid != 0) { + ceiling.thinkerFunction = T_MoveCeiling; + } + + DOOM.actions.AddThinker(ceiling); + DOOM.actions.AddActiveCeiling(ceiling); + break; + + case tc_door: + PADSAVEP(f, maxsize); + door = new vldoor_t(); + door.read(f); + door.sector = DOOM.levelLoader.sectors[door.sectorid]; + door.sector.specialdata = door; + door.thinkerFunction = T_VerticalDoor; + + DOOM.actions.AddThinker(door); + break; + + case tc_floor: + PADSAVEP(f, maxsize); + floor = new floormove_t(); + floor.read(f); + floor.sector = DOOM.levelLoader.sectors[floor.sectorid]; + floor.sector.specialdata = floor; + floor.thinkerFunction = T_MoveFloor; + + DOOM.actions.AddThinker(floor); + break; + + case tc_plat: + PADSAVEP(f, maxsize); + plat = new plat_t(); + plat.read(f); + plat.sector = DOOM.levelLoader.sectors[plat.sectorid]; + plat.sector.specialdata = plat; + + if (plat.functionid != 0) { + plat.thinkerFunction = T_PlatRaise; + } + + DOOM.actions.AddThinker(plat); + DOOM.actions.AddActivePlat(plat); + break; + + case tc_flash: + PADSAVEP(f, maxsize); + flash = new lightflash_t(); + flash.read(f); + flash.sector = DOOM.levelLoader.sectors[flash.sectorid]; + flash.thinkerFunction = T_LightFlash; + + DOOM.actions.AddThinker(flash); + break; + + case tc_strobe: + PADSAVEP(f, maxsize); + strobe = new strobe_t(); + strobe.read(f); + strobe.sector = DOOM.levelLoader.sectors[strobe.sectorid]; + strobe.thinkerFunction = T_StrobeFlash; + + DOOM.actions.AddThinker(strobe); + break; + + case tc_glow: + PADSAVEP(f, maxsize); + glow = new glow_t(); + glow.read(f); + glow.sector = DOOM.levelLoader.sectors[glow.sectorid]; + glow.thinkerFunction = T_Glow; + + DOOM.actions.AddThinker(glow); + break; + + default: + DOOM.doomSystem.Error("P_UnarchiveSpecials:Unknown tclass %d in savegame", tmp); + } + } + + } + + /** + * Pads save_p to a 4-byte boundary + * so that the load/save works on SGI&Gecko. + * + * @param save_p + */ + protected final int PADSAVEP(int save_p) { + return (save_p + ((4 - (save_p & 3)) & 3)); + } + + //protected final int PADSAVEP(ByteBuffer b, int save_p){ + // ByteBuffer + // return (save_p += (4 - ((int) save_p & 3)) & 3); + //} + protected final long PADSAVEP(DataInputStream f, int maxsize) throws IOException { + long save_p = maxsize - f.available(); + int padding = (4 - ((int) save_p & 3)) & 3; + // System.out.printf("Current position %d Padding by %d bytes %d\n",save_p,padding,maxsize); + f.skip(padding); + return padding; + } + + protected final long PADSAVEP(DataOutputStream f) throws IOException { + long save_p = f.size(); + int padding = (4 - ((int) save_p & 3)) & 3; + // System.out.printf("Current position %d Padding by %d bytes\n",save_p,padding); + for (int i = 0; i < padding; i++) { + f.write(0); + } + return padding; + } + + @Override + public boolean doSave(DataOutputStream f) { + try { + // The header must have been set, at this point. + this.fo = f; + //f.setLength(0); // Kill old info. + header.write(f); + + //header.read(f); + ArchivePlayers(); + ArchiveWorld(); + ArchiveThinkers(); + ArchiveSpecials(); + // TODO: the rest... + f.write(0x1D); + } catch (IOException e) { + LOGGER.log(Level.WARNING, e, () + -> String.format("Error while saving savegame! Cause: %s", e.getMessage())); + return false; // Needed to shut up compiler. + } + return true; + } + +} \ No newline at end of file diff --git a/doom/src/savegame/VanillaDSGHeader.java b/doom/src/savegame/VanillaDSGHeader.java new file mode 100644 index 0000000..f29a55c --- /dev/null +++ b/doom/src/savegame/VanillaDSGHeader.java @@ -0,0 +1,226 @@ +package savegame; + +import static data.Defines.VERSION; +import static data.Limits.MAXPLAYERS; +import static data.Limits.SAVESTRINGSIZE; +import static data.Limits.VERSIONSIZE; +import defines.skill_t; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import utils.C2JUtils; +import w.CacheableDoomObject; +import w.DoomBuffer; +import w.DoomIO; +import w.IReadableDoomObject; +import w.IWritableDoomObject; + +/** The header of a vanilla savegame. + * + * It contains a fixed-length, null-terminated string of 24 bytes max, in any case. + * Then a 16-byte "version string", which normally reads "version 109". + * Then bytes that record: + * skill +1 + * episode +1 + * map +1 + * players in game +4 + * gametime +3 (as 24-bit big-endian) + * + * So the header has an total size of *drum roll* 50 bytes. + * + * + * @author admin + * + */ +public class VanillaDSGHeader implements IDoomSaveGameHeader, IReadableDoomObject, IWritableDoomObject, CacheableDoomObject { + + public String name; // max size SAVEGAMENAME + public String vcheck; + // These are for DS + public skill_t gameskill; + public int gameepisode; + public int gamemap; + public boolean[] playeringame; + /** what bullshit, stored as 24-bit integer?! */ + public int leveltime; + // These help checking shit. + public boolean wrongversion; + public boolean properend; + + public VanillaDSGHeader() { + this.playeringame = new boolean[MAXPLAYERS]; + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + name = DoomBuffer.getNullTerminatedString(buf, SAVESTRINGSIZE); + vcheck = DoomBuffer.getNullTerminatedString(buf, VERSIONSIZE); + String vcheckb = ("version " + VERSION); + // no more unpacking, and report it. + if (wrongversion = !(vcheckb.equalsIgnoreCase(vcheck))) { + return; + } + gameskill = skill_t.values()[buf.get()]; + gameepisode = buf.get(); + gamemap = buf.get(); + + for (int i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = buf.get() != 0; + } + + // load a base level (this doesn't advance the pointer?) + //G_InitNew (gameskill, gameepisode, gamemap); + // get the times + int a = C2JUtils.toUnsignedByte(buf.get()); + int b = C2JUtils.toUnsignedByte(buf.get()); + int c = C2JUtils.toUnsignedByte(buf.get()); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + leveltime = (a << 16) | (b << 8) | c; + + // Mark this position... + buf.mark(); + buf.position(buf.limit() - 1); + properend = buf.get() == 0x1d; + buf.reset(); + + // We've loaded whatever consistutes "header" info, the rest must be unpacked by proper + // methods in the game engine itself. + } + + @Override + public void write(DataOutputStream f) + throws IOException { + DoomIO.writeString(f, name, SAVESTRINGSIZE); + DoomIO.writeString(f, vcheck, VERSIONSIZE); + f.writeByte(gameskill.ordinal()); + f.writeByte(gameepisode); + f.writeByte(gamemap); + for (int i = 0; i < MAXPLAYERS; i++) { + f.writeBoolean(playeringame[i]); + } + + // load a base level (this doesn't advance the pointer?) + //G_InitNew (gameskill, gameepisode, gamemap); + // get the times + byte a = (byte) (0x0000FF & (leveltime >> 16)); + byte b = (byte) (0x00FF & (leveltime >> 8)); + byte c = (byte) (0x00FF & (leveltime)); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + f.writeByte(a); + f.writeByte(b); + f.writeByte(c); + + // The end. This is actually just the header, so we don't "end" here just yet. + // f.writeByte(0x1d); + } + + @Override + public void read(DataInputStream f) + throws IOException { + name = DoomIO.readNullTerminatedString(f, SAVESTRINGSIZE); + vcheck = DoomIO.readNullTerminatedString(f, VERSIONSIZE); + gameskill = skill_t.values()[f.readUnsignedByte()]; + gameepisode = f.readByte(); + gamemap = f.readByte(); + for (int i = 0; i < MAXPLAYERS; i++) { + playeringame[i] = f.readBoolean(); + } + + // get the times + int a = f.readUnsignedByte(); + int b = f.readUnsignedByte(); + int c = f.readUnsignedByte(); + // Quite anomalous, leveltime is stored as a BIG ENDIAN, 24-bit unsigned integer :-S + leveltime = (a << 16) | (b << 8) | c; + + } + + ////////////////////////// NASTY GETTERS ////////////////////////////// + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getVersion() { + return vcheck; + } + + @Override + public void setVersion(String vcheck) { + this.vcheck = vcheck; + } + + @Override + public skill_t getGameskill() { + return gameskill; + } + + @Override + public void setGameskill(skill_t gameskill) { + this.gameskill = gameskill; + } + + @Override + public int getGameepisode() { + return gameepisode; + } + + @Override + public void setGameepisode(int gameepisode) { + this.gameepisode = gameepisode; + } + + @Override + public int getGamemap() { + return gamemap; + } + + @Override + public void setGamemap(int gamemap) { + this.gamemap = gamemap; + } + + @Override + public boolean[] getPlayeringame() { + return playeringame; + } + + @Override + public void setPlayeringame(boolean[] playeringame) { + this.playeringame = playeringame; + } + + @Override + public int getLeveltime() { + return leveltime; + } + + @Override + public void setLeveltime(int leveltime) { + this.leveltime = leveltime; + } + + @Override + public boolean isWrongversion() { + return wrongversion; + } + + @Override + public void setWrongversion(boolean wrongversion) { + this.wrongversion = wrongversion; + } + + @Override + public boolean isProperend() { + return properend; + } + +} \ No newline at end of file diff --git a/doom/src/st/AbstractStatusBar.java b/doom/src/st/AbstractStatusBar.java new file mode 100644 index 0000000..6cf6ddf --- /dev/null +++ b/doom/src/st/AbstractStatusBar.java @@ -0,0 +1,12 @@ +package st; + +import doom.DoomMain; + +public abstract class AbstractStatusBar implements IDoomStatusBar { + + protected final DoomMain DOOM; + + public AbstractStatusBar(DoomMain DOOM) { + this.DOOM = DOOM; + } +} \ No newline at end of file diff --git a/doom/src/st/IDoomStatusBar.java b/doom/src/st/IDoomStatusBar.java new file mode 100644 index 0000000..c400cbb --- /dev/null +++ b/doom/src/st/IDoomStatusBar.java @@ -0,0 +1,68 @@ +package st; +// Emacs style mode select -*- Java -*- +//----------------------------------------------------------------------------- +// +// $Id: IDoomStatusBar.java,v 1.4 2012/09/24 17:16:23 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// +//----------------------------------------------------------------------------- + +import doom.SourceCode.ST_Stuff; +import static doom.SourceCode.ST_Stuff.ST_Responder; +import doom.event_t; + +public interface IDoomStatusBar { + // + // STATUS BAR + // + + public void NotifyAMEnter(); + + public void NotifyAMExit(); + + /** Called by main loop. */ + @ST_Stuff.C(ST_Responder) + public boolean Responder(event_t ev); + + /** Called by main loop. */ + public void Ticker(); + + /** Called by main loop.*/ + public void Drawer(boolean fullscreen, boolean refresh); + + /** Called when the console player is spawned on each level. */ + public void Start(); + + /** Called by startup code. */ + public void Init(); + + /** Used externally to determine window scaling. + * This means that drawing transparent status bars is possible, but + * it will look fugly because of the solid windowing (and possibly + * HOMS). + */ + public int getHeight(); + + /** Forces a full refresh for reasons not handled otherwise, e.g. after full-page + * draws of help screens, which normally don't trigger a complete redraw even if + * they should, really. + */ + void forceRefresh(); + +} \ No newline at end of file diff --git a/doom/src/st/StatusBar.java b/doom/src/st/StatusBar.java new file mode 100644 index 0000000..a30c9f9 --- /dev/null +++ b/doom/src/st/StatusBar.java @@ -0,0 +1,2017 @@ +package st; + +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: StatusBar.java,v 1.47 2011/11/01 23:46:37 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Status bar code. +// Does the face/direction indicator animatin. +// Does palette indicators as well (red pain/berserk, bright pickup) +// +// ----------------------------------------------------------------------------- +import static data.Defines.NUMAMMO; +import static data.Defines.NUMCARDS; +import static data.Defines.NUMWEAPONS; +import static data.Defines.PU_STATIC; +import static data.Defines.TICRATE; +import static data.Defines.pw_invulnerability; +import static data.Defines.pw_ironfeet; +import static data.Defines.pw_strength; +import static data.Limits.MAXPLAYERS; +import static data.Tables.ANG180; +import static data.Tables.ANG45; +import data.sounds.musicenum_t; +import defines.ammotype_t; +import doom.DoomMain; +import doom.SourceCode; +import doom.SourceCode.CauseOfDesyncProbability; +import doom.SourceCode.ST_Stuff; +import static doom.SourceCode.ST_Stuff.ST_Responder; +import static doom.englsh.STSTR_BEHOLD; +import static doom.englsh.STSTR_BEHOLDX; +import static doom.englsh.STSTR_CHOPPERS; +import static doom.englsh.STSTR_CLEV; +import static doom.englsh.STSTR_DQDOFF; +import static doom.englsh.STSTR_DQDON; +import static doom.englsh.STSTR_FAADDED; +import static doom.englsh.STSTR_KFAADDED; +import static doom.englsh.STSTR_MUS; +import static doom.englsh.STSTR_NCOFF; +import static doom.englsh.STSTR_NCON; +import static doom.englsh.STSTR_NOMUS; +import doom.event_t; +import doom.evtype_t; +import static doom.items.weaponinfo; +import doom.player_t; +import static doom.player_t.CF_GODMODE; +import static doom.player_t.CF_NOCLIP; +import doom.weapontype_t; +import g.Signals; +import java.awt.Rectangle; +import m.Settings; +import m.cheatseq_t; +import p.mobj_t; +import rr.patch_t; +import static v.DoomGraphicSystem.V_NOSCALESTART; +import static v.DoomGraphicSystem.V_PREDIVIDE; +import static v.DoomGraphicSystem.V_SAFESCALE; +import static v.DoomGraphicSystem.V_SCALEOFFSET; +import static v.DoomGraphicSystem.V_TRANSLUCENTPATCH; +import static v.renderers.DoomScreen.BG; +import static v.renderers.DoomScreen.FG; +import static v.renderers.DoomScreen.SB; + +public class StatusBar extends AbstractStatusBar { + + public static final String rcsid + = "$Id: StatusBar.java,v 1.47 2011/11/01 23:46:37 velktron Exp $"; + + // Size of statusbar. + // Now sensitive for scaling. + // + // STATUS BAR DATA + // + // Palette indices. + // For damage/bonus red-/gold-shifts + private static int STARTREDPALS = 1; + + private static int STARTBONUSPALS = 9; + + private static int NUMREDPALS = 8; + + private static int NUMBONUSPALS = 4; + + // Radiation suit, green shift. + private static int RADIATIONPAL = 13; + + // N/256*100% probability + // that the normal face state will change + private static int ST_FACEPROBABILITY = 96; + + // For Responder + private static int ST_TOGGLECHAT = Signals.ScanCode.SC_ENTER.c; + + // Location of status bar + private int ST_X = 0; + private int ST_X2; + private int ST_FX; + private int ST_FY; + private Rectangle ST_RECT; + + // Should be set to patch width + // for tall numbers later on + // TODO: private static int ST_TALLNUMWIDTH = (tallnum[0].width); + // Number of status faces. + private static int ST_NUMPAINFACES = 5; + + private static int ST_NUMSTRAIGHTFACES = 3; + + private static int ST_NUMTURNFACES = 2; + + private static int ST_NUMSPECIALFACES = 3; + + private static int ST_FACESTRIDE + = (ST_NUMSTRAIGHTFACES + ST_NUMTURNFACES + ST_NUMSPECIALFACES); + + private static int ST_NUMEXTRAFACES = 2; + + private static int ST_NUMFACES + = (ST_FACESTRIDE * ST_NUMPAINFACES + ST_NUMEXTRAFACES); + + private static int ST_TURNOFFSET = (ST_NUMSTRAIGHTFACES); + + private static int ST_OUCHOFFSET = (ST_TURNOFFSET + ST_NUMTURNFACES); + + private static int ST_EVILGRINOFFSET = (ST_OUCHOFFSET + 1); + + private static int ST_RAMPAGEOFFSET = (ST_EVILGRINOFFSET + 1); + + private static int ST_GODFACE = (ST_NUMPAINFACES * ST_FACESTRIDE); + + private static int ST_DEADFACE = (ST_GODFACE + 1); + + private int ST_FACESX; + + private int ST_FACESY; + + private static int ST_EVILGRINCOUNT = (2 * TICRATE); + + private static int ST_STRAIGHTFACECOUNT = (TICRATE / 2); + + private static int ST_TURNCOUNT = (1 * TICRATE); + + private static int ST_OUCHCOUNT = (1 * TICRATE); + + private static int ST_RAMPAGEDELAY = (2 * TICRATE); + + private static int ST_MUCHPAIN = 20; + + // Location and size of statistics, + // justified according to widget type. + // Problem is, within which space? STbar? Screen? + // Note: this could be read in by a lump. + // Problem is, is the stuff rendered + // into a buffer, + // or into the frame buffer? + // AMMO number pos. + private int ST_AMMOWIDTH; + private int ST_AMMOX; + private int ST_AMMOY; + + // HEALTH number pos. + private int ST_HEALTHWIDTH = 3; + private int ST_HEALTHX; + private int ST_HEALTHY; + + // Weapon pos. + private int ST_ARMSX; + private int ST_ARMSY; + private int ST_ARMSBGX; + private int ST_ARMSBGY; + private int ST_ARMSXSPACE; + private int ST_ARMSYSPACE; + + // Frags pos. + private int ST_FRAGSX; + private int ST_FRAGSY; + private int ST_FRAGSWIDTH; + + // ARMOR number pos. + private int ST_ARMORWIDTH = 3; + + private int ST_ARMORX; + + private int ST_ARMORY; + + // Key icon positions. + private int ST_KEY0WIDTH; + private int ST_KEY0HEIGHT; + private int ST_KEY0X; + private int ST_KEY0Y; + + private int ST_KEY1WIDTH; + private int ST_KEY1X; + private int ST_KEY1Y; + + private int ST_KEY2WIDTH; + private int ST_KEY2X; + private int ST_KEY2Y; + + // Ammunition counter. + private int ST_AMMO0WIDTH; + private int ST_AMMO0HEIGHT; + + private int ST_AMMO0X; + private int ST_AMMO0Y; + + private int ST_AMMO1WIDTH; + + private int ST_AMMO1X; + + private int ST_AMMO1Y; + + private int ST_AMMO2WIDTH; + + private int ST_AMMO2X; + + private int ST_AMMO2Y; + + private int ST_AMMO3WIDTH; + + private int ST_AMMO3X; + + private int ST_AMMO3Y; + + // Indicate maximum ammunition. + // Only needed because backpack exists. + private int ST_MAXAMMO0WIDTH; + + private int ST_MAXAMMO0HEIGHT; + + private int ST_MAXAMMO0X; + + private int ST_MAXAMMO0Y; + + private int ST_MAXAMMO1WIDTH; + + private int ST_MAXAMMO1X; + + private int ST_MAXAMMO1Y; + + private int ST_MAXAMMO2WIDTH; + + private int ST_MAXAMMO2X; + + private int ST_MAXAMMO2Y; + + private int ST_MAXAMMO3WIDTH; + + private int ST_MAXAMMO3X; + private int ST_MAXAMMO3Y; + + // pistol + private int ST_WEAPON0X; + private int ST_WEAPON0Y; + + // shotgun + private int ST_WEAPON1X; + private int ST_WEAPON1Y; + + // chain gun + private int ST_WEAPON2X; + private int ST_WEAPON2Y; + + // missile launcher + private int ST_WEAPON3X; + private int ST_WEAPON3Y; + + // plasma gun + private int ST_WEAPON4X; + private int ST_WEAPON4Y; + + // bfg + private int ST_WEAPON5X; + private int ST_WEAPON5Y; + + // WPNS title + private int ST_WPNSX; + private int ST_WPNSY; + + // DETH title + private int ST_DETHX; + private int ST_DETHY; + + // Incoming messages window location + // UNUSED + // #define ST_MSGTEXTX (viewwindowx) + // #define ST_MSGTEXTY (viewwindowy+viewheight-18) + private static int ST_MSGTEXTX = 0; + + private static int ST_MSGTEXTY = 0; + + // Dimensions given in characters. + private static int ST_MSGWIDTH = 52; + + // Or shall I say, in lines? + private static int ST_MSGHEIGHT = 1; + + private static int ST_OUTTEXTX = 0; + + private static int ST_OUTTEXTY = 6; + + // Width, in characters again. + private static int ST_OUTWIDTH = 52; + + // Height, in lines. + private static int ST_OUTHEIGHT = 1; + + // TODO private static int ST_MAPWIDTH = + // (mapnames[(gameepisode-1)*9+(gamemap-1)].length)); + // TODO private static int ST_MAPTITLEX = (SCREENWIDTH - ST_MAPWIDTH * + // ST_CHATFONTWIDTH); + private static int ST_MAPTITLEY = 0; + + private static int ST_MAPHEIGHT = 1; + + // main player in game + private player_t plyr; + + // ST_Start() has just been called, OR we want to force an redraw anyway. + private boolean st_firsttime; + + @Override + public void forceRefresh() { + st_firsttime = true; + } + + // used to execute ST_Init() only once + private int veryfirsttime = 1; + + // lump number for PLAYPAL + private int lu_palette; + + // used for timing (unsigned int .. maybe long !) + private long st_clock; + + // used for making messages go away + int st_msgcounter = 0; + + // used when in chat + private st_chatstateenum_t st_chatstate; + + // whether in automap or first-person + private st_stateenum_t st_gamestate; + + /** whether left-side main status bar is active. This fugly hax + * (and others like it) are necessary in order to have something + * close to a pointer. + */ + private boolean[] st_statusbaron = {false}; + + // whether status bar chat is active + private boolean st_chat; + + // value of st_chat before message popped up + private boolean st_oldchat; + + // whether chat window has the cursor on + private boolean[] st_cursoron = {false}; + + /** !deathmatch */ + private boolean[] st_notdeathmatch = {true}; + + /** !deathmatch && st_statusbaron */ + private boolean[] st_armson = {true}; + + /** !deathmatch */ + private boolean[] st_fragson = {false}; + + // main bar left + private patch_t sbar; + + // 0-9, tall numbers + private patch_t[] tallnum = new patch_t[10]; + + // tall % sign + private patch_t tallpercent; + + // 0-9, short, yellow (,different!) numbers + private patch_t[] shortnum = new patch_t[10]; + + // 3 key-cards, 3 skulls + private patch_t[] keys = new patch_t[NUMCARDS]; + + // face status patches + private patch_t[] faces = new patch_t[ST_NUMFACES]; + + // face background + private patch_t faceback; + + // main bar right + private patch_t armsbg; + + // weapon ownership patches + private patch_t[][] arms = new patch_t[6][2]; + + // // WIDGETS ///// + // ready-weapon widget + private st_number_t w_ready; + + // in deathmatch only, summary of frags stats + private st_number_t w_frags; + + // health widget + private st_percent_t w_health; + + // arms background + private st_binicon_t w_armsbg; + + // weapon ownership widgets + private st_multicon_t[] w_arms = new st_multicon_t[6]; + + // face status widget + private st_multicon_t w_faces; + + // keycard widgets + private st_multicon_t[] w_keyboxes = new st_multicon_t[3]; + + // armor widget + private st_percent_t w_armor; + + // ammo widgets + private st_number_t[] w_ammo = new st_number_t[4]; + + // max ammo widgets + private st_number_t[] w_maxammo = new st_number_t[4]; + + // / END WIDGETS //// + // number of frags so far in deathmatch + private int[] st_fragscount = {0}; + + // used to use appopriately pained face + private int st_oldhealth = -1; + + // used for evil grin + private boolean[] oldweaponsowned = new boolean[NUMWEAPONS]; + + // count until face changes + private int st_facecount = 0; + + // current face index, used by w_faces + private int[] st_faceindex = new int[1]; + + // holds key-type for each key box on bar + private int[] keyboxes = new int[3]; + + // a random number per tick + private int st_randomnumber; + + // idmypos toggle mode + private boolean st_idmypos = false; + + // Massive bunches of cheat shit + // to keep it from being easy to figure them out. + // Yeah, right... + private char cheat_mus_seq[] + = {0xb2, 0x26, 0xb6, 0xae, 0xea, 1, 0, 0, 0xff}; + + private char cheat_choppers_seq[] + = {0xb2, 0x26, 0xe2, 0x32, 0xf6, 0x2a, 0x2a, 0xa6, 0x6a, 0xea, 0xff // id... + }; + + private char cheat_god_seq[] = {0xb2, 0x26, 0x26, 0xaa, 0x26, 0xff // iddqd +}; + + private char cheat_ammo_seq[] = {0xb2, 0x26, 0xf2, 0x66, 0xa2, 0xff // idkfa +}; + + private char cheat_ammonokey_seq[] = {0xb2, 0x26, 0x66, 0xa2, 0xff // idfa +}; + + // Smashing Pumpkins Into Samml Piles Of Putried Debris. + private char cheat_noclip_seq[] = {0xb2, 0x26, 0xea, 0x2a, 0xb2, // idspispopd + 0xea, 0x2a, 0xf6, 0x2a, 0x26, 0xff}; + + // + private char cheat_commercial_noclip_seq[] + = {0xb2, 0x26, 0xe2, 0x36, 0xb2, 0x2a, 0xff // idclip + }; + + private char cheat_powerup_seq[][] + = {{0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6e, 0xff}, // beholdv + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xea, 0xff}, // beholds + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xb2, 0xff}, // beholdi + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x6a, 0xff}, // beholdr + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xa2, 0xff}, // beholda + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0x36, 0xff}, // beholdl + {0xb2, 0x26, 0x62, 0xa6, 0x32, 0xf6, 0x36, 0x26, 0xff} // behold + }; + + private char cheat_clev_seq[] + = {0xb2, 0x26, 0xe2, 0x36, 0xa6, 0x6e, 1, 0, 0, 0xff // idclev + }; + + // my position cheat + private char cheat_mypos_seq[] + = {0xb2, 0x26, 0xb6, 0xba, 0x2a, 0xf6, 0xea, 0xff // idmypos + }; + + // Now what? + cheatseq_t cheat_mus = new cheatseq_t(cheat_mus_seq, 0); + + cheatseq_t cheat_god = new cheatseq_t(cheat_god_seq, 0); + + cheatseq_t cheat_ammo = new cheatseq_t(cheat_ammo_seq, 0); + + cheatseq_t cheat_ammonokey = new cheatseq_t(cheat_ammonokey_seq, 0); + + cheatseq_t cheat_noclip = new cheatseq_t(cheat_noclip_seq, 0); + + cheatseq_t cheat_commercial_noclip + = new cheatseq_t(cheat_commercial_noclip_seq, 0); + + cheatseq_t[] cheat_powerup + = {new cheatseq_t(cheat_powerup_seq[0], 0), + new cheatseq_t(cheat_powerup_seq[1], 0), + new cheatseq_t(cheat_powerup_seq[2], 0), + new cheatseq_t(cheat_powerup_seq[3], 0), + new cheatseq_t(cheat_powerup_seq[4], 0), + new cheatseq_t(cheat_powerup_seq[5], 0), + new cheatseq_t(cheat_powerup_seq[6], 0)}; + + cheatseq_t cheat_choppers = new cheatseq_t(cheat_choppers_seq, 0); + + cheatseq_t cheat_clev = new cheatseq_t(cheat_clev_seq, 0); + + cheatseq_t cheat_mypos = new cheatseq_t(cheat_mypos_seq, 0); + + cheatseq_t cheat_tnthom = new cheatseq_t("tnthom", false); + + // + String[] mapnames; + + // + // STATUS BAR CODE + // + public StatusBar(DoomMain DOOM) { + super(DOOM); + ST_HEIGHT = 32 * DOOM.vs.getSafeScaling(); + ST_WIDTH = DOOM.vs.getScreenWidth(); + ST_Y = (DOOM.vs.getScreenHeight() - ST_HEIGHT); + ST_X2 = (int) (104 * DOOM.vs.getSafeScaling()); + ST_FX = (int) (143 * DOOM.vs.getSafeScaling()); + ST_FY = (int) (169 * DOOM.vs.getSafeScaling()); + ST_FACESX = (int) (143 * DOOM.vs.getSafeScaling()); + + ST_FACESY = (int) (168 * DOOM.vs.getSafeScaling()); + + // AMMO number pos. + ST_AMMOWIDTH = 3; + ST_AMMOX = (int) (44 * DOOM.vs.getSafeScaling()); + ST_AMMOY = (int) (171 * DOOM.vs.getSafeScaling()); + + // HEALTH number pos + ST_HEALTHWIDTH = 3; + ST_HEALTHX = (int) (90 * DOOM.vs.getSafeScaling()); + ST_HEALTHY = (int) (171 * DOOM.vs.getSafeScaling()); + + // Weapon pos. + ST_ARMSX = (int) (111 * DOOM.vs.getSafeScaling()); + ST_ARMSY = (int) (172 * DOOM.vs.getSafeScaling()); + ST_ARMSBGX = (int) (104 * DOOM.vs.getSafeScaling()); + ST_ARMSBGY = (int) (168 * DOOM.vs.getSafeScaling()); + ST_ARMSXSPACE = 12 * DOOM.vs.getSafeScaling(); + ST_ARMSYSPACE = 10 * DOOM.vs.getSafeScaling(); + + // Frags pos. + ST_FRAGSX = (int) (138 * DOOM.vs.getSafeScaling()); + ST_FRAGSY = (int) (171 * DOOM.vs.getSafeScaling()); + ST_FRAGSWIDTH = 2; + + // + ST_ARMORX = (int) (221 * DOOM.vs.getSafeScaling()); + + ST_ARMORY = (int) (171 * DOOM.vs.getSafeScaling()); + + // Key icon positions. + ST_KEY0WIDTH = 8 * DOOM.vs.getSafeScaling(); + ST_KEY0HEIGHT = 5 * DOOM.vs.getSafeScaling(); + + ST_KEY0X = (int) (239 * DOOM.vs.getSafeScaling()); + ST_KEY0Y = (int) (171 * DOOM.vs.getSafeScaling()); + + ST_KEY1WIDTH = ST_KEY0WIDTH; + ST_KEY1X = (int) (239 * DOOM.vs.getSafeScaling()); + ST_KEY1Y = (int) (181 * DOOM.vs.getSafeScaling()); + + ST_KEY2WIDTH = ST_KEY0WIDTH; + ST_KEY2X = (int) (239 * DOOM.vs.getSafeScaling()); + ST_KEY2Y = (int) (191 * DOOM.vs.getSafeScaling()); + + // Ammunition counter. + ST_AMMO0WIDTH = 3 * DOOM.vs.getSafeScaling(); + ST_AMMO0HEIGHT = 6 * DOOM.vs.getSafeScaling(); + + ST_AMMO0X = (int) (288 * DOOM.vs.getSafeScaling()); + + ST_AMMO0Y = (int) (173 * DOOM.vs.getSafeScaling()); + + ST_AMMO1WIDTH = ST_AMMO0WIDTH; + + ST_AMMO1X = (int) (288 * DOOM.vs.getSafeScaling()); + + ST_AMMO1Y = (int) (179 * DOOM.vs.getSafeScaling()); + + ST_AMMO2WIDTH = ST_AMMO0WIDTH; + + ST_AMMO2X = (int) (288 * DOOM.vs.getSafeScaling()); + + ST_AMMO2Y = (int) (191 * DOOM.vs.getSafeScaling()); + + ST_AMMO3WIDTH = ST_AMMO0WIDTH; + + ST_AMMO3X = (int) (288 * DOOM.vs.getSafeScaling()); + + ST_AMMO3Y = (int) (185 * DOOM.vs.getSafeScaling()); + + // Indicate maximum ammunition. + // Only needed because backpack exists. + ST_MAXAMMO0WIDTH = 3 * DOOM.vs.getSafeScaling(); + ST_MAXAMMO0HEIGHT = 5 * DOOM.vs.getSafeScaling(); + + ST_MAXAMMO0X = (int) (314 * DOOM.vs.getSafeScaling()); + ST_MAXAMMO0Y = (int) (173 * DOOM.vs.getSafeScaling()); + + ST_MAXAMMO1WIDTH = ST_MAXAMMO0WIDTH; + ST_MAXAMMO1X = 314 * DOOM.vs.getSafeScaling(); + ST_MAXAMMO1Y = (int) (179 * DOOM.vs.getSafeScaling()); + + ST_MAXAMMO2WIDTH = ST_MAXAMMO0WIDTH; + ST_MAXAMMO2X = (int) (314 * DOOM.vs.getSafeScaling()); + ST_MAXAMMO2Y = (int) (191 * DOOM.vs.getSafeScaling()); + + ST_MAXAMMO3WIDTH = ST_MAXAMMO0WIDTH; + ST_MAXAMMO3X = (int) (314 * DOOM.vs.getSafeScaling()); + ST_MAXAMMO3Y = (int) (185 * DOOM.vs.getSafeScaling()); + + // pistol + ST_WEAPON0X = (int) (110 * DOOM.vs.getSafeScaling()); + ST_WEAPON0Y = (int) (172 * DOOM.vs.getSafeScaling()); + + // shotgun + ST_WEAPON1X = (int) (122 * DOOM.vs.getSafeScaling()); + ST_WEAPON1Y = (int) (172 * DOOM.vs.getSafeScaling()); + + // chain gun + ST_WEAPON2X = (int) (134 * DOOM.vs.getSafeScaling()); + + ST_WEAPON2Y = (int) (172 * DOOM.vs.getSafeScaling()); + + // missile launcher + ST_WEAPON3X = (int) (110 * DOOM.vs.getSafeScaling()); + + ST_WEAPON3Y = (int) (181 * DOOM.vs.getSafeScaling()); + + // plasma gun + ST_WEAPON4X = (int) (122 * DOOM.vs.getSafeScaling()); + + ST_WEAPON4Y = (int) (181 * DOOM.vs.getSafeScaling()); + + // bfg + ST_WEAPON5X = (int) (134 * DOOM.vs.getSafeScaling()); + + ST_WEAPON5Y = (int) (181 * DOOM.vs.getSafeScaling()); + + // WPNS title + ST_WPNSX = (int) (109 * DOOM.vs.getSafeScaling()); + + ST_WPNSY = (int) (191 * DOOM.vs.getSafeScaling()); + + // DETH title + ST_DETHX = (int) (109 * DOOM.vs.getSafeScaling()); + + ST_DETHY = (int) (191 * DOOM.vs.getSafeScaling()); + + ST_RECT = new Rectangle(ST_X, 0, ST_WIDTH, ST_HEIGHT); + //this.plyr=DM.players[DM.] + } + + public void refreshBackground() { + + if (st_statusbaron[0]) { + DOOM.graphicSystem.DrawPatchScaled(SB, sbar, DOOM.vs, ST_X, 0, V_SAFESCALE | V_NOSCALESTART); + //V.DrawPatch(ST_X, 0, BG, sbar); + + if (DOOM.netgame) { + DOOM.graphicSystem.DrawPatchScaled(SB, faceback, DOOM.vs, ST_FX, ST_Y, V_SAFESCALE | V_NOSCALESTART); + //V.DrawPatch(ST_FX, 0, BG, faceback); + } + + // Buffers the background. + DOOM.graphicSystem.CopyRect(SB, ST_RECT, FG, DOOM.graphicSystem.point(ST_X, ST_Y)); + //V.CopyRect(ST_X, 0, SCREEN_SB, ST_WIDTH, ST_HEIGHT, ST_X, ST_Y, SCREEN_FG); + } + + } + + public void Init() { + veryfirsttime = 0; + loadData(); + } + + protected boolean st_stopped = true; + + @Override + @SourceCode.Suspicious(CauseOfDesyncProbability.LOW) + public void Start() { + + if (!st_stopped) { + Stop(); + } + + initData(); + createWidgets(); + st_stopped = false; + } + + public void Stop() { + if (st_stopped) { + return; + } + // Reset palette. + DOOM.graphicSystem.setPalette(0); + + st_stopped = true; + } + + public void loadData() { + lu_palette = DOOM.wadLoader.GetNumForName("PLAYPAL"); + loadGraphics(); + } + + // Filter automap on/off. + @Override + public void NotifyAMEnter() { + st_gamestate = st_stateenum_t.AutomapState; + st_firsttime = true; + } + + @Override + public void NotifyAMExit() { + // fprintf(stderr, "AM exited\n"); + st_gamestate = st_stateenum_t.FirstPersonState; + } + + // Respond to keyboard input events, + // intercept cheats. + @Override + @ST_Stuff.C(ST_Responder) + public boolean Responder(event_t ev) { + if (ev.isType(evtype_t.ev_keydown)) { + if (!DOOM.netgame) { + // b. - enabled for more debug fun. + // if (gameskill != sk_nightmare) { + + // 'dqd' cheat for toggleable god mode + if (ev.ifKeyAsciiChar(cheat_god::CheckCheat)) { + plyr.cheats ^= CF_GODMODE; + if ((plyr.cheats & CF_GODMODE) != 0) { + if (plyr.mo != null) { + plyr.mo.health = 100; + } + + plyr.health[0] = 100; + plyr.message = STSTR_DQDON; + } else { + plyr.message = STSTR_DQDOFF; + } + } // 'fa' cheat for killer fucking arsenal + else if (ev.ifKeyAsciiChar(cheat_ammonokey::CheckCheat)) { + plyr.armorpoints[0] = 200; + plyr.armortype = 2; + + for (int i = 0; i < NUMWEAPONS; i++) { + plyr.weaponowned[i] = true; // true + } + System.arraycopy(plyr.maxammo, 0, plyr.ammo, 0, NUMAMMO); + + plyr.message = STSTR_FAADDED; + } // 'kfa' cheat for key full ammo + else if (ev.ifKeyAsciiChar(cheat_ammo::CheckCheat)) { + plyr.armorpoints[0] = 200; + plyr.armortype = 2; + + for (int i = 0; i < NUMWEAPONS; i++) { + plyr.weaponowned[i] = true; // true + } + System.arraycopy(plyr.maxammo, 0, plyr.ammo, 0, NUMAMMO); + + for (int i = 0; i < NUMCARDS; i++) { + plyr.cards[i] = true; + } + + plyr.message = STSTR_KFAADDED; + } // 'mus' cheat for changing music + else if (ev.ifKeyAsciiChar(cheat_mus::CheckCheat)) { + + char[] buf = new char[3]; + int musnum; + + plyr.message = STSTR_MUS; + cheat_mus.GetParam(buf); + + if (DOOM.isCommercial()) { + musnum + = musicenum_t.mus_runnin.ordinal() + (buf[0] - '0') + * 10 + buf[1] - '0' - 1; + + if (((buf[0] - '0') * 10 + buf[1] - '0') > 35) { + plyr.message = STSTR_NOMUS; + } else { + DOOM.doomSound.ChangeMusic(musnum, true); + } + } else { + musnum + = musicenum_t.mus_e1m1.ordinal() + (buf[0] - '1') * 9 + + (buf[1] - '1'); + + if (((buf[0] - '1') * 9 + buf[1] - '1') > 31) { + plyr.message = STSTR_NOMUS; + } else { + DOOM.doomSound.ChangeMusic(musnum, true); + } + } + } // Simplified, accepting both "noclip" and "idspispopd". + // no clipping mode cheat + else if (ev.ifKeyAsciiChar(cheat_noclip::CheckCheat) || ev.ifKeyAsciiChar(cheat_commercial_noclip::CheckCheat)) { + plyr.cheats ^= CF_NOCLIP; + + if ((plyr.cheats & CF_NOCLIP) != 0) { + plyr.message = STSTR_NCON; + } else { + plyr.message = STSTR_NCOFF; + } + } + // 'behold?' power-up cheats + for (int i = 0; i < 6; i++) { + if (ev.ifKeyAsciiChar(cheat_powerup[i]::CheckCheat)) { + if (plyr.powers[i] == 0) { + plyr.GivePower(i); + } else if (i != pw_strength) { + plyr.powers[i] = 1; + } else { + plyr.powers[i] = 0; + } + + plyr.message = STSTR_BEHOLDX; + } + } + + // 'behold' power-up menu + if (ev.ifKeyAsciiChar(cheat_powerup[6]::CheckCheat)) { + plyr.message = STSTR_BEHOLD; + } // 'choppers' invulnerability & chainsaw + else if (ev.ifKeyAsciiChar(cheat_choppers::CheckCheat)) { + plyr.weaponowned[weapontype_t.wp_chainsaw.ordinal()] = true; + plyr.powers[pw_invulnerability] = 1; // true + plyr.message = STSTR_CHOPPERS; + } // 'mypos' for player position + else if (ev.ifKeyAsciiChar(cheat_mypos::CheckCheat)) { + // MAES: made into a toggleable cheat. + this.st_idmypos = !st_idmypos; + } else if (ev.ifKeyAsciiChar(cheat_tnthom::CheckCheat)) { + // MAES: made into a toggleable cheat. + plyr.message = (DOOM.flashing_hom = !DOOM.flashing_hom) ? "HOM Detection On" + : "HOM Detection Off"; + } + } + + // 'clev' change-level cheat + if (ev.ifKeyAsciiChar(cheat_clev::CheckCheat)) { + char[] buf = new char[3]; + int epsd; + int map; + + cheat_clev.GetParam(buf); + + // This applies to Doom II, Plutonia and TNT. + if (DOOM.isCommercial()) { + epsd = 0; + map = (buf[0] - '0') * 10 + buf[1] - '0'; + } else { + epsd = buf[0] - '0'; + map = buf[1] - '0'; + } + + // Catch invalid maps. + if (epsd < 1 && (!DOOM.isCommercial())) { + return false; + } + + if (map < 1) { + return false; + } + + // Ohmygod - this is not going to work. + if (DOOM.isRetail() + && ((epsd > 4) || (map > 9))) { + return false; + } + + // MAES: If it's doom.wad but not ultimate + if (DOOM.isRegistered() && !DOOM.isRetail() + && ((epsd > 3) || (map > 9))) { + return false; + } + + if (DOOM.isShareware() + && ((epsd > 1) || (map > 9))) { + return false; + } + + if (DOOM.isCommercial() + && ((epsd > 1) || (map > 34))) { + return false; + } + + // So be it. + plyr.message = STSTR_CLEV; + DOOM.DeferedInitNew(DOOM.gameskill, epsd, map); + } + } + return false; + } + + protected int lastcalc; + + protected int oldhealth = -1; + + public int calcPainOffset() { + int health = 0; + + health = plyr.health[0] > 100 ? 100 : plyr.health[0]; + + if (health != oldhealth) { + lastcalc + = ST_FACESTRIDE * (((100 - health) * ST_NUMPAINFACES) / 101); + oldhealth = health; + } + return lastcalc; + } + + protected int lastattackdown = -1; + + protected int priority = 0; + + /** + * This is a not-very-pretty routine which handles the face states and their + * timing. the precedence of expressions is: dead > evil grin > turned head + * > straight ahead + */ + public void updateFaceWidget() { + long badguyangle; // angle_t + long diffang; + + boolean doevilgrin; + + if (priority < 10) { + // dead + if (plyr.health[0] == 0) { + priority = 9; + st_faceindex[0] = ST_DEADFACE; + st_facecount = 1; + } + } + + if (priority < 9) { + if (plyr.bonuscount != 0) { + // picking up bonus + doevilgrin = false; + + for (int i = 0; i < NUMWEAPONS; i++) { + if (oldweaponsowned[i] != plyr.weaponowned[i]) { + doevilgrin = true; + oldweaponsowned[i] = plyr.weaponowned[i]; + } + } + if (doevilgrin) { + // evil grin if just picked up weapon + priority = 8; + st_facecount = ST_EVILGRINCOUNT; + st_faceindex[0] = calcPainOffset() + ST_EVILGRINOFFSET; + } + } + + } + + if (priority < 8) { + if ((plyr.damagecount != 0) && (plyr.attacker != null) + && (plyr.attacker != plyr.mo)) { + // being attacked + priority = 7; + /** + * Another switchable fix of mine + * - Good Sign 2017/04/02 + */ + if ((DOOM.CM.equals(Settings.fix_ouch_face, Boolean.TRUE) + ? st_oldhealth - plyr.health[0] + : plyr.health[0] - st_oldhealth) > ST_MUCHPAIN) { + st_facecount = ST_TURNCOUNT; + st_faceindex[0] = calcPainOffset() + ST_OUCHOFFSET; + } else { + badguyangle + = DOOM.sceneRenderer.PointToAngle2(plyr.mo.x, plyr.mo.y, plyr.attacker.x, + plyr.attacker.y); + boolean obtuse; // that's another "i" + + if (badguyangle > plyr.mo.angle) { + // whether right or left + diffang = badguyangle - plyr.mo.angle; + obtuse = diffang > ANG180; + } else { + // whether left or right + diffang = plyr.mo.angle - badguyangle; + obtuse = diffang <= ANG180; + } // confusing, aint it? + + st_facecount = ST_TURNCOUNT; + st_faceindex[0] = calcPainOffset(); + + if (diffang < ANG45) { + // head-on + st_faceindex[0] += ST_RAMPAGEOFFSET; + } else if (obtuse) { + // turn face right + st_faceindex[0] += ST_TURNOFFSET; + } else { + // turn face left + st_faceindex[0] += ST_TURNOFFSET + 1; + } + } + } + } + + if (priority < 7) { + // getting hurt because of your own damn stupidity + if (plyr.damagecount != 0) { + /** + * Another switchable fix of mine + * - Good Sign 2017/04/02 + */ + if ((DOOM.CM.equals(Settings.fix_ouch_face, Boolean.TRUE) + ? st_oldhealth - plyr.health[0] + : plyr.health[0] - st_oldhealth) > ST_MUCHPAIN) { + priority = 7; + st_facecount = ST_TURNCOUNT; + st_faceindex[0] = calcPainOffset() + ST_OUCHOFFSET; + } else { + priority = 6; + st_facecount = ST_TURNCOUNT; + st_faceindex[0] = calcPainOffset() + ST_RAMPAGEOFFSET; + } + + } + + } + + if (priority < 6) { + // rapid firing + if (plyr.attackdown) { + if (lastattackdown == -1) { + lastattackdown = ST_RAMPAGEDELAY; + } else if (--lastattackdown == 0) { + priority = 5; + st_faceindex[0] = calcPainOffset() + ST_RAMPAGEOFFSET; + st_facecount = 1; + lastattackdown = 1; + } + } else { + lastattackdown = -1; + } + + } + + if (priority < 5) { + // invulnerability + if (((plyr.cheats & CF_GODMODE) != 0) + || (plyr.powers[pw_invulnerability] != 0)) { + priority = 4; + + st_faceindex[0] = ST_GODFACE; + st_facecount = 1; + + } + + } + + // look left or look right if the facecount has timed out + if (st_facecount == 0) { + st_faceindex[0] = calcPainOffset() + (st_randomnumber % 3); + st_facecount = ST_STRAIGHTFACECOUNT; + priority = 0; + } + + st_facecount--; + + } + + protected int largeammo = 1994; // means "n/a" + + /** + * MAES: this code updated the widgets. Now, due to the way they are + * constructed, they originally were "hooked" to actual variables using + * pointers so that they could tap into them directly and self-update. + * Clearly we can't do that in Java unless said variables are inside an + * array and we provide both the array AND an index. For other cases, we + * must simply build ad-hoc hacks. + * + * In any case, only "status" updates are performed here. Actual visual + * updates are performed by the Drawer. + * + */ + public void updateWidgets() { + + int i; + + // MAES: sticky idmypos cheat that is actually useful + // TODO: this spams the player message queue at every tic. + // A direct overlay with a widget would be more useful. + if (this.st_idmypos) { + mobj_t mo = DOOM.players[DOOM.consoleplayer].mo; + plyr.message = String.format("ang= 0x%x; x,y= (%x, %x)", + (int) mo.angle, mo.x, mo.y); + + } + + // must redirect the pointer if the ready weapon has changed. + // if (w_ready.data != plyr.readyweapon) + // { + if (weaponinfo[plyr.readyweapon.ordinal()].ammo == ammotype_t.am_noammo) { + w_ready.numindex = largeammo; + } else { + w_ready.numindex + = weaponinfo[plyr.readyweapon.ordinal()].ammo.ordinal(); + } + + w_ready.data = plyr.readyweapon.ordinal(); + + // if (*w_ready.on) + // STlib_updateNum(&w_ready, true); + // refresh weapon change + // } + // update keycard multiple widgets + for (i = 0; i < 3; i++) { + keyboxes[i] = plyr.cards[i] ? i : -1; + + if (plyr.cards[i + 3]) { + keyboxes[i] = i + 3; + } + } + + // refresh everything if this is him coming back to life + updateFaceWidget(); + + // used by the w_armsbg widget + st_notdeathmatch[0] = !DOOM.deathmatch; + + // used by w_arms[] widgets + st_armson[0] = st_statusbaron[0] && !(DOOM.altdeath || DOOM.deathmatch); + + // used by w_frags widget + st_fragson[0] = (DOOM.altdeath || DOOM.deathmatch) && st_statusbaron[0]; + st_fragscount[0] = 0; + + for (i = 0; i < MAXPLAYERS; i++) { + if (i != DOOM.consoleplayer) { + st_fragscount[0] += plyr.frags[i]; + } else { + st_fragscount[0] -= plyr.frags[i]; + } + } + + // get rid of chat window if up because of message + if (--st_msgcounter == 0) { + st_chat = st_oldchat; + } + + } + + public void Ticker() { + + st_clock++; + st_randomnumber = DOOM.random.M_Random(); + updateWidgets(); + st_oldhealth = plyr.health[0]; + + } + + static int st_palette = 0; + + public void doPaletteStuff() { + + int palette; + //byte[] pal; + int cnt; + int bzc; + + cnt = plyr.damagecount; + + if (plyr.powers[pw_strength] != 0) { + // slowly fade the berzerk out + bzc = 12 - (plyr.powers[pw_strength] >> 6); + + if (bzc > cnt) { + cnt = bzc; + } + } + + if (cnt != 0) { + palette = (cnt + 7) >> 3; + + if (palette >= NUMREDPALS) { + palette = NUMREDPALS - 1; + } + + palette += STARTREDPALS; + } else if (plyr.bonuscount != 0) { + palette = (plyr.bonuscount + 7) >> 3; + + if (palette >= NUMBONUSPALS) { + palette = NUMBONUSPALS - 1; + } + + palette += STARTBONUSPALS; + } else if (plyr.powers[pw_ironfeet] > 4 * 32 + || (plyr.powers[pw_ironfeet] & 8) != 0) { + palette = RADIATIONPAL; + } else { + palette = 0; + } + + if (palette != st_palette) { + st_palette = palette; + DOOM.graphicSystem.setPalette(palette); + } + + } + + public void drawWidgets(boolean refresh) { + int i; + + // used by w_arms[] widgets + st_armson[0] = st_statusbaron[0] && !(DOOM.altdeath || DOOM.deathmatch); + + // used by w_frags widget + st_fragson[0] = DOOM.deathmatch && st_statusbaron[0]; + + w_ready.update(refresh); + + for (i = 0; i < 4; i++) { + w_ammo[i].update(refresh); + w_maxammo[i].update(refresh); + } + + w_armor.update(refresh); + + w_armsbg.update(refresh); + + for (i = 0; i < 6; i++) { + w_arms[i].update(refresh); + } + + w_faces.update(refresh); + + for (i = 0; i < 3; i++) { + w_keyboxes[i].update(refresh); + } + + w_frags.update(refresh); + + w_health.update(refresh); + } + + public void doRefresh() { + + st_firsttime = false; + + // draw status bar background to off-screen buff + refreshBackground(); + + // and refresh all widgets + drawWidgets(true); + + } + + public void diffDraw() { + // update all widgets + drawWidgets(false); + } + + public void Drawer(boolean fullscreen, boolean refresh) { + + st_statusbaron[0] = (!fullscreen) || DOOM.automapactive; + st_firsttime = st_firsttime || refresh; + + // Do red-/gold-shifts from damage/items + doPaletteStuff(); + + // If just after ST_Start(), refresh all + if (st_firsttime) { + doRefresh(); + } // Otherwise, update as little as possible + else { + diffDraw(); + } + + } + + public void loadGraphics() { + + int i; + int j; + int facenum; + + String namebuf; + + // Load the numbers, tall and short + for (i = 0; i < 10; i++) { + namebuf = ("STTNUM" + i); + tallnum[i] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + + namebuf = ("STYSNUM" + i); + shortnum[i] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + + } + + // Load percent key. + // Note: why not load STMINUS here, too? + tallpercent = DOOM.wadLoader.CachePatchName("STTPRCNT", PU_STATIC); + // MAES: in fact, I do this for sanity. Fuck them. Seriously. + sttminus = DOOM.wadLoader.CachePatchName("STTMINUS"); + + // key cards + for (i = 0; i < NUMCARDS; i++) { + namebuf = ("STKEYS" + i); + keys[i] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + } + + // arms background + armsbg = DOOM.wadLoader.CachePatchName("STARMS", PU_STATIC); + + // arms ownership widgets + for (i = 0; i < 6; i++) { + namebuf = ("STGNUM" + (i + 2)); + + // gray # + arms[i][0] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + + // yellow # + arms[i][1] = shortnum[i + 2]; + } + + // face backgrounds for different color players + namebuf = ("STFB" + DOOM.consoleplayer); + faceback = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + + // status bar background bits + sbar = DOOM.wadLoader.CachePatchName("STBAR", PU_STATIC); + + // face states + facenum = 0; + for (i = 0; i < ST_NUMPAINFACES; i++) { + for (j = 0; j < ST_NUMSTRAIGHTFACES; j++) { + namebuf = ("STFST" + (i) + (j)); + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + } + namebuf = "STFTR" + i + "0"; // turn right + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + namebuf = "STFTL" + i + "0"; // turn left + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + namebuf = "STFOUCH" + i; // ouch! + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + namebuf = "STFEVL" + i; // evil grin ;) + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + namebuf = "STFKILL" + i; // pissed off + faces[facenum++] = DOOM.wadLoader.CachePatchName(namebuf, PU_STATIC); + } + faces[facenum++] = DOOM.wadLoader.CachePatchName("STFGOD0", PU_STATIC); + faces[facenum++] = DOOM.wadLoader.CachePatchName("STFDEAD0", PU_STATIC); + + } + + public void unloadGraphics() { + + int i; // unload the numbers, tall and short + for (i = 0; i < 10; i++) { + DOOM.wadLoader.UnlockLumpNum(tallnum[i]); + tallnum[i] = null; + DOOM.wadLoader.UnlockLumpNum(shortnum[i]); + shortnum[i] = null; + } + + // unload tall percent + DOOM.wadLoader.UnlockLumpNum(tallpercent); + tallpercent = null; + + // unload arms background + DOOM.wadLoader.UnlockLumpNum(armsbg); + armsbg = null; + // unload gray #'s + for (i = 0; i < 6; i++) { + DOOM.wadLoader.UnlockLumpNum(arms[i][0]); + arms[i][0] = null; + DOOM.wadLoader.UnlockLumpNum(arms[i][1]); + arms[i][1] = null; + + } + + // unload the key cards for (i=0;i. + */ +package timing; + +/** + * + * @author Good Sign + */ +public class DelegateTicker implements ITicker { + + private final FastTicker ft = new FastTicker(); + private final MilliTicker mt = new MilliTicker(); + private final NanoTicker nt = new NanoTicker(); + private ITicker currentTicker = ft; + + @Override + public int GetTime() { + return currentTicker.GetTime(); + } + + public void changeTicker() { + if (currentTicker == nt) { + currentTicker = mt; + ((MilliTicker) currentTicker).basetime = 0; + ((MilliTicker) currentTicker).oldtics = 0; + } else if (currentTicker == mt) { + currentTicker = ft; + ((FastTicker) currentTicker).fasttic = 0; + } else { + currentTicker = nt; + ((NanoTicker) currentTicker).basetime = 0; + ((NanoTicker) currentTicker).oldtics = 0; + } + } +} \ No newline at end of file diff --git a/doom/src/timing/FastTicker.java b/doom/src/timing/FastTicker.java new file mode 100644 index 0000000..6714ec2 --- /dev/null +++ b/doom/src/timing/FastTicker.java @@ -0,0 +1,15 @@ +package timing; + +public class FastTicker implements ITicker { + + /** + * I_GetTime + * returns time in 1/70th second tics + */ + @Override + public int GetTime() { + return fasttic++; + } + + protected volatile int fasttic = 0; +} \ No newline at end of file diff --git a/doom/src/timing/ITicker.java b/doom/src/timing/ITicker.java new file mode 100644 index 0000000..8d56439 --- /dev/null +++ b/doom/src/timing/ITicker.java @@ -0,0 +1,22 @@ +package timing; + +import doom.CVarManager; +import doom.CommandVariable; +import doom.SourceCode.I_IBM; +import static doom.SourceCode.I_IBM.I_GetTime; + +public interface ITicker { + + static ITicker createTicker(CVarManager CVM) { + if (CVM.bool(CommandVariable.MILLIS)) { + return new MilliTicker(); + } else if (CVM.bool(CommandVariable.FASTTIC) || CVM.bool(CommandVariable.FASTDEMO)) { + return new DelegateTicker(); + } else { + return new NanoTicker(); + } + } + + @I_IBM.C(I_GetTime) + public int GetTime(); +} \ No newline at end of file diff --git a/doom/src/timing/MilliTicker.java b/doom/src/timing/MilliTicker.java new file mode 100644 index 0000000..8fd684b --- /dev/null +++ b/doom/src/timing/MilliTicker.java @@ -0,0 +1,30 @@ +package timing; + +import static data.Defines.TICRATE; + +public class MilliTicker + implements ITicker { + + /** + * I_GetTime + * returns time in 1/70th second tics + */ + @Override + public int GetTime() { + long tp; + //struct timezone tzp; + int newtics; + + tp = System.currentTimeMillis(); + if (basetime == 0) { + basetime = tp; + } + newtics = (int) (((tp - basetime) * TICRATE) / 1000); + return newtics; + } + + protected volatile long basetime = 0; + protected volatile int oldtics = 0; + protected volatile int discrepancies; + +} \ No newline at end of file diff --git a/doom/src/timing/NanoTicker.java b/doom/src/timing/NanoTicker.java new file mode 100644 index 0000000..23acf23 --- /dev/null +++ b/doom/src/timing/NanoTicker.java @@ -0,0 +1,41 @@ +package timing; + +import static data.Defines.TICRATE; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class NanoTicker + implements ITicker { + + private static final Logger LOGGER = Loggers.getLogger(NanoTicker.class.getName()); + + /** + * I_GetTime + * returns time in 1/70th second tics + */ + @Override + public int GetTime() { + long tp; + //struct timezone tzp; + int newtics; + + // Attention: System.nanoTime() might not be consistent across multicore CPUs. + // To avoid the core getting back to the past, + tp = System.nanoTime(); + if (basetime == 0) { + basetime = tp; + } + newtics = (int) (((tp - basetime) * TICRATE) / 1000000000);// + tp.tv_usec*TICRATE/1000000; + if (newtics < oldtics) { + LOGGER.log(Level.WARNING, String.format("Timer discrepancies detected : %d", (++discrepancies))); + return oldtics; + } + return (oldtics = newtics); + } + + protected volatile long basetime = 0; + protected volatile int oldtics = 0; + protected volatile int discrepancies; + +} \ No newline at end of file diff --git a/doom/src/utils/BinarySearch.java b/doom/src/utils/BinarySearch.java new file mode 100644 index 0000000..7952d1f --- /dev/null +++ b/doom/src/utils/BinarySearch.java @@ -0,0 +1,886 @@ +package utils; + +import java.util.Comparator; +import java.util.List; +import java.util.ListIterator; +import java.util.RandomAccess; +import java.util.function.Function; +import java.util.function.IntBinaryOperator; +import java.util.function.IntFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; + +public enum BinarySearch { + ; + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param list of one type of objects + * @param converter from one type of objects to another + * @param key a value of another object type + * @return + */ + public static > int find(final List list, + final Function converter, + final E key) { + return find(list, converter, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param array of one type of objects + * @param converter from one type of objects to another + * @param key a value of another object type + * @return + */ + public static > int find(final T[] array, + final Function converter, + final E key) { + return find(array, converter, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param list of one type of objects + * @param comparator - a comparator for objects of type E + * @param converter from one type of objects to another + * @param key a value of another object type + * @return + */ + public static int find(final List list, + final Function converter, + final Comparator comparator, + final E key) { + return find(list, converter, comparator, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param array of one type of objects + * @param comparator - a comparator for objects of type E + * @param converter from one type of objects to another + * @param key a value of another object type + * @return + */ + public static int find(final T[] array, + final Function converter, + final Comparator comparator, + final E key) { + return find(array, converter, comparator, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive integer + * @param a primitive integer key value + * @return + */ + public static int findByInt(final List list, + final ToIntFunction converter, + final int key) { + return findByInt(list, converter, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive integer + * @param a primitive integer key value + * @return + */ + public static int findByInt(final T[] array, + final ToIntFunction converter, + final int key) { + return findByInt(array, converter, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive integer + * @param comparator - a comparator for primitive integer values + * @param a primitive integer key value + * @return + */ + public static int findByInt(final List list, + final ToIntFunction converter, + final IntBinaryOperator comparator, + final int key) { + return findByInt(list, converter, comparator, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive integer + * @param comparator - a comparator for primitive integer values + * @param a primitive integer key value + * @return + */ + public static int findByInt(final T[] array, + final ToIntFunction converter, + final IntBinaryOperator comparator, + final int key) { + return findByInt(array, converter, comparator, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive long + * @param a primitive long key value + * @return + */ + public static int findByLong(final List list, + final ToLongFunction converter, + final long key) { + return findByLong(list, converter, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive long + * @param a primitive long key value + * @return + */ + public static int findByLong(final T[] array, + final ToLongFunction converter, + final long key) { + return findByLong(array, converter, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive long + * @param comparator - a comparator for primitive long values + * @param a primitive long key value + * @return + */ + public static int findByLong(final List list, + final ToLongFunction converter, + final LongComparator comparator, + final long key) { + return findByLong(list, converter, comparator, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive long + * @param comparator - a comparator for primitive long values + * @param a primitive long key value + * @return + */ + public static int findByLong(final T[] array, + final ToLongFunction converter, + final LongComparator comparator, + final long key) { + return findByLong(array, converter, comparator, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive double + * @param a primitive double key value + * @return + */ + public static int findByDouble(final List list, + final ToDoubleFunction converter, + final double key) { + return findByDouble(list, converter, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive double + * @param a primitive double key value + * @return + */ + public static int findByDouble(final T[] array, + final ToDoubleFunction converter, + final double key) { + return findByDouble(array, converter, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive double + * @param comparator - a comparator for primitive double values + * @param a primitive double key value + * @return + */ + public static int findByDouble(final List list, + final ToDoubleFunction converter, + final DoubleComparator comparator, + final double key) { + return findByDouble(list, converter, comparator, 0, list.size(), key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive double + * @param comparator - a comparator for primitive double values + * @param a primitive double key value + * @return + */ + public static int findByDouble(final T[] array, + final ToDoubleFunction converter, + final DoubleComparator comparator, + final double key) { + return findByDouble(array, converter, comparator, 0, array.length, key); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param list of one type of objects + * @param converter from one type of objects to another + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key a value of another object type + * @return + */ + public static > int find(final List list, + final Function converter, + final int from, final int to, final E key) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> converter.apply(getter.apply(i)).compareTo(key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param array of one type of objects + * @param converter from one type of objects to another + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key a value of another object type + * @return + */ + public static > int find(final T[] array, + final Function converter, + final int from, final int to, final E key) { + rangeCheck(array.length, from, to); + return findByIndex(i -> converter.apply(array[i]).compareTo(key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param list of one type of objects + * @param converter from one type of objects to another + * @param comparator - a comparator for objects of type E + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key a value of another object type + * @return + */ + public static int find(final List list, + final Function converter, + final Comparator comparator, + final int from, final int to, final E key) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> comparator.compare(converter.apply(getter.apply(i)), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using object of another type, given from any object of one type + * a function can get an object of another type + * + * @param array of one type of objects + * @param converter from one type of objects to another + * @param comparator - a comparator for objects of type E + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key a value of another object type + * @return + */ + public static int find(final T[] array, + final Function converter, + final Comparator comparator, + final int from, final int to, final E key) { + rangeCheck(array.length, from, to); + return findByIndex(i -> comparator.compare(converter.apply(array[i]), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive integer + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive integer key value + * @return + */ + public static int findByInt(final List list, + final ToIntFunction converter, + final int from, final int to, final int key) { + final IntFunction getter = listGetter(list); + return findByInt(i -> converter.applyAsInt(getter.apply(i)), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive integer + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive integer key value + * @return + */ + public static int findByInt(final T[] array, + final ToIntFunction converter, + final int from, final int to, final int key) { + rangeCheck(array.length, from, to); + return findByInt(i -> converter.applyAsInt(array[i]), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive integer + * @param comparator - a comparator for primitive integer values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive integer key value + * @return + */ + public static int findByInt(final List list, + final ToIntFunction converter, + final IntBinaryOperator comparator, + final int from, final int to, final int key) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> comparator.applyAsInt(converter.applyAsInt(getter.apply(i)), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive integer, given from any object + * of one type a function can get a primitive integer + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive integer + * @param comparator - a comparator for primitive integer values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive integer key value + * @return + */ + public static int findByInt(final T[] array, + final ToIntFunction converter, + final IntBinaryOperator comparator, + final int from, final int to, final int key) { + rangeCheck(array.length, from, to); + return findByIndex(i -> comparator.applyAsInt(converter.applyAsInt(array[i]), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive long + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive long key value + * @return + */ + public static int findByLong(final List list, + final ToLongFunction converter, + final int from, final int to, final long key) { + final IntFunction getter = listGetter(list); + return findByLong(i -> converter.applyAsLong(getter.apply(i)), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive long + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive long key value + * @return + */ + public static int findByLong(final T[] array, + final ToLongFunction converter, + final int from, final int to, final long key) { + rangeCheck(array.length, from, to); + return findByLong(i -> converter.applyAsLong(array[i]), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive long + * @param comparator - a comparator for primitive long values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive long key value + * @return + */ + public static int findByLong(final List list, + final ToLongFunction converter, + final LongComparator comparator, + final int from, final int to, final long key) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> comparator.compareAsLong(converter.applyAsLong(getter.apply(i)), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive long, given from any object + * of one type a function can get a primitive long + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive long + * @param comparator - a comparator for primitive long values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive long key value + * @return + */ + public static int findByLong(final T[] array, + final ToLongFunction converter, + final LongComparator comparator, + final int from, final int to, final long key) { + rangeCheck(array.length, from, to); + return findByIndex(i -> comparator.compareAsLong(converter.applyAsLong(array[i]), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive double + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive double key value + * @return + */ + public static int findByDouble(final List list, + final ToDoubleFunction converter, + final int from, final int to, final double key) { + final IntFunction getter = listGetter(list); + return findByDouble(i -> converter.applyAsDouble(getter.apply(i)), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive double + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive double key value + * @return + */ + public static int findByDouble(final T[] array, + final ToDoubleFunction converter, + final int from, final int to, final double key) { + rangeCheck(array.length, from, to); + return findByDouble(i -> converter.applyAsDouble(array[i]), from, to, key); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param list of one type of objects + * @param converter from one type of objects to a primitive double + * @param comparator - a comparator for primitive double values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive double key value + * @return + */ + public static int findByDouble(final List list, + final ToDoubleFunction converter, + final DoubleComparator comparator, + final int from, final int to, final double key) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> comparator.compareAsDouble(converter.applyAsDouble(getter.apply(i)), key), from, to); + } + + /** + * Binary search supporting search for one type of objects + * using primitive double, given from any object + * of one type a function can get a primitive double + * + * @param array of one type of objects + * @param converter from one type of objects to a primitive double + * @param comparator - a comparator for primitive double values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param a primitive double key value + * @return + */ + public static int findByDouble(final T[] array, + final ToDoubleFunction converter, + final DoubleComparator comparator, + final int from, final int to, final double key) { + rangeCheck(array.length, from, to); + return findByIndex(i -> comparator.compareAsDouble(converter.applyAsDouble(array[i]), key), from, to); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by some key object, using the getter + * who, given an index in the invisible structure, can produce a key + * object someway used to sort it. + * + * @param getter - a function accepting indexes, producing a key object used for sort + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a key object + */ + public static > int find(final IntFunction getter, + final int from, final int to, final E key) { + return findByIndex(i -> getter.apply(i).compareTo(key), from, to); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by some key object, using the getter + * who, given an index in the invisible structure, can produce a key + * object someway used to sort it. + * + * @param getter - a function accepting indexes, producing a key object used for sort + * @param comparator - a comparator for objects of type E + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a key object + */ + public static int find(final IntFunction getter, + final Comparator comparator, + final int from, final int to, final E key) { + return findByIndex(i -> comparator.compare(getter.apply(i), key), from, to); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive integer key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive integer key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive integer used for sort + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive integer key + */ + public static int findByInt(final IntUnaryOperator getter, + final int from, final int to, final int key) { + return findByInt(getter, Integer::compare, from, to, key); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive integer key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive integer key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive integer used for sort + * @param comparator - a comparator for primitive integers + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive integer key + */ + public static int findByInt(final IntUnaryOperator getter, + final IntBinaryOperator comparator, + final int from, final int to, final int key) { + return findByIndex(i -> comparator.applyAsInt(getter.applyAsInt(i), key), from, to); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive long key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive long key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive long used for sort + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive long key + */ + public static int findByLong(final LongGetter getter, + final int from, final int to, final long key) { + return findByLong(getter, Long::compare, from, to, key); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive long key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive long key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive long used for sort + * @param comparator - a comparator for primitive long values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive long key + */ + public static int findByLong(final LongGetter getter, + final LongComparator comparator, + final int from, final int to, final long key) { + return findByIndex(i -> comparator.compareAsLong(getter.getAsLong(i), key), from, to); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive double key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive double key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive double used for sort + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive double key + */ + public static int findByDouble(final DoubleGetter getter, + final int from, final int to, final double key) { + return findByDouble(getter, Double::compare, from, to, key); + } + + /** + * Blind binary search, presuming there is some sorted structure, + * whose sorting is someway ensured by primitive double key, + * using the getter who, given an index in the invisible structure, can produce + * the primitive double key someway used to sort it. + * + * @param getter - a function accepting indexes, producing a primitive double used for sort + * @param comparator - a comparator for primitive double values + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + * @param key - a primitive double key + */ + public static int findByDouble(final DoubleGetter getter, + final DoubleComparator comparator, + final int from, final int to, final double key) { + return findByIndex(i -> comparator.compareAsDouble(getter.getAsDouble(i), key), from, to); + } + + /** + * Blind binary search applying array elements to matching function until it returns 0 + * @param list of one type of objects + * @param matcher - a matcher returning comparison result based on single list element + **/ + public static int findByMatch(final T[] array, + final ToIntFunction matcher) { + return findByMatch(array, matcher, 0, array.length); + } + + /** + * Blind binary search applying List elements to matching function until it returns 0 + * @param list of one type of objects + * @param matcher - a matcher returning comparison result based on single list element + **/ + public static int findByMatch(final List list, + final ToIntFunction matcher) { + return findByMatch(list, matcher, 0, list.size()); + } + + /** + * Blind binary search applying array elements to matching function until it returns 0 + * @param list of one type of objects + * @param matcher - a matcher returning comparison result based on single list element + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + **/ + public static int findByMatch(final T[] array, + final ToIntFunction matcher, + final int from, + final int to) { + rangeCheck(array.length, from, to); + return findByIndex(i -> matcher.applyAsInt(array[i]), from, to); + } + + /** + * Blind binary search applying List elements to matching function until it returns 0 + * @param list of one type of objects + * @param matcher - a matcher returning comparison result based on single list element + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + **/ + public static int findByMatch(final List list, + final ToIntFunction matcher, + final int from, + final int to) { + final IntFunction getter = listGetter(list); + return findByIndex(i -> matcher.applyAsInt(getter.apply(i)), from, to); + } + + /** + * Blind binary search applying index to comparison function until it returns 0 + * @param comparator - index-comparing function + * @param from - an index (inclusive) from which to start search + * @param to - an index (exclusive) from which to start search + **/ + public static int findByIndex(final IntUnaryOperator comparator, final int from, final int to) { + int low = from; + int high = to - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + int cmp = comparator.applyAsInt(mid); + + if (cmp < 0) { + low = mid + 1; + } else if (cmp > 0) { + high = mid - 1; + } else { + return mid; // key found + } + } + return -(low + 1); // key not found + } + + /** + * A copy of Arrays.rangeCheck private method from JDK + */ + private static void rangeCheck(int arrayLength, int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IllegalArgumentException( + "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + if (fromIndex < 0) { + throw new ArrayIndexOutOfBoundsException(fromIndex); + } + if (toIndex > arrayLength) { + throw new ArrayIndexOutOfBoundsException(toIndex); + } + } + + /** + * A copy of Collections.get private method from JDK + */ + private static T get(ListIterator i, int index) { + T obj = null; + int pos = i.nextIndex(); + if (pos <= index) { + do { + obj = i.next(); + } while (pos++ < index); + } else { + do { + obj = i.previous(); + } while (--pos > index); + } + return obj; + } + + private static > IntFunction listGetter(final L list) { + if (list instanceof RandomAccess) { + return ((List) list)::get; + } + + final ListIterator it = list.listIterator(); + return i -> get(it, i); + } + + @FunctionalInterface + public interface LongComparator { + + int compareAsLong(long f1, long f2); + } + + @FunctionalInterface + public interface DoubleComparator { + + int compareAsDouble(double f1, double f2); + } + + @FunctionalInterface + public interface LongGetter { + + long getAsLong(int i); + } + + @FunctionalInterface + public interface DoubleGetter { + + double getAsDouble(int i); + } +} \ No newline at end of file diff --git a/doom/src/utils/C2JUtils.java b/doom/src/utils/C2JUtils.java new file mode 100644 index 0000000..8650a05 --- /dev/null +++ b/doom/src/utils/C2JUtils.java @@ -0,0 +1,856 @@ +package utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import p.Resettable; +import w.InputStreamSugar; + +/** + * Some utilities that emulate C stlib methods or provide convenient functions + * to do repetitive system and memory related stuff. + * + * @author Maes + */ +public final class C2JUtils { + + private static final Logger LOGGER = Loggers.getLogger(C2JUtils.class.getName()); + + public static char[] strcpy(char[] s1, final char[] s2) { + System.arraycopy(s2, 0, s1, 0, Math.min(s1.length, s2.length)); + return s1; + } + + public static char[] strcpy(char[] s1, final char[] s2, int off, int len) { + for (int i = 0; i < len; i++) { + s1[i] = s2[i + off]; + } + return s1; + } + + public static char[] strcpy(char[] s1, final char[] s2, int off) { + for (int i = 0; i < Math.min(s1.length, s2.length - off); i++) { + s1[i] = s2[i + off]; + } + return s1; + } + + public static char[] strcpy(char[] s1, String s2) { + for (int i = 0; i < Math.min(s1.length, s2.length()); i++) { + s1[i] = s2.charAt(i); + } + return s1; + } + + /** Return a byte[] array from the string's chars, + * ANDed to the lowest 8 bits. + * + * @param str + * @return + */ + public static byte[] toByteArray(String str) { + byte[] retour = new byte[str.length()]; + for (int i = 0; i < str.length(); i++) { + retour[i] = (byte) (str.charAt(i) & 0xFF); + } + return retour; + } + + /** + * Finds index of first element of array matching key. Useful whenever an + * "indexOf" property is required or you encounter the C-ism [pointer- + * array_base] used to find array indices in O(1) time. However, since this + * is just a dumb unsorted search, running time is O(n), so use this method + * only sparingly and in scenarios where it won't occur very frequently + * -once per level is probably OK-, but watch out for nested loops, and + * cache the result whenever possible. Consider adding an index or ID type + * of field to the searched type if you require to use this property too + * often. + * + * @param array + * @param key + * @return + */ + public static int indexOf(Object[] array, Object key) { + for (int i = 0; i < array.length; i++) { + if (array[i] == key) { + return i; + } + } + + return -1; + } + + /** + * Emulates C-style "string comparison". "Strings" are considered + * null-terminated, and comparison is performed only up to the smaller of + * the two. + * + * @param s1 + * @param s2 + * @return + */ + public static boolean strcmp(char[] s1, final char[] s2) { + boolean match = true; + for (int i = 0; i < Math.min(s1.length, s2.length); i++) { + if (s1[i] != s2[i]) { + match = false; + break; + } + } + return match; + } + + public static boolean strcmp(char[] s1, String s2) { + return strcmp(s1, s2.toCharArray()); + } + + /** + * C-like string length (null termination). + * + * @param s1 + * @return + */ + public static int strlen(char[] s1) { + if (s1 == null) { + return 0; + } + int len = 0; + + while (s1[len++] > 0) { + if (len >= s1.length) { + break; + } + } + + return len - 1; + } + + /** + * Return a new String based on C-like null termination. + * + * @param s + * @return + */ + public static String nullTerminatedString(char[] s) { + if (s == null) { + return ""; + } + int len = 0; + + while (s[len++] > 0) { + if (len >= s.length) { + break; + } + } + + return new String(s, 0, len - 1); + } + + /** + * Automatically "initializes" arrays of objects with their default + * constuctor. It's better than doing it by hand, IMO. If you have a better + * way, be my guest. + * + * @param os + * @param c + * @throws Exception + * @throws + */ + public static void initArrayOfObjects(T[] os, Class c) { + try { + for (int i = 0; i < os.length; i++) { + os[i] = c.getDeclaredConstructor().newInstance(); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to allocate %d objects of class %s", os.length, c.getName()), e); + System.exit(-1); + } + } + + /** + * Automatically "initializes" arrays of objects with their default + * constuctor. It's better than doing it by hand, IMO. If you have a better + * way, be my guest. + * + * @param os + * @throws Exception + * @throws + */ + @Deprecated + public static void initArrayOfObjects(T[] os) { + @SuppressWarnings("unchecked") + Class c = (Class) os.getClass().getComponentType(); + try { + for (int i = 0; i < os.length; i++) { + os[i] = c.getDeclaredConstructor().newInstance(); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to allocate %d objects of class %s", os.length, c.getName()), e); + System.exit(-1); + } + } + + /** + * Use of this method is very bad. It prevents refactoring measures. Also, + * the use of reflection is acceptable on initialization, but in runtime it + * causes performance loss. Use instead: + * SomeType[] array = new SomeType[length]; + * Arrays.setAll(array, i -> new SomeType()); + * + * - Good Sign 2017/05/07 + * + * Uses reflection to automatically create and initialize an array of + * objects of the specified class. Does not require casting on "reception". + * + * @param + * @param c + * @param num + * @return + * @return + */ + @Deprecated + public static T[] createArrayOfObjects(Class c, int num) { + T[] os; + + os = getNewArray(c, num); + + try { + for (int i = 0; i < os.length; i++) { + os[i] = c.getDeclaredConstructor().newInstance(); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to instantiate %d objects of class %s", os.length, c.getName()), e); + System.exit(-1); + } + + return os; + } + + /** + * Uses reflection to automatically create and initialize an array of + * objects of the specified class. Does not require casting on "reception". + * Requires an instance of the desired class. This allows getting around + * determining the runtime type of parametrized types. + * + * + * @param + * @param instance An instance of a particular class. + * @param num + * @return + * @return + */ + @SuppressWarnings("unchecked") + public static T[] createArrayOfObjects(T instance, int num) { + T[] os; + + Class c = (Class) instance.getClass(); + + os = getNewArray(c, num); + + try { + for (int i = 0; i < os.length; i++) { + os[i] = c.getDeclaredConstructor().newInstance(); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to instantiate %d objects of class %s", os.length, c.getName()), e); + System.exit(-1); + } + + return os; + } + + /** + * Automatically "initializes" arrays of objects with their default + * constuctor. It's better than doing it by hand, IMO. If you have a better + * way, be my guest. + * + * @param os + * @param startpos inclusive + * @param endpos non-inclusive + * @throws Exception + * @throws + */ + public static void initArrayOfObjects(T[] os, int startpos, int endpos) { + @SuppressWarnings("unchecked") + Class c = (Class) os.getClass().getComponentType(); + try { + for (int i = startpos; i < endpos; i++) { + os[i] = c.getDeclaredConstructor().newInstance(); + } + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to allocate %d objects of class %s", os.length, c.getName()), e); + System.exit(-1); + } + } + + /** This method gets eventually inlined, becoming very fast */ + public static int toUnsignedByte(byte b) { + return (0x000000FF & b); + } + + // Optimized array-fill methods designed to operate like C's memset. + public static void memset(boolean[] array, boolean value, int len) { + if (len > 0) { + array[0] = value; + } + for (int i = 1; i < len; i += i) { + System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i); + } + } + + public static void memset(byte[] array, byte value, int len) { + if (len > 0) { + array[0] = value; + } + for (int i = 1; i < len; i += i) { + System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i); + } + } + + public static void memset(char[] array, char value, int len) { + if (len > 0) { + array[0] = value; + } + for (int i = 1; i < len; i += i) { + System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i); + } + } + + public static void memset(int[] array, int value, int len) { + if (len > 0) { + array[0] = value; + } + for (int i = 1; i < len; i += i) { + System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i); + } + } + + public static void memset(short[] array, short value, int len) { + if (len > 0) { + array[0] = value; + } + for (int i = 1; i < len; i += i) { + System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i); + } + } + + public static long unsigned(int num) { + return 0xFFFFFFFFL & num; + } + + public static char unsigned(short num) { + return (char) num; + } + + /** + * Convenient alias for System.arraycopy(src, 0, dest, 0, length); + * + * @param dest + * @param src + * @param length + */ + @SuppressWarnings("SuspiciousSystemArraycopy") + public static void memcpy(Object dest, Object src, int length) { + System.arraycopy(src, 0, dest, 0, length); + } + + public static boolean testReadAccess(String uri) { + InputStream in; + + // This is bullshit. + if (uri == null) { + return false; + } + if (uri.length() == 0) { + return false; + } + + try { + in = new FileInputStream(uri); + } catch (FileNotFoundException e) { + // Not a file... + URL u; + try { + u = new URI(uri).toURL(); + } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e1) { + return false; + } + try { + in = u.openConnection().getInputStream(); + } catch (IOException e1) { + return false; + } + + } + + if (in != null) { + try { + in.close(); + } catch (IOException e) { + + } + return true; + } + // All is well. Go on... + return true; + + } + + public static boolean testWriteAccess(String uri) { + OutputStream out; + + // This is bullshit. + if (uri == null) { + return false; + } + if (uri.length() == 0) { + return false; + } + + try { + out = new FileOutputStream(uri); + } catch (FileNotFoundException e) { + // Not a file... + URL u; + try { + u = new URI(uri).toURL(); + } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e1) { + return false; + } + try { + out = u.openConnection().getOutputStream(); + } catch (IOException e1) { + return false; + } + + } + + if (out != null) { + try { + out.close(); + } catch (IOException e) { + + } + return true; + } + // All is well. Go on... + return true; + } + + /** + * Returns true if flags are included in arg. Synonymous with (flags & + * arg)!=0 + * + * @param flags + * @param arg + * @return + */ + public static boolean flags(int flags, int arg) { + return ((flags & arg) != 0); + } + + public static boolean flags(long flags, long arg) { + return ((flags & arg) != 0); + } + + /** + * Returns 1 for true and 0 for false. Useful, given the amount of + * "arithmetic" logical functions in legacy code. Synonymous with + * (expr?1:0); + * + * @param flags + * @param arg + * @return + */ + public static int eval(boolean expr) { + return (expr ? 1 : 0); + } + + /** + * Returns 1 for non-null and 0 for null objects. Useful, given the amount + * of "existential" logical functions in legacy code. Synonymous with + * (expr!=null); + * + * @param flags + * @param arg + * @return + */ + public static boolean eval(Object expr) { + return (expr != null); + } + + /** + * Returns true for expr!=0, false otherwise. + * + * @param flags + * @param arg + * @return + */ + public static boolean eval(int expr) { + return expr != 0; + } + + /** + * Returns true for expr!=0, false otherwise. + * + * @param flags + * @param arg + * @return + */ + public static boolean eval(long expr) { + return expr != 0; + } + + public static void resetAll(final Resettable[] r) { + for (final Resettable r1 : r) { + r1.reset(); + } + } + + /** + * Useful for unquoting strings, since StringTokenizer won't do it for us. + * Returns null upon any failure. + * + * @param s + * @param c + * @return + */ + public static String unquote(String s, char c) { + + int firstq = s.indexOf(c); + int lastq = s.lastIndexOf(c); + // Indexes valid? + if (isQuoted(s, c)) { + return s.substring(firstq + 1, lastq); + } + + return null; + } + + public static boolean isQuoted(String s, char c) { + + int q1 = s.indexOf(c); + int q2 = s.lastIndexOf(c); + char c1, c2; + + // Indexes valid? + if (q1 != -1 && q2 != -1) { + if (q1 < q2) { + c1 = s.charAt(q1); + c2 = s.charAt(q2); + return (c1 == c2); + } + } + + return false; + } + + public static String unquoteIfQuoted(String s, char c) { + + String tmp = unquote(s, c); + if (tmp != null) { + return tmp; + } + return s; + } + + /** + * Return either 0 or a hashcode + * + * @param o + */ + public static int pointer(Object o) { + if (o == null) { + return 0; + } else { + return o.hashCode(); + } + } + + public static boolean checkForExtension(String filename, String ext) { + + // Null filenames satisfy null extensions. + if ((filename == null || filename.isEmpty()) && (ext == null || ext.isEmpty())) { + return true; + } else if (filename == null) { // avoid NPE - Good Sign 2017/05/07 + filename = ""; + } + + String separator = System.getProperty("file.separator"); + + // Remove the path upto the filename. + int lastSeparatorIndex = filename.lastIndexOf(separator); + if (lastSeparatorIndex != -1) { + filename = filename.substring(lastSeparatorIndex + 1); + } + + String realext; + + // Get extension separator. It SHOULD be . on all platforms, right? + int pos = filename.lastIndexOf('.'); + + if (pos >= 0 && pos <= filename.length() - 2) { // Extension present + + // Null comparator on valid extension + if (ext == null || ext.isEmpty()) { + return false; + } + + realext = filename.substring(pos + 1); + return realext.compareToIgnoreCase(ext) == 0; + } else if (ext == null || ext.isEmpty()) { // No extension, and null/empty comparator + return true; + } + + // No extension, and non-null/nonempty comparator. + return false; + } + + /** Return the filename without extension, and stripped + * of the path. + * + * @param s + * @return + */ + public static String removeExtension(String s) { + + String separator = System.getProperty("file.separator"); + String filename; + + // Remove the path upto the filename. + int lastSeparatorIndex = s.lastIndexOf(separator); + if (lastSeparatorIndex == -1) { + filename = s; + } else { + filename = s.substring(lastSeparatorIndex + 1); + } + + // Remove the extension. + int extensionIndex = filename.lastIndexOf('.'); + if (extensionIndex == -1) { + return filename; + } + + return filename.substring(0, extensionIndex); + } + + /** + * This method is supposed to return the "name" part of a filename. It was + * intended to return length-limited (max 8 chars) strings to use as lump + * indicators. There's normally no need to enforce this behavior, as there's + * nothing preventing the engine from INTERNALLY using lump names with >8 + * chars. However, just to be sure... + * + * @param path + * @param limit Set to any value >0 to enforce a length limit + * @param whole keep extension if set to true + * @return + */ + public static String extractFileBase(String path, int limit, boolean whole) { + + if (path == null) { + return path; + } + + int src = path.length() - 1; + + String separator = System.getProperty("file.separator"); + src = path.lastIndexOf(separator) + 1; + + if (src < 0) // No separator + { + src = 0; + } + + int len = path.lastIndexOf('.'); + if (whole || len < 0) { + len = path.length() - src; // No extension. + } else { + len -= src; + } + + // copy UP to the specific number of characters, or all + if (limit > 0) { + len = Math.min(limit, len); + } + + return path.substring(src, src + len); + } + + /** Maes: File instead of "inthandle" */ + public static long filelength(File handle) { + try { + return handle.length(); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error fstating", e); + return -1; + } + + } + + @SuppressWarnings("unchecked") + public static T[] resize(T[] oldarray, int newsize) { + if (oldarray[0] != null) { + return resize(oldarray[0], oldarray, newsize); + } + + T cls; + try { + cls = (T) oldarray.getClass().getComponentType().getDeclaredConstructor().newInstance(); + return resize(cls, oldarray, newsize); + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + LOGGER.log(Level.SEVERE, "Cannot autodetect type in resizeArray.", e); + return null; + } + } + + /** Generic array resizing method. Calls Arrays.copyOf but then also + * uses initArrayOfObject for the "abundant" elements. + * + * @param + * @param instance + * @param oldarray + * @param newsize + * @return + */ + public static T[] resize(T instance, T[] oldarray, int newsize) { + // Hmm... nope. + if (newsize <= oldarray.length) { + return oldarray; + } + + // Copy old array with built-in stuff. + T[] tmp = Arrays.copyOf(oldarray, newsize); + + // Init the null portions as well + C2JUtils.initArrayOfObjects(tmp, oldarray.length, tmp.length); + LOGGER.log(Level.INFO, String.format("Old array of type %s resized. New capacity: %d", instance.getClass(), newsize)); + + return tmp; + } + + /** Resize an array without autoinitialization. Same as Arrays.copyOf(..), just + * prints a message. + * + * @param + * @param oldarray + * @param newsize + * @return + */ + public static T[] resizeNoAutoInit(T[] oldarray, int newsize) { + // For non-autoinit types, this is enough. + T[] tmp = Arrays.copyOf(oldarray, newsize); + + LOGGER.log(Level.INFO, String.format("Old array of type %s resized without auto-init. New capacity: %d", + tmp.getClass().getComponentType(), newsize)); + + return tmp; + } + + @SuppressWarnings("unchecked") + public static T[] getNewArray(T instance, int size) { + Class c = (Class) instance.getClass(); + + try { + return (T[]) Array.newInstance(c, size); + } catch (NegativeArraySizeException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to allocate %d objects of class %s", size, c.getName()), e); + System.exit(-1); + } + + return null; + } + + public final static T[] getNewArray(int size, T instance) { + @SuppressWarnings("unchecked") + Class c = (Class) instance.getClass(); + return getNewArray(c, size); + } + + @SuppressWarnings("unchecked") + public final static T[] getNewArray(Class c, int size) { + try { + return (T[]) Array.newInstance(c, size); + } catch (NegativeArraySizeException e) { + LOGGER.log(Level.SEVERE, String.format("Failure to allocate %d objects of class %s", size, c.getName()), e); + System.exit(-1); + } + + return null; + } + + /** + * Try to guess whether a URI represents a local file, a network any of the + * above but zipped. Returns + * + * @param uri + * @return an int with flags set according to InputStreamSugar + */ + public static int guessResourceType(String uri) { + + int result = 0; + InputStream in; + + // This is bullshit. + if (uri == null || uri.length() == 0) { + return InputStreamSugar.BAD_URI; + } + + try { + in = new FileInputStream(new File(uri)); + // It's a file + result |= InputStreamSugar.FILE; + } catch (FileNotFoundException e) { + // Not a file... + URL u; + try { + u = new URI(uri).toURL(); + } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e1) { + return InputStreamSugar.BAD_URI; + } + try { + in = u.openConnection().getInputStream(); + result |= InputStreamSugar.NETWORK_FILE; + } catch (IOException e1) { + return InputStreamSugar.BAD_URI; + } + + } + + // Try guessing if it's a ZIP file. A bit lame, really + // TODO: add proper validation, and maybe MIME type checking + // for network streams, for cases that we can't really + // tell from extension alone. + if (checkForExtension(uri, "zip")) { + result |= InputStreamSugar.ZIP_FILE; + + } + + try { + in.close(); + } catch (IOException e) { + + } + + // All is well. Go on... + return result; + } + + private C2JUtils() { + } +} \ No newline at end of file diff --git a/doom/src/utils/GenericCopy.java b/doom/src/utils/GenericCopy.java new file mode 100644 index 0000000..107c950 --- /dev/null +++ b/doom/src/utils/GenericCopy.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package utils; + +import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.function.Supplier; + +public class GenericCopy { + + private static final boolean[] BOOL_0 = {false}; + private static final byte[] BYTE_0 = {0}; + private static final short[] SHORT_0 = {0}; + private static final char[] CHAR_0 = {0}; + private static final int[] INT_0 = {0}; + private static final float[] FLOAT_0 = {0}; + private static final long[] LONG_0 = {0}; + private static final double[] DOUBLE_0 = {0}; + + public static void memset(long[] array, int start, int length, long... value) { + if (length > 0) { + if (value.length == 0) { + value = LONG_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(int[] array, int start, int length, int... value) { + if (length > 0) { + if (value.length == 0) { + value = INT_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(short[] array, int start, int length, short... value) { + if (length > 0) { + if (value.length == 0) { + value = SHORT_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(char[] array, int start, int length, char... value) { + if (length > 0) { + if (value.length == 0) { + value = CHAR_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(byte[] array, int start, int length, byte... value) { + if (length > 0) { + if (value.length == 0) { + value = BYTE_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(double[] array, int start, int length, double... value) { + if (length > 0) { + if (value.length == 0) { + value = DOUBLE_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(float[] array, int start, int length, float... value) { + if (length > 0) { + if (value.length == 0) { + value = FLOAT_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + public static void memset(boolean[] array, int start, int length, boolean... value) { + if (length > 0) { + if (value.length == 0) { + value = BOOL_0; + } + System.arraycopy(value, 0, array, start, value.length); + + for (int i = value.length; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + @SuppressWarnings("SuspiciousSystemArraycopy") + public static void memset(T array, int start, int length, T value, int valueStart, int valueLength) { + if (length > 0 && valueLength > 0) { + System.arraycopy(value, valueStart, array, start, valueLength); + + for (int i = valueLength; i < length; i += i) { + System.arraycopy(array, start, array, start + i, ((length - i) < i) ? (length - i) : i); + } + } + } + + @SuppressWarnings("SuspiciousSystemArraycopy") + public static void memcpy(T srcArray, int srcStart, T dstArray, int dstStart, int length) { + System.arraycopy(srcArray, srcStart, dstArray, dstStart, length); + } + + public static T[] malloc(final ArraySupplier supplier, final IntFunction generator, final int length) { + final T[] array = generator.apply(length); + Arrays.setAll(array, supplier::getWithInt); + return array; + } + + public interface ArraySupplier extends Supplier { + + default T getWithInt(int ignoredInt) { + return get(); + } + } + + private GenericCopy() { + } +} \ No newline at end of file diff --git a/doom/src/utils/OSValidator.java b/doom/src/utils/OSValidator.java new file mode 100644 index 0000000..1795ac5 --- /dev/null +++ b/doom/src/utils/OSValidator.java @@ -0,0 +1,46 @@ +package utils; + +/** Half-assed way of finding the OS we're running under, shamelessly + * ripped from: + * + * http://www.mkyong.com/java/how-to-detect-os-in-java-systemgetpropertyosname/ + * . + * This is required, as some things in AWT don't work exactly consistently cross-OS + * (AWT frame size is the first thing that goes wrong, but also mouse grabbing + * behavior). + * + * TODO: replace with Apache Commons library? + * + * @author velktron + * + */ +public class OSValidator { + + public static boolean isWindows() { + + String os = System.getProperty("os.name").toLowerCase(); + //windows + return (os.contains("win")); + + } + + public static boolean isMac() { + + String os = System.getProperty("os.name").toLowerCase(); + //Mac + return (os.contains("mac")); + + } + + public static boolean isUnix() { + + String os = System.getProperty("os.name").toLowerCase(); + //linux or unix + return (os.contains("nix") || os.contains("nux")); + + } + + public static boolean isUnknown() { + return (!isWindows() && !isUnix() && !isMac()); + } +} \ No newline at end of file diff --git a/doom/src/utils/OrderedExecutor.java b/doom/src/utils/OrderedExecutor.java new file mode 100644 index 0000000..3a47e11 --- /dev/null +++ b/doom/src/utils/OrderedExecutor.java @@ -0,0 +1,117 @@ +package utils; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * An executor that make sure tasks submitted with the same key + * will be executed in the same order as task submission + * (order of calling the {@link #submit(Object, Runnable)} method). + * + * Tasks submitted will be run in the given {@link Executor}. + * There is no restriction on how many threads in the given {@link Executor} + * needs to have (it can be single thread executor as well as a cached thread pool). + * + * If there are more than one thread in the given {@link Executor}, tasks + * submitted with different keys may be executed in parallel, but never + * for tasks submitted with the same key. + * + * * @param type of keys. + */ +public class OrderedExecutor { + + private static final Logger LOGGER = Loggers.getLogger(OrderedExecutor.class.getName()); + + private final Executor executor; + private final Map tasks; + + /** + * Constructs a {@code OrderedExecutor}. + * + * @param executor tasks will be run in this executor. + */ + public OrderedExecutor(Executor executor) { + this.executor = executor; + this.tasks = new HashMap<>(); + } + + /** + * Adds a new task to run for the given key. + * + * @param key the key for applying tasks ordering. + * @param runnable the task to run. + */ + public synchronized void submit(K key, Runnable runnable) { + Task task = tasks.get(key); + if (task == null) { + task = new Task(); + tasks.put(key, task); + } + task.add(runnable); + } + + /** + * Private inner class for running tasks for each key. + * Each key submitted will have one instance of this class. + */ + private class Task implements Runnable { + + private final Lock lock; + private final Queue queue; + + Task() { + this.lock = new ReentrantLock(); + this.queue = new LinkedList<>(); + } + + public void add(Runnable runnable) { + boolean runTask; + lock.lock(); + try { + // Run only if no job is running. + runTask = queue.isEmpty(); + queue.offer(runnable); + } finally { + lock.unlock(); + } + if (runTask) { + executor.execute(this); + } + } + + @Override + public void run() { + // Pick a task to run. + Runnable runnable; + lock.lock(); + try { + runnable = queue.peek(); + } finally { + lock.unlock(); + } + try { + runnable.run(); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "OrderedExecutor run failure", ex); + } + // Check to see if there are queued task, if yes, submit for execution. + lock.lock(); + try { + queue.poll(); + if (!queue.isEmpty()) { + executor.execute(this); + } + } finally { + lock.unlock(); + } + } + } +} \ No newline at end of file diff --git a/doom/src/utils/ParseString.java b/doom/src/utils/ParseString.java new file mode 100644 index 0000000..525398e --- /dev/null +++ b/doom/src/utils/ParseString.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package utils; + +import java.util.Optional; + +/** + * @author Good Sign + */ +public enum ParseString { + ; + public static Object parseString(String stringSource) { + final Optional qt = QuoteType.getQuoteType(stringSource); + final boolean quoted = qt.isPresent(); + if (quoted) { + stringSource = qt.get().unQuote(stringSource); + } + + if (quoted && stringSource.length() == 1) { + final Character test = stringSource.charAt(0); + if (test >= 0 && test < 255) { + return test; + } + } + + Optional ret = checkInt(stringSource); + if (!ret.isPresent()) { + ret = checkDouble(stringSource); + if (!ret.isPresent()) { + ret = checkBoolean(stringSource); + if (!ret.isPresent()) { + return stringSource; + } + } + } + + return ret.get(); + } + + public static Optional checkInt(final String stringSource) { + Optional ret; + try { + long longRet = Long.parseLong(stringSource); + return longRet < Integer.MAX_VALUE + ? Optional.of((int) longRet) + : Optional.of(longRet); + } catch (NumberFormatException e) { + } + + try { + long longRet = Long.decode(stringSource); + return longRet < Integer.MAX_VALUE + ? Optional.of((int) longRet) + : Optional.of(longRet); + } catch (NumberFormatException e) { + } + + return Optional.empty(); + } + + public static Optional checkDouble(final String stringSource) { + try { + return Optional.of(Double.parseDouble(stringSource)); + } catch (NumberFormatException e) { + } + + return Optional.empty(); + } + + public static Optional checkBoolean(final String stringSource) { + try { + return Optional.of(Boolean.parseBoolean(stringSource)); + } catch (NumberFormatException e) { + } + + if ("false".compareToIgnoreCase(stringSource) == 0) { + return Optional.of(Boolean.FALSE); + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/doom/src/utils/QuoteType.java b/doom/src/utils/QuoteType.java new file mode 100644 index 0000000..55c9086 --- /dev/null +++ b/doom/src/utils/QuoteType.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package utils; + +import java.util.Optional; + +/** + * @author Good Sign + */ +public enum QuoteType { + SINGLE('\''), DOUBLE('"'); + public final char quoteChar; + + QuoteType(final char quoteChar) { + this.quoteChar = quoteChar; + } + + public boolean isQuoted(final String s) { + return C2JUtils.isQuoted(s, quoteChar); + } + + public String unQuote(final String s) { + return C2JUtils.unquote(s, quoteChar); + } + + public static Optional getQuoteType(final String stringSource) { + if (stringSource.length() > 2) { + for (final QuoteType type : QuoteType.values()) { + if (type.isQuoted(stringSource)) { + return Optional.of(type); + } + } + } + + return Optional.empty(); + } +} \ No newline at end of file diff --git a/doom/src/utils/ResourceIO.java b/doom/src/utils/ResourceIO.java new file mode 100644 index 0000000..63c34d8 --- /dev/null +++ b/doom/src/utils/ResourceIO.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package utils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * Resource IO to automate read/write on configuration/resources + * + * @author Good Sign + */ +public class ResourceIO { + + private static final Logger LOGGER = Loggers.getLogger(ResourceIO.class.getName()); + + private final Path file; + private final Charset charset = Charset.forName("US-ASCII"); + + public ResourceIO(final File file) { + this.file = file.toPath(); + } + + public ResourceIO(final Path file) { + this.file = file; + } + + public ResourceIO(final String path) { + this.file = FileSystems.getDefault().getPath(path); + } + + public boolean exists() { + return Files.exists(file); + } + + public boolean readLines(final Consumer lineConsumer) { + if (Files.exists(file)) { + try ( BufferedReader reader = Files.newBufferedReader(file, charset)) { + String line; + while ((line = reader.readLine()) != null) { + lineConsumer.accept(line); + } + + return true; + } catch (IOException x) { + LOGGER.log(Level.WARNING, "ResourceIO read failure", x); + return false; + } + } + + return false; + } + + public boolean writeLines(final Supplier lineSupplier, final OpenOption... options) { + try ( BufferedWriter writer = Files.newBufferedWriter(file, charset, options)) { + String line; + while ((line = lineSupplier.get()) != null) { + writer.write(line, 0, line.length()); + writer.newLine(); + } + + return true; + } catch (IOException x) { + LOGGER.log(Level.WARNING, "ResourceIO write failure", x); + return false; + } + } + + public String getFileame() { + return file.toString(); + } +} \ No newline at end of file diff --git a/doom/src/utils/Throwers.java b/doom/src/utils/Throwers.java new file mode 100644 index 0000000..fb75762 --- /dev/null +++ b/doom/src/utils/Throwers.java @@ -0,0 +1,308 @@ +package utils; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.concurrent.Callable; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public enum Throwers { + ; + @SafeVarargs + public static Callable + callable(ThrowingCallable r, Class... cl) throws Throwed { + return () -> { + try { + return r.call(); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static Runnable + runnable(ThrowingRunnable r, Class... cl) throws Throwed { + return () -> { + try { + r.run(); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static Consumer + consumer(ThrowingConsumer c, Class... cl) throws Throwed { + return (t) -> { + try { + c.accept(t); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static BiConsumer + biConsumer(ThrowingBiConsumer c, Class... cl) throws Throwed { + return (t1, t2) -> { + try { + c.accept(t1, t2); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static Predicate + predicate(ThrowingPredicate p, Class... cl) throws Throwed { + return (t) -> { + try { + return p.test(t); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static BiPredicate + biPredicate(ThrowingBiPredicate p, Class... cl) throws Throwed { + return (t1, t2) -> { + try { + return p.test(t1, t2); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static Function + function(ThrowingFunction f, Class... cl) throws Throwed { + return (t) -> { + try { + return f.apply(t); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static BiFunction + biFunction(ThrowingBiFunction f, Class... cl) throws Throwed { + return (t1, t2) -> { + try { + return f.apply(t1, t2); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + @SafeVarargs + public static Supplier + supplier(ThrowingSupplier s, Class... cl) throws Throwed { + return () -> { + try { + return s.get(); + } catch (Throwable e) { + if (classifyMatching(e, cl)) { + throw doThrow(e); + } else { + throw doThrowE(e); + } + } + }; + } + + public static class Throwed extends RuntimeException { + + private static final long serialVersionUID = 5802686109960804684L; + public final Throwable t; + + private Throwed(Throwable t) { + super(null, null, true, false); + this.t = t; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return t.fillInStackTrace(); + } + + @Override + public synchronized Throwable getCause() { + return t.getCause(); + } + + @Override + public String getLocalizedMessage() { + return t.getLocalizedMessage(); + } + + @Override + public String getMessage() { + return t.getMessage(); + } + + @Override + public StackTraceElement[] getStackTrace() { + return t.getStackTrace(); + } + + @Override + public void setStackTrace(StackTraceElement[] stackTrace) { + t.setStackTrace(stackTrace); + } + + @Override + public synchronized Throwable initCause(Throwable cause) { + return t.initCause(cause); + } + + @Override + @SuppressWarnings("CallToPrintStackTrace") + public void printStackTrace() { + t.printStackTrace(); + } + + @Override + public void printStackTrace(PrintStream s) { + t.printStackTrace(s); + } + + @Override + public void printStackTrace(PrintWriter s) { + t.printStackTrace(s); + } + + @Override + public String toString() { + return t.toString(); + } + } + + public interface ThrowingCallable { + + T call() throws Throwable; + } + + public interface ThrowingRunnable { + + void run() throws Throwable; + } + + public interface ThrowingConsumer { + + void accept(T t) throws Throwable; + } + + public interface ThrowingBiConsumer { + + void accept(T1 t1, T2 t2) throws Throwable; + } + + public interface ThrowingPredicate { + + boolean test(T t) throws Throwable; + } + + public interface ThrowingBiPredicate { + + boolean test(T1 t1, T2 t2) throws Throwable; + } + + public interface ThrowingFunction { + + R apply(T t) throws Throwable; + } + + public interface ThrowingBiFunction { + + R apply(T1 t1, T2 t2) throws Throwable; + } + + public interface ThrowingSupplier { + + T get() throws Throwable; + } + + /** + * Throw checked exception as runtime exception preserving stack trace The class of exception will be changed so it + * will only trigger catch statements for new type + * + * @param e exception to be thrown + * @return impossible + * @throws Throwed + */ + public static RuntimeException doThrow(final Throwable e) throws Throwed { + throw new Throwed(e); + } + + /** + * Throw checked exception as runtime exception preserving stack trace The class of exception will not be changed. + * In example, an InterruptedException would then cause a Thread to be interrupted + * + * @param + * @param e exception to be thrown + * @return impossible + * @throws E (in runtime) + */ + @SuppressWarnings("unchecked") + private static RuntimeException doThrowE(final Throwable e) throws E { + throw (E) e; + } + + @SafeVarargs + private static boolean classifyMatching(Throwable ex, Class... options) { + for (Class o : options) { + if (o.isInstance(ex)) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/doom/src/utils/TraitFactory.java b/doom/src/utils/TraitFactory.java new file mode 100644 index 0000000..9cc4d2f --- /dev/null +++ b/doom/src/utils/TraitFactory.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Logger; +import mochadoom.Loggers; + +/** + * Purpose of this pattern-interface: store Trait-specific class-wise context objects + * and be able to get them in constant time. + * + * Simple usage: + * You may read the theory below to understand, why and for what reason I wrote + * TraitFactory. However, the simplest use is: create an interface extending Trait, + * put there static final KeyChain object field, and declare some static ContextKey<> + * fields in descending classes and/or interfaces for your objects using KeyChain.newKey. + * + * Then to initialize everything, just call TraitFactory.build() and the result + * will be SharedContext to return on overriden method of Trait. + * + * TraitFactory.build utilizes (at the instantiation time, not in runtime) some + * black reflection magic to free you from need to look for every Trait in line, + * and call some registering function to add Objects to Keys in InsertConveyor. + * + * General contract of Trait: + * + * 0. In the constructor of object implementing the subset of Traits based + * on this Trait, you must call TraitFactory.build(this, idCapacity); + * Implementing this Trait otherwise means nothing. + * + * The result of TraitFactory.build(this, idCapacity); must be stored + * and the overriden method getContext() must return it. + * + * You can use some static non-final int[] field of deepest Trait dependency + * that is incremented by static initialization of all who depend on it, + * to determine idCapacity, or just guess big enough on your own. Also you can + * use helper object, KeyChain. + * + * 1. In a Trait of your subset, where you want to have some object in context, you + * must create static final ContextKey fild. During the static final ContextKey + * initialization, you can also hack into incrementing some static non-final + * somewhere, to be sure all who do the same produce unique fast ContextKeys. + * + * You can create several ContextKeys per Trait and store several contexts, + * and, if your preferedIds are unique, they will be still instant-fast. + * + * 2. You may want to be sure that all of your interfaces have created their context + * objects and put them into the InsertConveyor. To do that, you should have a + * method on the class using traits, that will descend into the top level traits, + * then lower and lower until the last of the traits. + * + * ContextKey does not override hashCode and is a final class. So the hashCode() + * method will be something like memory pointer, and uniqye per ContextKey. + * Default context storage (FactoryContext.class) does not check it until + * any new stored ContextKey have preferedId already taken, and reports different + * context Object Class. If such happen, all associated contexts are moved + * into HashMap and context acquisition will be since significantly slower. + * + * If your ContextKey does not overlap with another one, access to context Object + * would be the most instant of all possible. + * + * 3. In use, call contextGet(ContextKey) or some helper methods to get + * the Object from context. Alternatively, you can acquire the SharedContext. + * The helper methods are better in case you fear nulls. + * + * As the SharedContext is Shared, you can use it and objects from it in any + * descendants of the trait where you put this object into the context by key. + * + * If you made sure you never put two Objects of different type with two ContextKeys + * with matching preferedIds and Class'es, the cost of get(ContextKey) will be + * as negligible as one level of indirection + array access by int. + */ +public class TraitFactory { + + private final static Logger LOGGER = Loggers.getLogger(TraitFactory.class.getName()); + + public static SharedContext build(T traitUser, KeyChain usedChain) + throws IllegalArgumentException, IllegalAccessException { + return build(traitUser, usedChain.currentCapacity); + } + + public static SharedContext build(T traitUser, int idCapacity) + throws IllegalArgumentException, IllegalAccessException { + final FactoryContext c = new FactoryContext(idCapacity); + repeatRecursive(traitUser.getClass().getInterfaces(), c); + return c; + } + + private static void repeatRecursive(final Class[] traitUserInteraces, final FactoryContext c) + throws IllegalAccessException, SecurityException, IllegalArgumentException { + for (Class cls : traitUserInteraces) { + final Field[] declaredFields = cls.getDeclaredFields(); + for (final Field f : declaredFields) { + final int modifiers = f.getModifiers(); + if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { + final Class fieldClass = f.getType(); + if (fieldClass == ContextKey.class) { + final ContextKey key = ContextKey.class.cast(f.get(null)); + c.put(key, key.contextConstructor); + LOGGER.fine(() -> String.format("%s for %s", c.get(key).getClass(), f.getDeclaringClass())); + } + } + } + + repeatRecursive(cls.getInterfaces(), c); + } + } + + public interface Trait { + + SharedContext getContext(); + + default T contextGet(ContextKey key, T defaultValue) { + final T got = getContext().get(key); + return got == null ? defaultValue : got; + } + + default T contextRequire(ContextKey key) { + final T got = getContext().get(key); + if (got == null) { + throw defaultException(key).get(); + } + + return got; + } + + default T contextRequire(ContextKey key, Supplier exceptionSupplier) throws E { + final T got = getContext().get(key); + if (got == null) { + throw exceptionSupplier.get(); + } + + return got; + } + + default boolean contextTest(ContextKey key, Predicate predicate) { + final T got = getContext().get(key); + return got == null ? false : predicate.test(got); + } + + default void contextWith(ContextKey key, Consumer consumer) { + final T got = getContext().get(key); + if (got != null) { + consumer.accept(got); + } + } + + default R contextMap(ContextKey key, Function mapper, R defaultValue) { + final T got = getContext().get(key); + if (got != null) { + return mapper.apply(got); + } else { + return defaultValue; + } + } + + default Supplier defaultException(ContextKey key) { + return () -> new SharedContextException(key, this.getClass()); + } + } + + public final static class ContextKey { + + final Class traitClass; + final int preferredId; + final Supplier contextConstructor; + + public ContextKey(final Class traitClass, int preferredId, Supplier contextConstructor) { + this.traitClass = traitClass; + this.preferredId = preferredId; + this.contextConstructor = contextConstructor; + } + + @Override + public String toString() { + return String.format("context in the Trait %s (preferred id: %d)", traitClass, preferredId); + } + } + + public final static class KeyChain { + + int currentCapacity; + + public ContextKey newKey(final Class traitClass, Supplier contextConstructor) { + return new ContextKey<>(traitClass, currentCapacity++, contextConstructor); + } + } + + public interface SharedContext { + + T get(ContextKey key); + } + + final static class FactoryContext implements InsertConveyor, SharedContext { + + private HashMap, Object> traitMap; + private ContextKey[] keys; + private Object[] contexts; + private boolean hasMap = false; + + private FactoryContext(final int idCapacity) { + this.keys = new ContextKey[idCapacity]; + this.contexts = new Object[idCapacity]; + } + + @Override + public void put(ContextKey key, Supplier context) { + if (!hasMap) { + if (key.preferredId >= 0 && key.preferredId < keys.length) { + // return in the case of duplicate initialization of trait + if (keys[key.preferredId] == key) { + LOGGER.finer(() -> "Already found, skipping: " + key); + return; + } else if (keys[key.preferredId] == null) { + keys[key.preferredId] = key; + contexts[key.preferredId] = context.get(); + return; + } + } + + hasMap = true; + for (int i = 0; i < keys.length; ++i) { + traitMap.put(keys[i], contexts[i]); + } + + keys = null; + contexts = null; + } + + traitMap.put(key, context.get()); + } + + @Override + @SuppressWarnings("unchecked") + public T get(ContextKey key) { + if (hasMap) { + return (T) traitMap.get(key); + } else if (key.preferredId >= 0 && key.preferredId < keys.length) { + return (T) contexts[key.preferredId]; + } + + return null; + } + } + + public interface InsertConveyor { + + void put(ContextKey key, Supplier context); + + default void putObj(ContextKey key, Object context) { + put(key, () -> context); + } + } + + private static class SharedContextException extends RuntimeException { + + private static final long serialVersionUID = 5356800492346200764L; + + SharedContextException(ContextKey key, Class topLevel) { + super(String.format("Trait context %s is not initialized when used by %s or" + + "is dereferencing a null pointer when required to do not", + key, topLevel)); + } + } + + private static Type[] getParameterizedTypes(Object object) { + Type superclassType = object.getClass().getGenericSuperclass(); + if (!ParameterizedType.class.isAssignableFrom(superclassType.getClass())) { + return null; + } + return ((ParameterizedType) superclassType).getActualTypeArguments(); + } + + private TraitFactory() { + } +} \ No newline at end of file diff --git a/doom/src/v/DoomGraphicSystem.java b/doom/src/v/DoomGraphicSystem.java new file mode 100644 index 0000000..a59c4cf --- /dev/null +++ b/doom/src/v/DoomGraphicSystem.java @@ -0,0 +1,229 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v; + +import f.Wiper; +import java.awt.Image; +import java.awt.Rectangle; +import m.IRandom; +import m.Settings; +import mochadoom.Engine; +import rr.patch_t; +import v.graphics.Horizontal; +import v.graphics.Plotter; +import v.graphics.Relocation; +import v.renderers.DoomScreen; +import v.scale.VideoScale; +import v.tables.BlurryTable; + +/** + * Refactored a lot of it; most notable changes: + * + * - 2d rendering methods are unified, standartized, generized, typehinted and incapsulated, + * they are moved into separate interfaces and those interfaces to separate package + * + * - Fixed buggy 2d alrorithms, such as scaling, rewritten and parallelized column drawing logic, + * unified and simplified calculation of areas on column-major surface + * + * - Renderer drivers are separated from drawing API and refactored a lot: fixed all issues with + * improper gammas, lights and tinting, fixed delay before it applied on non-indexed render, + * parallelized HiColor and TrueColor renderers. Only standard indexed 8-bit renderer is still + * single-threaded, and he is very performant and is cool too! + * + * -- Good Sign 2017/04/12 + * + * Notes about method hiding: + * - (A comment on the notes below) It would be only wonderful, if it also will make reflection-access + * (that what happens when some lame Java developer cannot access something and he just use reflection to + * set method public) harder on these methods. I hate lame Java developers. + * + * Never trust clients. Never show them too much. So for those of you, who don't know something like that, + * I introduce a good method of hiding interface methods. It is called Hiding By Complexity of Implementation. + * Why I call it that? Because we strike a zombie sergeant using a shotgun. + * + * What do we need to hide? Complexity. + * Why do we need to hide complexity? Because it is hard to use complex internal methods *properly*. + * That is why they are internal. A here it is the main contract: if you want to use internal methods, + * you create all their environment properly by sub-contracts of concrete interfaces. + * So we hide complexity of usage by complexity of implementation the usable case. And the sergeant falls. + * + * A lot of interfaces with a lot of default methods. This is intended feature hiding mechanism. + * Yes, it seems that a lot of PUBLIC default methods (default method is always public) + * gains much access and power to one who use it... But actually, these interfaces restrict + * much more, then static methods, because you have to actually *implement* the interface + * to access any of these methods, and implementing these interfaces means implementing + * a whole part of DoomGraphicsSystem. And I've thought out the interfaces contracts in the way + * that if someone *implement* them on purpose, their methods will be safe and useful for him. + * + * -- Good Sign 2017/04/14 + * + * DoomVideoSystem is now an interface, that all "video drivers" (whether do screen, disk, etc.) + * must implement. + * + * 23/10/2011: Made into a generic type, which affects the underlying raw screen data + * type. This should make -in theory- true color or super-indexed (>8 bits) video modes + * possible. The catch is that everything directly meddling with the renderer must also + * be aware of the underlying implementation. E.g. the various screen arrays will not be + * necessarily byte[]. + * + * @author Maes + */ +public interface DoomGraphicSystem { + + /** + * Flags used by patch drawing functions + * Now working as separate and optional varargs argument + * Added by _D_. Unsure if I should use VSI objects instead, as they + * already carry scaling information which doesn't need to be repacked... + */ + final int V_NOSCALESTART = 0x00010000; // dont scale x,y, start coords + final int V_SCALESTART = 0x00020000; // scale x,y, start coords + final int V_SCALEPATCH = 0x00040000; // scale patch + final int V_NOSCALEPATCH = 0x00080000; // don't scale patch + final int V_WHITEMAP = 0x00100000; // draw white (for v_drawstring) + final int V_FLIPPEDPATCH = 0x00200000; // flipped in y + final int V_TRANSLUCENTPATCH = 0x00400000; // draw patch translucent + final int V_PREDIVIDE = 0x00800000; // pre-divide by best x/y scale. + final int V_SCALEOFFSET = 0x01000000; // Scale the patch offset + final int V_NOSCALEOFFSET = 0x02000000; // dont's cale patch offset + final int V_SAFESCALE = 0x04000000; // scale only by minimal scale of x/y instead of both + + /** + * Public API + * See documentation in r2d package + * + * These are only methods DoomGraphicSystem wants to share from the whole insanely big package r2d + * Because using only these methods, it is minimal risk of breaking something. Actually, + * the only problematic cases should be passing null instead of argument or invalid coordinates. + */ + /* SCREENS */ + V getScreen(DoomScreen screenType); + + int getScalingX(); + + int getScalingY(); + + int getScreenWidth(); + + int getScreenHeight(); + + void screenCopy(V srcScreen, V dstScreen, Relocation relocation); + + void screenCopy(DoomScreen srcScreen, DoomScreen dstScreen); + + /* PALETTES */ + void setUsegamma(int gammalevel); + + int getUsegamma(); + + void setPalette(int palette); + + int getPalette(); + + int getBaseColor(byte color); + + int getBaseColor(int color); + + /* POINTS */ + int point(int x, int y); + + int point(int x, int y, int width); + + /* LINES */ + void drawLine(Plotter plotter, int x1, int x2); + + /* PATCHES */ + void DrawPatch(DoomScreen screen, patch_t patch, int x, int y, int... flags); + + void DrawPatchCentered(DoomScreen screen, patch_t patch, int y, int... flags); + + void DrawPatchCenteredScaled(DoomScreen screen, patch_t patch, VideoScale vs, int y, int... flags); + + void DrawPatchScaled(DoomScreen screen, patch_t patch, VideoScale vs, int x, int y, int... flags); + + void DrawPatchColScaled(DoomScreen screen, patch_t patch, VideoScale vs, int x, int col); + + /* RECTANGLES */ + void CopyRect(DoomScreen srcScreenType, Rectangle rectangle, DoomScreen dstScreenType); + + void CopyRect(DoomScreen srcScreenType, Rectangle rectangle, DoomScreen dstScreenType, int dstPoint); + + void FillRect(DoomScreen screenType, Rectangle rectangle, V patternSrc, Horizontal pattern); + + void FillRect(DoomScreen screenType, Rectangle rectangle, V patternSrc, int point); + + void FillRect(DoomScreen screenType, Rectangle rectangle, int color); + + void FillRect(DoomScreen screenType, Rectangle rectangle, byte color); + + /* BLOCKS */ + V convertPalettedBlock(byte... src); + + V ScaleBlock(V block, VideoScale vs, int width, int height); + + void TileScreen(DoomScreen dstScreen, V block, Rectangle blockArea); + + void TileScreenArea(DoomScreen dstScreen, Rectangle screenArea, V block, Rectangle blockArea); + + void DrawBlock(DoomScreen dstScreen, V block, Rectangle sourceArea, int destinationPoint); + + /** + * No matter how complex/weird/arcane palette manipulations you do internally, the AWT module + * must always be able to "tap" into what's the current, "correct" screen after all manipulation and + * color juju was applied. Call after a palette/gamma change. + */ + Image getScreenImage(); + + /** + * Saves screenshot to a file "filling a planar buffer to linear" + * (I cannot guarantee I understood - Good Sign 2017/04/01) + * @param name + * @param screen + * @return true if succeed + */ + boolean writeScreenShot(String name, DoomScreen screen); + + /** + * If the renderer operates color maps, get them + * Used for scene rendering + */ + V[] getColorMap(); + + /** + * Plotter for point-by-point drawing of AutoMap + */ + default Plotter createPlotter(DoomScreen screen) { + switch (Engine.getConfig().getValue(Settings.automap_plotter_style, Plotter.Style.class)) { + case Thick: + return new Plotter.Thick<>(getScreen(screen), getScreenWidth(), getScreenHeight()); + case Deep: + return new Plotter.Deep<>(getScreen(screen), getScreenWidth(), getScreenHeight()); + default: + return new Plotter.Thin<>(getScreen(screen), getScreenWidth()); + } + } + + Wiper createWiper(IRandom random); + + BlurryTable getBlurryTable(); + + /** + * Indexed renderer needs to reset its image + */ + default void forcePalette() { + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Blocks.java b/doom/src/v/graphics/Blocks.java new file mode 100644 index 0000000..4cdd78e --- /dev/null +++ b/doom/src/v/graphics/Blocks.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.awt.Rectangle; +import java.lang.reflect.Array; +import v.scale.VideoScale; + +/** + * Manipulating Blocks + * + * @author Good Sign + */ +public interface Blocks> extends Points, Palettes { + + /** + * Converts a block of paletted pixels into screen format pixels + * It is advised that implementation should both perform caching + * and be additionally optimized for 1-value src arrays + */ + V convertPalettedBlock(byte... src); + + /** + * Fills the whole dstScreen tiling the copies of block across it + */ + default void TileScreen(E dstScreen, V block, Rectangle blockArea) { + final int screenHeight = getScreenHeight(); + final int screenWidth = getScreenWidth(); + + for (int y = 0; y < screenHeight; y += blockArea.height) { + // Draw whole blocks. + for (int x = 0; x < screenWidth; x += blockArea.width) { + final int destination = point(x, y, screenWidth); + DrawBlock(dstScreen, block, blockArea, destination); + } + } + } + + /** + * Fills the rectangular part of dstScreen tiling the copies of block across it + */ + default void TileScreenArea(E dstScreen, Rectangle screenArea, V block, Rectangle blockArea) { + final int screenWidth = getScreenWidth(); + final int fiilLimitX = screenArea.x + screenArea.width; + final int fiilLimitY = screenArea.y + screenArea.height; + + for (int y = screenArea.y; y < fiilLimitY; y += blockArea.height) { + // Draw whole blocks. + for (int x = screenArea.x; x < fiilLimitX; x += blockArea.width) { + final int destination = point(x, y, screenWidth); + DrawBlock(dstScreen, block, blockArea, destination); + } + } + } + + /** + * Draws a linear block of pixels from the source buffer into screen buffer + * V_DrawBlock + */ + default void DrawBlock(E dstScreen, V block, Rectangle sourceArea, int destinationPoint) { + final V screen = getScreen(dstScreen); + final int bufferLength = Array.getLength(screen); + final int screenWidth = getScreenWidth(); + final Relocation rel = new Relocation( + point(sourceArea.x, sourceArea.y), + destinationPoint, + sourceArea.width); + + for (int h = sourceArea.height; h > 0; --h, rel.source += sourceArea.width, rel.destination += screenWidth) { + if (rel.destination + rel.length >= bufferLength) { + return; + } + screenCopy(block, screen, rel); + } + } + + default V ScaleBlock(V block, VideoScale vs, int width, int height) { + return ScaleBlock(block, width, height, vs.getScalingX(), vs.getScalingY()); + } + + default V ScaleBlock(V block, int width, int height, int dupX, int dupY) { + final int newWidth = width * dupX; + final int newHeight = height * dupY; + @SuppressWarnings("unchecked") + final V newBlock = (V) Array.newInstance(block.getClass().getComponentType(), newWidth * newHeight); + final Horizontal row = new Horizontal(0, dupX); + + for (int i = 0; i < width; ++i) { + for (int j = 0; j < height; ++j) { + final int pointSource = point(i, j, width); + final int pointDestination = point(i * dupX, j * dupY, newWidth); + row.start = pointDestination; + // Fill first line of rect + screenSet(block, pointSource, newBlock, row); + // Fill the rest of the rect + RepeatRow(newBlock, row, dupY - 1, newWidth); + } + } + + return newBlock; + } + + /** + * Given a row, repeats it down the screen + */ + default void RepeatRow(V screen, final Horizontal row, int times) { + RepeatRow(screen, row, times, getScreenWidth()); + } + + /** + * Given a row, repeats it down the screen + */ + default void RepeatRow(V block, final Horizontal row, int times, int blockWidth) { + if (times > 0) { + final Relocation rel = row.relocate(blockWidth); + for (; times > 0; --times, rel.shift(blockWidth)) { + screenCopy(block, block, rel); + } + } + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/ColorTransform.java b/doom/src/v/graphics/ColorTransform.java new file mode 100644 index 0000000..bf60777 --- /dev/null +++ b/doom/src/v/graphics/ColorTransform.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 1993-1996 Id Software, Inc. + * from f_wipe.c + * + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.lang.reflect.Array; +import static utils.GenericCopy.memcpy; + +public interface ColorTransform { + + default boolean initTransform(Wipers.WiperImpl wiper) { + memcpy(wiper.wipeStartScr, 0, wiper.wipeEndScr, 0, Array.getLength(wiper.wipeEndScr)); + return false; + } + + default boolean colorTransformB(Wipers.WiperImpl wiper) { + byte[] w = wiper.wipeStartScr, e = wiper.wipeEndScr; + boolean changed = false; + for (int i = 0, newval; i < w.length; ++i) { + if (w[i] != e[i]) { + w[i] = w[i] > e[i] + ? (newval = w[i] - wiper.ticks) < e[i] ? e[i] : (byte) newval + : (newval = w[i] + wiper.ticks) > e[i] ? e[i] : (byte) newval; + changed = true; + } + } + return !changed; + } + + default boolean colorTransformS(Wipers.WiperImpl wiper) { + short[] w = wiper.wipeStartScr, e = wiper.wipeEndScr; + boolean changed = false; + for (int i = 0, newval; i < w.length; ++i) { + if (w[i] != e[i]) { + w[i] = w[i] > e[i] + ? (newval = w[i] - wiper.ticks) < e[i] ? e[i] : (byte) newval + : (newval = w[i] + wiper.ticks) > e[i] ? e[i] : (byte) newval; + changed = true; + } + } + return !changed; + } + + default boolean colorTransformI(Wipers.WiperImpl wiper) { + int[] w = wiper.wipeStartScr, e = wiper.wipeEndScr; + boolean changed = false; + for (int i = 0, newval; i < w.length; ++i) { + if (w[i] != e[i]) { + w[i] = w[i] > e[i] + ? (newval = w[i] - wiper.ticks) < e[i] ? e[i] : (byte) newval + : (newval = w[i] + wiper.ticks) > e[i] ? e[i] : (byte) newval; + changed = true; + } + } + return !changed; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Colors.java b/doom/src/v/graphics/Colors.java new file mode 100644 index 0000000..ea51c43 --- /dev/null +++ b/doom/src/v/graphics/Colors.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import v.tables.ColorTint; + +/** + * Package containing individual color modification and transformation methods + */ +public interface Colors { + + /** + * Get alpha from packed argb long word. + * + * @param argb8888 + * @return + */ + default int getAlpha(int argb8888) { + return (argb8888 >>> 24) & 0xFF; + } + + /** + * Get red from packed argb long word. + * + * @param rgb888 + * @return + */ + default int getRed(int rgb888) { + return (0xFF0000 & rgb888) >> 16; + } + + /** + * Get red from packed rgb555 + * + * @param rgb555 + * @return + */ + default int getRed5(int rgb555) { + return (rgb555 >> 10) & 0x1F; + } + + /** + * Get green from packed argb long word. + * + * @param rgb888 + * @return + */ + default int getGreen(int rgb888) { + return (0xFF00 & rgb888) >> 8; + } + + /** + * Get green from packed rgb555 + * + * @param rgb555 + * @return + */ + default int getGreen5(int rgb555) { + return (rgb555 >> 5) & 0x1F; + } + + /** + * Get blue from packed argb long word. + * + * @param rgb888 + * @return + */ + default int getBlue(int rgb888) { + return 0xFF & rgb888; + } + + /** + * Get blue from packed rgb555 + * + * @param rgb555 + * @return + */ + default int getBlue5(int rgb555) { + return rgb555 & 0x1F; + } + + /** + * Get all four color channels into an array + */ + default int[] getARGB8888(int argb8888, int[] container) { + container[0] = getAlpha(argb8888); + container[1] = getRed(argb8888); + container[2] = getGreen(argb8888); + container[3] = getBlue(argb8888); + return container; + } + + /** + * Get all four color channels into an array + */ + default int[] getRGB888(int rgb888, int[] container) { + container[0] = getRed(rgb888); + container[1] = getGreen(rgb888); + container[2] = getBlue(rgb888); + return container; + } + + /** + * Get all three colors into an array + */ + default int[] getRGB555(int rgb555, int[] container) { + container[0] = getRed5(rgb555); + container[1] = getGreen5(rgb555); + container[2] = getBlue5(rgb555); + return container; + } + + /** + * Compose rgb888 color (opaque) + */ + default int toRGB888(int r, int g, int b) { + return 0xFF000000 + ((r & 0xFF) << 16) + ((g & 0xFF) << 8) + (b & 0xFF); + } + + /** + * Compose argb8888 color + */ + default int toARGB8888(int a, int r, int g, int b) { + return ((a & 0xFF) << 24) + ((r & 0xFF) << 16) + ((g & 0xFF) << 8) + (b & 0xFF); + } + + /** + * Compose rgb888 color + */ + default short toRGB555(int r, int g, int b) { + return (short) (((r & 0x1F) << 10) + ((g & 0x1F) << 5) + (b & 0x1F)); + } + + /** + * Alter rgb888 color by applying a tint to it + * @param int[] rgbInput an array containing rgb888 color components + */ + default int[] tintRGB888(final ColorTint tint, final int[] rgbInput, int[] rgbOutput) { + rgbOutput[0] = tint.tintRed8(rgbInput[0]); + rgbOutput[1] = tint.tintGreen8(rgbInput[1]); + rgbOutput[2] = tint.tintBlue8(rgbInput[2]); + return rgbOutput; + } + + /** + * Alter rgb555 color by applying a tint to it + * @param int[] rgbInput an array containing rgb555 color components + */ + default int[] tintRGB555(final ColorTint tint, final int[] rgbInput, int[] rgbOutput) { + rgbOutput[0] = tint.tintRed5(rgbInput[0]); + rgbOutput[1] = tint.tintGreen5(rgbInput[1]); + rgbOutput[2] = tint.tintBlue5(rgbInput[2]); + return rgbOutput; + } + + default double sigmoid(double r) { + return (1 / (1 + Math.pow(Math.E, (-1 * r)))); + } + + default int sigmoidGradient(int component1, int component2, float ratio) { + return (int) ((ratio * component1) + ((1 - ratio) * component2)); + } + + /** + * Tells which color is further by comparing distance between two packed rgb888 ints + */ + default int CompareColors888(int rgb888_1, int rgb888_2) { + final long distance = ColorDistance888(rgb888_1, rgb888_2); + return distance > 0 ? 1 : distance < 0 ? -1 : 0; + } + + /** + * Computes simplified Euclidean color distance (without extracting square root) between two packed rbg888 ints + */ + default long ColorDistance888(int rgb888_1, int rgb888_2) { + final int r1 = getRed(rgb888_1), + g1 = getGreen(rgb888_1), + b1 = getBlue(rgb888_1), + r2 = getRed(rgb888_2), + g2 = getGreen(rgb888_2), + b2 = getBlue(rgb888_2); + + final long dr = r1 - r2, dg = g1 - g2, db = b1 - b2; + return dr * dr + dg * dg + db * db; + } + + /** + * Tells which color is further by comparing hue, saturation, value distance between two packed rgb888 ints + */ + default int CompareColorsHSV888(int rgb888_1, int rgb888_2) { + final long distance = ColorDistanceHSV888(rgb888_1, rgb888_2); + return distance > 0 ? 1 : distance < 0 ? -1 : 0; + } + + /** + * Computes simplified Euclidean color distance (without extracting square root) between two packed rbg888 ints + * based on hue, saturation and value + */ + default long ColorDistanceHSV888(int rgb888_1, int rgb888_2) { + final int r1 = (int) (0.21 * getRed(rgb888_1)), + g1 = (int) (0.72 * getGreen(rgb888_1)), + b1 = (int) (0.07 * getBlue(rgb888_1)), + r2 = (int) (0.21 * getRed(rgb888_2)), + g2 = (int) (0.72 * getGreen(rgb888_2)), + b2 = (int) (0.07 * getBlue(rgb888_2)); + + final long dr = r1 - r2, dg = g1 - g2, db = b1 - b2; + return dr * dr + dg * dg + db * db; + } + + /** + * Tells which color is further by comparing distance between two packed rgb555 shorts + */ + default int CompareColors555(short rgb555_1, short rgb555_2) { + final long distance = ColorDistance555(rgb555_1, rgb555_2); + return distance > 0 ? 1 : distance < 0 ? -1 : 0; + } + + /** + * Computes simplified Euclidean color distance (without extracting square root) between two packed rbg555 shorts + */ + default long ColorDistance555(short rgb1, short rgb2) { + final int r1 = getRed5(rgb1), + g1 = getGreen5(rgb1), + b1 = getBlue5(rgb1), + r2 = getRed5(rgb2), + g2 = getGreen5(rgb2), + b2 = getBlue5(rgb2); + + final long dr = r1 - r2, dg = g1 - g2, db = b1 - b2; + return dr * dr + dg * dg + db * db; + } + + /** + * Tells which color is further by comparing hue, saturation, value distance between two packed rgb555 shorts + */ + default int CompareColorsHSV555(short rgb555_1, short rgb555_2) { + final long distance = ColorDistanceHSV555(rgb555_1, rgb555_2); + return distance > 0 ? 1 : distance < 0 ? -1 : 0; + } + + /** + * Computes simplified Euclidean color distance (without extracting square root) between two packed rbg888 ints + * based on hue, saturation and value + */ + default long ColorDistanceHSV555(short rgb555_1, int rgb555_2) { + final int r1 = (int) (0.21 * getRed5(rgb555_1)), + g1 = (int) (0.72 * getGreen5(rgb555_1)), + b1 = (int) (0.07 * getBlue5(rgb555_1)), + r2 = (int) (0.21 * getRed5(rgb555_2)), + g2 = (int) (0.72 * getGreen5(rgb555_2)), + b2 = (int) (0.07 * getBlue5(rgb555_2)); + + final long dr = r1 - r2, dg = g1 - g2, db = b1 - b2; + return dr * dr + dg * dg + db * db; + } + + default float[] ColorRatio(int[] rgb1, int[] rgb2, float[] out) { + for (int i = 0; i < 3; ++i) { + out[i] = rgb2[i] > 0 ? rgb1[i] / (float) rgb2[i] : 1.0f; + } + return out; + } + + /** + * Get ARGB_8888 from RGB_555, with proper higher-bit + * replication. + * + * @param rgb555 + * @return rgb888 packed int + * @author velktron + */ + default int rgb555to888(short rgb555) { + // .... .... .... .... + // 111 11 = 7C00 + // 11 111 = 03E0 + // 1F= 1 1111 + int ri = (0x7C00 & rgb555) >> 7; + int gi = (0x3E0 & rgb555) >> 2; + int bi = (0x1F & rgb555) << 3; + // replicate 3 higher bits + int bits = (ri & 224) >> 5; + ri += bits; + bits = (gi & 224) >> 5; + gi += bits; + bits = (bi & 224) >> 5; + bi += bits; + // ARGB 8888 packed + return toRGB888(ri, gi, bi); + } + + /** + * Get RGB_555 from packed ARGB_8888. + * + * @param argb + * @return rgb555 packed short + * @authoor velktron + */ + default short argb8888to555(int argb8888) { + int ri = (0xFF010000 & argb8888) >> 19; + int gi = (0xFF00 & argb8888) >> 11; + int bi = (0xFF & argb8888) >> 3; + return toRGB555(ri, gi, bi); + } + + /** + * Get packed RGB_555 word from individual 8-bit RGB components. + * + * WARNING: there's no sanity/overflow check for performance reasons. + * + * @param r + * @param g + * @param b + * @return rgb888 packed int + * @author velktron + */ + default short rgb888to555(int r, int g, int b) { + return toRGB555(r >> 3, g >> 3, b >> 3); + } + + /** + * Finds a color in the palette's range from rangel to rangeh closest to specified r, g, b + * by distortion, the lesst distorted color is the result. Used for rgb555 invulnerability colormap + */ + default int BestColor(int r, int g, int b, int[] palette, int rangel, int rangeh) { + /** + * let any color go to 0 as a last resort + */ + long bestdistortion = ((long) r * r + (long) g * g + (long) b * b) * 2; + int bestcolor = 0; + for (int i = rangel; i <= rangeh; i++) { + final long dr = r - getRed(palette[i]); + final long dg = g - getGreen(palette[i]); + final long db = b - getBlue(palette[i]); + final long distortion = dr * dr + dg * dg + db * db; + if (distortion < bestdistortion) { + if (distortion == 0) { + return i; // perfect match + } + bestdistortion = distortion; + bestcolor = i; + } + } + return bestcolor; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Columns.java b/doom/src/v/graphics/Columns.java new file mode 100644 index 0000000..0bf5722 --- /dev/null +++ b/doom/src/v/graphics/Columns.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.function.IntConsumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.IntStream; +import m.Settings; +import mochadoom.Engine; +import mochadoom.Loggers; +import rr.column_t; +import rr.patch_t; + +/** + * Patch columns drawing. + * The whole class is my custom hand-crafted code + * - Good Sign 2017/04/03 + * + * @author Good Sign + */ +public interface Columns> extends Blocks { + + static final Logger LOGGER = Loggers.getLogger(Columns.class.getName()); + + /** + * We have to draw columns to the screen, not rows and is ineffective performance-wise because + * System.arraycopy only speeds a lot row copying, where it only have to be called once + */ + default void DrawColumn(V screen, column_t col, Horizontal row, V data, int scrWidth, int dupy) { + final int fullRowShift = scrWidth * dupy; + /** + * For each post, j is the index of post. + * + * A delta is a number of transparent rows to skip, if it is 0xFF then the whole column + * is transparent, so if we have delta 0xFF, then we've done with column drawing. + */ + for (int j = 0, delta = 0; + j < col.posts && col.postdeltas[j] != 0xFF; + ++j) { + // shift a row down by difference of current and previous delta with respect to scaling + row.shift(point(0, (-delta + (delta = col.postdeltas[j])) * dupy, scrWidth)); + final int saveRowStart = row.start; + + /** + * For each pixel in the post: p is a position of pixel in the column's data, + * column.postlen[j] is how many pixels tall is the post (a vertical string of pixels) + */ + for (int p = 0; p < col.postlen[j]; ++p, row.shift(fullRowShift)) { + // Fill first line of rect + screenSet(data, col.postofs[j] + p, screen, row); + // Fill the rest of the rect + RepeatRow(screen, row, dupy - 1); + } + row.start = saveRowStart; + } + } + + /** + * Accepts patch columns drawing arguments (usually from Patches::DrawPatch method) + * and submits the task to the local ForkJoinPool. The task iterates over patch columns in parallel. + * We need to only iterate through real patch.width and perform scale in-loop + */ + default void DrawPatchColumns(V screen, patch_t patch, int x, int y, int dupx, int dupy, boolean flip) { + final int scrWidth = getScreenWidth(); + final IntConsumer task = i -> { + final int startPoint = point(x + i * dupx, y, scrWidth); + final column_t column = flip ? patch.columns[patch.width - 1 - i] : patch.columns[i]; + DrawColumn(screen, column, new Horizontal(startPoint, dupx), + convertPalettedBlock(column.data), scrWidth, dupy); + }; + + /** + * As vanilla DOOM does not parallel column computation, we should have the option to turn off + * the parallelism. Just set it to 0 in cfg:parallelism_patch_columns, and it will process columns in serial. + * + * It will also prevent a crash on a dumb negative value set to this option. However, a value of 1000 is even + * more dumb, but will probably not crash - just take hellion of megabytes memory and waste all the CPU time on + * computing "what to process" instead of "what will be the result" + */ + if (U.COLUMN_THREADS > 0) try { + U.pool.submit(() -> IntStream.range(0, patch.width).parallel().forEach(task)).get(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, null, ex); + } else { + for (int i = 0; i < patch.width; ++i) { + task.accept(i); + } + } + } + + class U { + + static final int COLUMN_THREADS = Engine.getConfig().getValue(Settings.parallelism_patch_columns, Integer.class); + private static final ForkJoinPool pool = COLUMN_THREADS > 0 ? new ForkJoinPool(COLUMN_THREADS) : null; + + private U() { + } + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Direction.java b/doom/src/v/graphics/Direction.java new file mode 100644 index 0000000..f1ce393 --- /dev/null +++ b/doom/src/v/graphics/Direction.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * + * @author Good Sign + */ +public enum Direction { + LEFT_UP, UP, RIGHT_UP, + /* \ || / */ + /* \ || / */ + /* \ || / */ + /* \ || / */ + LEFT,/*===*/ CENTER,/*===*/ RIGHT, + /* / || \ */ + /* / || \ */ + /* / || \ */ + /* / || \ */ + LEFT_DOWN, DOWN, RIGHT_DOWN; + + public static final List directions = Collections.unmodifiableList(Arrays.asList(values())); + + /** + * Categorization constants + */ + // LEFT_UP, UP, RIGHT_UP + public final boolean hasTop = ordinal() < 3; + // LEFT_UP, LEFT, LEFT_DOWN + public final boolean hasLeft = ordinal() % 3 == 0; + // RIGHT_UP, RIGHT_ RIGHT_DOWN + public final boolean hasRight = ordinal() % 3 == 2; + // LEFT_DOWN, DOWN, RIGHT_DOWN + public final boolean hasBottom = ordinal() > 5; + // UP, LEFT, RIGHT, DOWN + public final boolean straight = ordinal() % 2 != 0; + + public boolean isAdjacent(Direction dir) { + return this.straight ^ dir.straight; + } + + /** + * Conversions + */ + public Direction next() { + if (this == RIGHT_DOWN) { + return LEFT_UP; + } + + return directions.get(ordinal() + 1); + } + + public Direction opposite() { + switch (this) { + case LEFT_UP: + return RIGHT_DOWN; + case UP: + return DOWN; + case RIGHT_UP: + return LEFT_DOWN; + case LEFT: + return RIGHT; + default: // CENTER + return this; + case RIGHT: + return LEFT; + case LEFT_DOWN: + return RIGHT_UP; + case DOWN: + return UP; + case RIGHT_DOWN: + return LEFT_UP; + } + } + + public Direction rotationHor(int sign) { + if (sign == 0) { + return this; + } + + switch (this) { + case LEFT_UP: + return sign > 0 ? UP : LEFT; + case UP: + return sign > 0 ? RIGHT_UP : LEFT_UP; + case RIGHT_UP: + return sign > 0 ? RIGHT : UP; + case LEFT: + return sign > 0 ? CENTER : this; + default: // CENTER + return sign > 0 ? RIGHT : LEFT; + case RIGHT: + return sign > 0 ? CENTER : this; + case LEFT_DOWN: + return sign > 0 ? DOWN : LEFT; + case DOWN: + return sign > 0 ? RIGHT_DOWN : LEFT_DOWN; + case RIGHT_DOWN: + return sign > 0 ? RIGHT : DOWN; + } + } + + public Direction rotationVert(int sign) { + if (sign == 0) { + return this; + } + + switch (this) { + case LEFT_UP: + return sign > 0 ? LEFT : UP; + case UP: + return sign > 0 ? CENTER : this; + case RIGHT_UP: + return sign > 0 ? RIGHT : UP; + case LEFT: + return sign > 0 ? LEFT_DOWN : LEFT_UP; + default: // CENTER + return sign > 0 ? DOWN : UP; + case RIGHT: + return sign > 0 ? RIGHT_DOWN : RIGHT_UP; + case LEFT_DOWN: + return sign > 0 ? DOWN : LEFT; + case DOWN: + return sign < 0 ? CENTER : this; + case RIGHT_DOWN: + return sign > 0 ? DOWN : RIGHT; + } + } + + public Direction rotation(int signX, int signY) { + final Direction rotX = rotationHor(signX), rotY = rotationHor(signY); + + if (rotX.isAdjacent(rotY)) { + if (signX > 0 && signY > 0) { + return RIGHT_DOWN; + } else if (signX > 0 && signY < 0) { + return RIGHT_UP; + } else if (signX < 0 && signY > 0) { + return LEFT_DOWN; + } else if (signX < 0 && signY < 0) { + return LEFT_UP; + } + } + + // otherwise, 2nd takes precedence + return rotY; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Horizontal.java b/doom/src/v/graphics/Horizontal.java new file mode 100644 index 0000000..f26a332 --- /dev/null +++ b/doom/src/v/graphics/Horizontal.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +/** + * Horizontal represents a range from a screen buffer (byte or short or int array) + * + * @author Good Sign + */ +public class Horizontal { + + public int start; + public int length; + + public Horizontal() { + } + + public Horizontal(int start, int length) { + this.start = start; + this.length = length; + } + + public Relocation relocate(int amount) { + return new Relocation(start, start + amount, length); + } + + public void shift(int amount) { + this.start += amount; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Lights.java b/doom/src/v/graphics/Lights.java new file mode 100644 index 0000000..12e898d --- /dev/null +++ b/doom/src/v/graphics/Lights.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import static v.graphics.Palettes.PAL_NUM_COLORS; +import v.tables.GreyscaleFilter; + +/** + * This package provides methods to dynamically generate lightmaps + * They are intended to be used instead of COLORMAP lump to + * compute sector brightness + * + * @author Good Sign + * @author John Carmack + * @author Velktron + */ +public interface Lights extends Colors { + + /** + * Light levels. Binded to the colormap subsystem + */ + final int COLORMAP_LIGHTS_15 = 1 << 5; + final int COLORMAP_LIGHTS_24 = 1 << 8; + + /** + * Standard lengths for colormaps + */ + final int COLORMAP_STD_LENGTH_15 = COLORMAP_LIGHTS_15 + 1; + final int COLORMAP_STD_LENGTH_24 = COLORMAP_LIGHTS_24 + 1; + + /** + * Default index of inverse colormap. Note that it will be shifted to the actual position + * in generated lights map by the difference in lights count between 5 and 8 bits lighting. + * I have discovered, that player_t.fixedcolormap property is *stored* by game when writing files, + * for example it could be included in savegame or demos. + * + * If we preshift inverse colormap, MochaDoom not in TrueColor bppMode or any Vanilla DOOM would crash + * when trying to load savegame made when under invulnerabilty in TrueColor bppMode. + * - Good Sign 2017/04/15 + */ + final int COLORMAP_INVERSE = 32; + + /** + * An index of of the lighted palette in colormap used for FUZZ effect and partial invisibility + */ + final int COLORMAP_BLURRY = 6; + + /** + * An index of of the most lighted palette in colormap + */ + final int COLORMAP_BULLBRIGHT = 1; + + /** + * An index of of palette0 in colormap which is not altered + */ + final int COLORMAP_FIXED = 0; + + /** + * A difference in percents between color multipliers of two adjacent light levels + * It took sometime to dig this out, and this could be possibly used to simplify + * BuildLight functions without decrease in their perfectness + * + * The formula to apply to a color will then be: + * float ratio = 1.0f - LIGHT_INCREMENT_RATIO_24 * lightLevel; + * color[0] = (int) (color[0] * ratio + 0.5) + * color[1] = (int) (color[1] * ratio + 0.5) + * color[2] = (int) (color[2] * ratio + 0.5) + * + * However, this one is untested, and existing formula in function AddLight8 does effectively the same, + * just a little slower. + * + * - Good Sign 2017/04/17 + */ + final float LIGHT_INCREMENT_RATIO_24 = 1.0f / COLORMAP_LIGHTS_24; + + /** + * Builds TrueColor lights based on standard COLORMAP lump in DOOM format + * Currently only supports lightmap manipulation, but does not change colors + * for hacked COLORMAP lumps + * + * Color indexes in colormaps on darker color levels point to less matching + * colors so only the direction of increase/decrease of lighting is actually + * used from COLORMAP lump. Everything else is computed based on PLAYPAL + * + * @param int[] palette A packed RGB888 256-entry int palette + * @param byete[][] colormap read from COLORMAP lump + * @author Good Sign + */ + default int[][] BuildLights24(int[] palette, byte[][] colormap) { + final int[][] targetColormap = new int[Math.max(colormap.length, COLORMAP_STD_LENGTH_15) - COLORMAP_LIGHTS_15 + COLORMAP_LIGHTS_24][PAL_NUM_COLORS]; + + // init operation containers + final int[] color0 = new int[3], color1 = new int[3], color2 = new int[3]; + final float[] ratio0 = new float[3]; + float weight = 0.0f; + + /** + * Fixed color map - just copy it, only translating palette to real color + * It is presumably the brightest colormap, but maybe not: we shall check weight of color ratios + */ + for (int i = 0; i < PAL_NUM_COLORS; ++i) { + targetColormap[0][i] = palette[colormap[0][i] & 0xFF]; + getRGB888(targetColormap[0][i], color0); + getRGB888(palette[i], color1); + // calculate color ratio + ColorRatio(color0, color1, ratio0); + // add average ratio to the weight + weight += GreyscaleFilter.component(ratio0[0], ratio0[1], ratio0[2]); + } + + // initialize ratio to relate weight with number of colors, with default PLAYPAL should always be 1.0f + float currentLightRatio = Math.min(weight / PAL_NUM_COLORS, 1.0f); + + // [1 .. 255]: all colormaps except 1 fixed, 1 inverse and 1 unused + for (int i = 1; i < COLORMAP_LIGHTS_24; ++i) { + // [1 .. 31] the index of the colormap to be target for gradations: max 31 of ceiling of i / 8 + final int div = (int) Math.ceil((double) i / 8); + final int target = Math.min(div, COLORMAP_LIGHTS_15 - 1); + final int remainder = div < COLORMAP_LIGHTS_15 ? i % 8 : 0; + final float gradient = 1.0f - remainder * 0.125f; + + // calculate weight again for each colormap + weight = 0.0f; + for (int j = 0; j < PAL_NUM_COLORS; ++j) { + // translated indexed color from wad-read colormap i at position j + getRGB888(palette[colormap[target][j] & 0xFF], color0); + // translated indexed color from our previous generated colormap at position j + getRGB888(targetColormap[i - 1][j], color1); + // calculate color ratio + ColorRatio(color0, color1, ratio0); + // add average ratio to the weight + weight += GreyscaleFilter.component(ratio0[0], ratio0[1], ratio0[2]); + // to detect which color we will use, get the fixed colormap one + getRGB888(targetColormap[0][j], color2); + + /** + * set our color using smooth TrueColor formula: we well use the brighter color as a base + * since the brighter color simply have more information not omitted + * if we are going up in brightness, not down, it will be compensated by ratio + */ + targetColormap[i][j] = toRGB888( + sigmoidGradient(color1[0], (int) (Math.max(color2[0], color0[0]) * currentLightRatio + 0.5), gradient), + sigmoidGradient(color1[1], (int) (Math.max(color2[1], color0[1]) * currentLightRatio + 0.5), gradient), + sigmoidGradient(color1[2], (int) (Math.max(color2[2], color0[2]) * currentLightRatio + 0.5), gradient) + ); + } + + // now detect if we are lightening or darkening + currentLightRatio += weight > PAL_NUM_COLORS ? LIGHT_INCREMENT_RATIO_24 : -LIGHT_INCREMENT_RATIO_24; + } + + // copy all other parts of colormap + for (int i = COLORMAP_LIGHTS_24, j = COLORMAP_LIGHTS_15; j < colormap.length; ++i, ++j) { + CopyMap24(targetColormap[i], palette, colormap[j]); + } + + return targetColormap; + } + + /** + * RF_BuildLights lifted from dcolors.c + * + * Used to compute extended-color colormaps even in absence of the + * COLORS15 lump. Must be recomputed if gamma levels change, since + * they actually modify the RGB envelopes. + * + * Variation that produces TrueColor lightmaps + * + * @param int[] palette A packed RGB888 256-entry int palette + */ + default int[][] BuildLights24(int[] palette) { + final int[][] targetColormap = new int[COLORMAP_STD_LENGTH_24][PAL_NUM_COLORS]; + final int[] palColor = new int[3]; + + // Don't repeat work more then necessary - loop first over colors, not lights + for (int c = 0; c < PAL_NUM_COLORS; ++c) { + getRGB888(palette[c], palColor); + for (int l = 0; l < COLORMAP_LIGHTS_24; ++l) { + // Full-quality truecolor. + targetColormap[l][c] = toRGB888( + AddLight8(palColor[0], l), // R + AddLight8(palColor[1], l), // G + AddLight8(palColor[2], l) // B + ); + } + + // Special map for invulnerability. Do not waste time, build it right now + BuildSpecials24(targetColormap[COLORMAP_LIGHTS_24], palColor, c); + } + + return targetColormap; + } + + /** + * RF_BuildLights lifted from dcolors.c + * + * Used to compute extended-color colormaps even in absence of the + * COLORS15 lump. Must be recomputed if gamma levels change, since + * they actually modify the RGB envelopes. + * + * @param int[] palette A packed RGB888 256-entry int palette + * @param byte[][] colormap, if supplied it will be used to translate the lights, + * the inverse colormap will be translated from it and all unused copied. + * - Good Sign 2017/04/17 + */ + default short[][] BuildLights15(int[] palette, byte[][] colormaps) { + final short[][] targetColormap = new short[Math.max(colormaps.length, COLORMAP_STD_LENGTH_15)][PAL_NUM_COLORS]; + + for (int c = 0; c < colormaps.length; ++c) { + CopyMap15(targetColormap[c], palette, colormaps[c]); + } + + return targetColormap; + } + + /** + * RF_BuildLights lifted from dcolors.c + * + * Used to compute extended-color colormaps even in absence of the + * COLORS15 lump. Must be recomputed if gamma levels change, since + * they actually modify the RGB envelopes. + * + * @param int[] palette A packed RGB888 256-entry int palette + */ + default short[][] BuildLights15(int[] palette) { + final short[][] targetColormap = new short[COLORMAP_STD_LENGTH_15][PAL_NUM_COLORS]; + final int[] palColor = new int[3]; + + // Don't repeat work more then necessary - loop first over colors, not lights + for (int c = 0; c < PAL_NUM_COLORS; ++c) { + getRGB888(palette[c], palColor); + for (int l = 0; l < COLORMAP_LIGHTS_15; ++l) { + // RGB555 for HiColor, eight times less smooth then TrueColor version + targetColormap[l][c] = toRGB555( + AddLight5(palColor[0], l), // R + AddLight5(palColor[1], l), // G + AddLight5(palColor[2], l) // B + ); + } + + // Special map for invulnerability. Do not waste time, build it right now + BuildSpecials15(targetColormap[COLORMAP_LIGHTS_15], palColor, c); + } + + return targetColormap; + } + + /** + * RF_BuildLights lifted from dcolors.c + * + * Used to compute extended-color colormaps even in absence of the + * COLORMAP lump. Must be recomputed if gamma levels change, since + * they actually modify the RGB envelopes. + * + * @param int[] palette A packed RGB888 256-entry int palette + * @return this concrete one builds Indexed colors. Maybe I would regret it + * - Good Sign 2017/04/19 + */ + default byte[][] BuildLightsI(int[] palette) { + final byte[][] targetColormap = new byte[COLORMAP_STD_LENGTH_15][PAL_NUM_COLORS]; + final int[] palColor = new int[3]; + + // Don't repeat work more then necessary - loop first over colors, not lights + for (int c = 0; c < PAL_NUM_COLORS; ++c) { + getRGB888(palette[c], palColor); + for (int l = 0; l < COLORMAP_LIGHTS_15; ++l) { + // RGB555 for HiColor, eight times less smooth then TrueColor version + targetColormap[l][c] = (byte) BestColor( + AddLightI(palColor[0], l), // R + AddLightI(palColor[1], l), // G + AddLightI(palColor[2], l), // B + palette, 0, PAL_NUM_COLORS - 1 + ); + } + + // Special map for invulnerability. Do not waste time, build it right now + BuildSpecialsI(targetColormap[COLORMAP_LIGHTS_15], palColor, palette, c); + } + + return targetColormap; + } + + /** + * @param c8 one rgb888 color component value + * @param light light level to add + * @return one rgb888 component value with added light level + */ + default int AddLight8(int c8, int light) { + return (int) (c8 * (1 - (float) light / COLORMAP_LIGHTS_24) + 0.5); + } + + /** + * @param c8 one rgb888 color component value (not a mistake - input is rgb888) + * @param light light level to add + * @return one rgb555 component value with added light level + */ + default int AddLight5(int c8, int light) { + return ((int) (c8 * (1 - (float) light / COLORMAP_LIGHTS_15) + 0.5)) >> 3; + } + + /** + * @param c8 one rgb888 color component value (not a mistake - input is rgb888) + * @param light light level to add + * @return one rgb555 component value with added light level + */ + default int AddLightI(int c8, int light) { + return (int) (c8 * (1 - (float) light / COLORMAP_LIGHTS_15) + 0.5); + } + + /** + * Decides the size of array for colormap and creates it + * @param hasColormap whether the array have lump-read colormap + * @param an array that can possibly have colormap read from COLORMAP lump + * @return empty array for colormap + */ + default int[][] AllocateColormap24(final boolean hasColormap, byte[][][] colormap) { + // if the lump-read COLORMAP is shorter, we must allocate enough + final int targetLength = hasColormap + ? COLORMAP_STD_LENGTH_24 + Math.max(0, colormap[0].length - COLORMAP_STD_LENGTH_15) + : COLORMAP_STD_LENGTH_24; + + final int[][] targetColormap = new int[targetLength][PAL_NUM_COLORS]; + return targetColormap; + } + + /** + * Decides the size of array for colormap and creates it + * @param hasColormap whether the array have lump-read colormap + * @param an array that can possibly have colormap read from COLORMAP lump + * @return empty array for colormap + */ + default short[][] AllocateColormap15(final boolean hasColormap, byte[][][] colormap) { + // if the lump-read COLORMAP is shorter, we must allocate enough + final int targetLength = hasColormap + ? Math.max(COLORMAP_STD_LENGTH_15, colormap[0].length) + : COLORMAP_STD_LENGTH_15; + + final short[][] targetColormap = new short[targetLength][PAL_NUM_COLORS]; + return targetColormap; + } + + /** + * Copy selected colormap from COLORMAP lump with respect to palette + * @param int[] stuff a 256-entry part of target colormap + * @param int[] palette A packed RGB888 256-entry int palette + * @param byte[] map a 256-entry part of COLORMAP lump to copy + */ + default void CopyMap24(int[] targetColormap, int[] palette, byte[] map) { + for (int c = 0; c < PAL_NUM_COLORS; ++c) { + targetColormap[c] = palette[map[c] & 0xFF]; + } + } + + /** + * Copy selected colormap from COLORMAP lump with respect to palette + * @param short[] stuff a 256-entry part of target colormap + * @param int[] palette A packed RGB888 256-entry int palette + * @param byte[] map a 256-entry part of COLORMAP lump to copy + */ + default void CopyMap15(short[] targetColormap, int[] palette, byte[] map) { + final int[] palColor = new int[3]; + for (int c = 0; c < PAL_NUM_COLORS; ++c) { + getRGB888(palette[map[c] & 0xFF], palColor); + targetColormap[c] = rgb888to555(palColor[0], palColor[1], palColor[2]); + } + } + + /** + * TrueColor invulnerability specials + * The key is: get the color, compute its luminance (or other method of grey if set in cfg) + * and substract it from white + * + * @param int[] stuff target array to set into + * @param int[] rgb unpacked color components + * @param index an index of the color int 256-entry int palette + */ + default void BuildSpecials24(int[] targetColormap, int[] rgb, int index) { + final float luminance = GreyscaleFilter.component((float) rgb[0], rgb[1], rgb[2]); + final int grey = (int) (255 * (1.0 - luminance / PAL_NUM_COLORS)); + targetColormap[index] = toRGB888(grey, grey, grey); + } + + /** + * HiColor invulnerability specials + * The key is: get the color, compute its luminance (or other method of grey if set in cfg) + * and substract it from white + * + * @param short[] stuff target array to set into + * @param int[] rgb unpacked color components + * @param index an index of the color int 256-entry int palette + */ + default void BuildSpecials15(short[] targetColormap, int[] rgb, int index) { + final float luminance = GreyscaleFilter.component((float) rgb[0], rgb[1], rgb[2]); + final int grey = (int) (255 * (1.0 - luminance / PAL_NUM_COLORS)); + targetColormap[index] = toRGB555(grey >> 3, grey >> 3, grey >> 3); + } + + /** + * Indexed invulnerability specials + * The key is: get the color, compute its luminance (or other method of grey if set in cfg) + * and substract it from white + * + * @param byte[] stuff target array to set into + * @param int[] rgb unpacked color components + * @param index an index of the color int 256-entry int palette + */ + default void BuildSpecialsI(byte[] targetColormap, int[] rgb, int[] palette, int index) { + final float luminance = GreyscaleFilter.component((float) rgb[0], rgb[1], rgb[2]); + final int grey = (int) (255 * (1.0 - luminance / PAL_NUM_COLORS)); + targetColormap[index] = (byte) BestColor(grey, grey, grey, palette, 0, PAL_NUM_COLORS - 1); + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Lines.java b/doom/src/v/graphics/Lines.java new file mode 100644 index 0000000..0de72a3 --- /dev/null +++ b/doom/src/v/graphics/Lines.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 1993-1996 Id Software, Inc. + * from am_map.c + * + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +public interface Lines { + + /** + * Bresenham's line algorithm modified to use custom Plotter + * + * @param plotter + * @param x2 + * @param y2 + */ + default void drawLine(Plotter plotter, int x1, int x2) { + drawLine(plotter, x1, x2, 1, 1); + } + + default void drawLine(Plotter plotter, int x2, int y2, int dupX, int dupY) { + // delta of exact value and rounded value of the dependant variable + int d = 0, dy, dx, ix, iy; + + { + final int x = plotter.getX(), y = plotter.getY(); + + dy = Math.abs(y2 - y); + dx = Math.abs(x2 - x); + + ix = x < x2 ? 1 : -1; // increment direction + iy = y < y2 ? 1 : -1; + } + + int dy2 = (dy << 1); // slope scaling factors to avoid floating + int dx2 = (dx << 1); // point + + if (dy <= dx) { + for (;;) { + plotter.plot(); + if (plotter.getX() == x2) { + break; + } + d += dy2; + if (d > dx) { + plotter.shift(ix, iy); + d -= dx2; + } else { + plotter.shiftX(ix); + } + } + } else { + for (;;) { + plotter.plot(); + if (plotter.getY() == y2) { + break; + } + d += dx2; + if (d > dy) { + plotter.shift(ix, iy); + d -= dy2; + } else { + plotter.shiftY(iy); + } + } + } + } + +} \ No newline at end of file diff --git a/doom/src/v/graphics/Melt.java b/doom/src/v/graphics/Melt.java new file mode 100644 index 0000000..e30710b --- /dev/null +++ b/doom/src/v/graphics/Melt.java @@ -0,0 +1,173 @@ +/** + * Copyright (C) 1993-1996 Id Software, Inc. + * from f_wipe.c + * + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import static utils.C2JUtils.memcpy; + +public interface Melt extends ColorTransform { + + /** + * No more fucking column-major transpose! + * A funny fast thing for 1993, but able to make Intel i7 think hard in 2017 + * (well, at least, in energy saving mode :p) + * - Good Sign, 2017/04/10 + */ + default boolean initMeltScaled(Wipers.WiperImpl wiper) { + return initMelt(wiper, true); + } + + default boolean initMelt(Wipers.WiperImpl wiper) { + return initMelt(wiper, false); + } + + default boolean initMelt(Wipers.WiperImpl wiper, boolean scaled) { + // copy start screen to main screen + memcpy(wiper.wipeStartScr, wiper.wipeScr, wiper.screenWidth * wiper.screenHeight); + setupColumnPositions(wiper, scaled); + return false; + } + + /** + * setup initial column positions + * (y<0 => not ready to scroll yet) + */ + default void setupColumnPositions(Wipers.WiperImpl wiper, boolean scaled) { + final int lim = scaled ? wiper.screenWidth / wiper.dupy : wiper.screenWidth; + wiper.y = new int[lim]; + wiper.y[0] = -(wiper.random.M_Random() % 16); + for (int i = 1; i < lim; i++) { + final int r = (wiper.random.M_Random() % 3) - 1; + wiper.y[i] = wiper.y[i - 1] + r; + + if (wiper.y[i] > 0) { + wiper.y[i] = 0; + } else if (wiper.y[i] == -16) { + wiper.y[i] = -15; + } + } + } + + /** + * The only place where we cannot have generic code, because it is 1 pixel copy operation + * which to be called tens thousands times and will cause overhead on just literally any more intermediate function + * The "same object" comparison is actually comparison of two integers - pointers in memory, - so it is instant + * and branching is predictable, so a good cache will negate the class checks completely + * - Good Sign 2017/04/10 + */ + @SuppressWarnings("MismatchedReadAndWriteOfArray") + default void toScreen(Class bufType, Object src, Object dest, int width, int dy, int ps, int pd) { + if (bufType == int[].class) { + final int[] to = (int[]) src, from = (int[]) dest; + for (int i = 0; i < dy; ++i) { + final int iWidth = width * i; + to[pd + iWidth] = from[ps + iWidth]; + } + } else if (bufType == short[].class) { + final short[] to = (short[]) src, from = (short[]) dest; + for (int i = 0; i < dy; ++i) { + final int iWidth = width * i; + to[pd + iWidth] = from[ps + iWidth]; + } + } else if (bufType == byte[].class) { + final byte[] to = (byte[]) src, from = (byte[]) dest; + for (int i = 0; i < dy; ++i) { + final int iWidth = width * i; + to[pd + iWidth] = from[ps + iWidth]; + } + } else { + throw new UnsupportedOperationException("Do not have support for: " + bufType); + } + } + + /** + * Completely opposite of the previous method. Only performant when scaling is on. + * Stick to System.arraycopy since there is certainly several pixels to get and set. + * Also, it doesn't even need to check and cast to classes + * - Good Sign 2017/04/10 + */ + @SuppressWarnings("SuspiciousSystemArraycopy") + default void toScreenScaled(Wipers.WiperImpl wiper, Object from, int dy, int ps, int pd) { + for (int i = 0; i < dy; ++i) { + final int iWidth = wiper.screenWidth * i; + System.arraycopy(from, ps + iWidth, wiper.wipeScr, pd + iWidth, wiper.dupy); + } + } + + /** + * Scrolls down columns ready for scroll and those who aren't makes a bit more ready + * Finally no more shitty transpose! + * - Good Sign 2017/04/10 + */ + default boolean doMeltScaled(Wipers.WiperImpl wiper) { + return doMelt(wiper, true); + } + + default boolean doMelt(Wipers.WiperImpl wiper) { + return doMelt(wiper, false); + } + + default boolean doMelt(Wipers.WiperImpl wiper, boolean scaled) { + final int lim = scaled ? wiper.screenWidth / wiper.dupy : wiper.screenWidth; + boolean done = true; + + while (wiper.ticks-- > 0) { + for (int i = 0; i < lim; i++) { + // Column won't start yet. + if (wiper.y[i] < 0) { + wiper.y[i]++; + done = false; + } else if (wiper.y[i] < wiper.screenHeight) { + int dy = (wiper.y[i] < wiper.scaled_16) ? wiper.y[i] + (scaled ? wiper.dupy : 1) : wiper.scaled_8; + if (wiper.y[i] + dy >= wiper.screenHeight) { + dy = wiper.screenHeight - wiper.y[i]; + } + int pd = wiper.y[i] * wiper.screenWidth + (scaled ? i * wiper.dupx : i); + + // MAES: this part should draw the END SCREEN "behind" the melt. + if (scaled) { + toScreenScaled(wiper, wiper.wipeEndScr, dy, pd, pd); + } else { + toScreen(wiper.bufferType, wiper.wipeScr, wiper.wipeEndScr, wiper.screenWidth, dy, pd, pd); + } + + wiper.y[i] += dy; + pd += dy * wiper.screenWidth; + + // This draws a column shifted by y[i] + if (scaled) { + toScreenScaled(wiper, wiper.wipeStartScr, wiper.screenHeight - wiper.y[i], i * wiper.dupy, pd); + } else { + toScreen(wiper.bufferType, wiper.wipeScr, wiper.wipeStartScr, wiper.screenWidth, wiper.screenHeight - wiper.y[i], i, pd); + } + + done = false; + } + } + } + + return done; + } + + default boolean exitMelt(Wipers.WiperImpl wiper) { + wiper.y = null; //Z_Free(y); + wiper.ticks = 0; + return false; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Palettes.java b/doom/src/v/graphics/Palettes.java new file mode 100644 index 0000000..9a01cf8 --- /dev/null +++ b/doom/src/v/graphics/Palettes.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package v.graphics; + +import java.awt.image.IndexColorModel; +import static v.tables.GammaTables.LUT; + +/** + * Refactored and included as the module of new software 2d graphics API + * - Good Sign 2017/04/14 + * + * Palettes & colormaps library + * + * @author Good Sign + * @author Maes + */ +public interface Palettes extends Lights { + + /** + * Maximum number of colors in palette + */ + static int PAL_NUM_COLORS = 256; + + /** + * There is 256 colors in standard PALYPAL lump, 3 bytes for each color (RGB value) + * totaling 256 * 3 = 768 bytes + */ + final int PAL_NUM_STRIDES = 3; + + /** + * Maximum number of palettes + * PLAYPAL length / (PAL_NUM_COLORS * PAL_NUM_STRIDES) + * + * TODO: think some way of support for future Hexen, Heretic, Strife palettes + */ + static int NUM_PALETTES = 14; + + /** + * Methods to be used by implementor + */ + /** + * Perform any action necessary so that palettes get modified according to specified gamma. + * Consider this a TIME CONSUMING operation, so don't call it unless really necessary. + * + * @param gammalevel + */ + void setUsegamma(int gammalevel); + + /** + * Getter for gamma level + * + * @return + */ + int getUsegamma(); + + /** + * Perform any action necessary so that the screen output uses the specified palette + * Consider this a TIME CONSUMING operation, so don't call it unless really necessary. + * + * @param palette + */ + void setPalette(int palette); + + /** + * Getter for palette + * + * @return + */ + int getPalette(); + + /** + * Get the value corresponding to a base color (0-255). + * Depending on the implementation this might be indexed, + * RGB etc. Use whenever you need "absolute" colors. + * + * @return int + */ + int getBaseColor(byte color); + + default int getBaseColor(int color) { + return getBaseColor((byte) color); + } + + /** + * Extracts RGB888 color from an index in the palette + * @param byte[] pal proper playpal + * @param int index and index of the color in the palette + * @return int packed opaque rgb888 pixel + */ + default int paletteToRGB888(byte[] pal, int index) { + return toRGB888(pal[index], pal[index + 1], pal[index + 2]); + } + + /** + * Extracts RGB555 color from an index in the palette + * @param byte[] pal proper playpal + * @param int index and index of the color in the palette + * @return int packed rgb555 pixel + */ + default short paletteToRGB555(byte[] pal, int index) { + return rgb888to555(pal[index], pal[index + 1], pal[index + 2]); + } + + /** + * Extracts RGB888 color components from an index in the palette to the container + * @param byte[] pal proper playpal + * @param byte index and index of the color in the palette + * @param int[] container to hold individual RGB color components + * @return int[] the populated container + */ + default int[] getPaletteRGB888(byte[] pal, int index, int[] container) { + container[0] = pal[index] & 0xFF; + container[1] = pal[index + 1] & 0xFF; + container[2] = pal[index + 2] & 0xFF; + return container; + } + + /** + * ColorShiftPalette - lifted from dcolors.c Operates on RGB888 palettes in + * separate bytes. at shift = 0, the colors are normal at shift = steps, the + * colors are all the given rgb + */ + default void ColorShiftPalette(byte[] inpal, byte[] outpal, int r, int g, int b, int shift, int steps) { + int in_p = 0; + int out_p = 0; + for (int i = 0; i < PAL_NUM_COLORS; i++) { + final int dr = r - inpal[in_p + 0]; + final int dg = g - inpal[in_p + 1]; + final int db = b - inpal[in_p + 2]; + outpal[out_p + 0] = (byte) (inpal[in_p + 0] + dr * shift / steps); + outpal[out_p + 1] = (byte) (inpal[in_p + 1] + dg * shift / steps); + outpal[out_p + 2] = (byte) (inpal[in_p + 2] + db * shift / steps); + in_p += 3; + out_p += 3; + } + } + + /** + * Given raw palette data, returns an array with proper TrueColor data + * @param byte[] pal proper palette + * @return int[] 32 bit Truecolor ARGB colormap + */ + default int[] paletteTrueColor(byte[] pal) { + final int pal888[] = new int[PAL_NUM_COLORS]; + + // Initial palette can be neutral or based upon "gamma 0", + // which is actually a bit biased and distorted + for (int x = 0; x < PAL_NUM_COLORS; ++x) { + pal888[x] = paletteToRGB888(pal, x * PAL_NUM_STRIDES); + } + + return pal888; + } + + /** + * Given raw palette data, returns an array with proper HiColor data + * @param byte[] pal proper palette + * @return short[] 16 bit HiColor RGB colormap + */ + default short[] paletteHiColor(byte[] pal) { + final short[] pal555 = new short[PAL_NUM_COLORS]; + + // Apply gammas a-posteriori, not a-priori. + // Initial palette can be neutral or based upon "gamma 0", + // which is actually a bit biased and distorted + for (int x = 0; x < PAL_NUM_COLORS; ++x) { + pal555[x] = paletteToRGB555(pal, x * PAL_NUM_STRIDES); + } + + return pal555; + } + + /** + * Given an array of certain length and raw palette data fills array + * with IndexColorModel's for each palette. Gammas are applied a-priori + * @param IndexColorModel[][] cmaps preallocated array, as it is often reconstructed for gamma, do not reallocate it + * @param byte[] pal proper palette + * @return the same araay as input, but all values set to new IndexColorModels + */ + default IndexColorModel[][] cmapIndexed(IndexColorModel icms[][], byte[] pal) { + final int colorsXstride = PAL_NUM_COLORS * PAL_NUM_STRIDES; + + // Now we have our palettes. + for (int i = 0; i < icms[0].length; ++i) { + //new IndexColorModel(8, PAL_NUM_COLORS, pal, i * colorsXstride, false); + icms[0][i] = createIndexColorModel(pal, i * colorsXstride); + } + + // Wire the others according to the gamma table. + final byte[] tmpcmap = new byte[colorsXstride]; + + // For each gamma value... + for (int j = 1; j < LUT.length; j++) { + // For each palette + for (int i = 0; i < NUM_PALETTES; i++) { + for (int k = 0; k < PAL_NUM_COLORS; ++k) { + final int iXcolorsXstride_plus_StrideXk = i * colorsXstride + PAL_NUM_STRIDES * k; + tmpcmap[3 * k/**/] = (byte) LUT[j][0xFF & pal[/**/iXcolorsXstride_plus_StrideXk]]; // R + tmpcmap[3 * k + 1] = (byte) LUT[j][0xFF & pal[1 + iXcolorsXstride_plus_StrideXk]]; // G + tmpcmap[3 * k + 2] = (byte) LUT[j][0xFF & pal[2 + iXcolorsXstride_plus_StrideXk]]; // B + } + + icms[j][i] = createIndexColorModel(tmpcmap, 0); + } + } + + return icms; + } + + /** + * @param byte[] cmap a colormap from which to make color model + * @param int start position in colormap from which to take PAL_NUM_COLORS + * @return IndexColorModel + */ + default IndexColorModel createIndexColorModel(byte cmap[], int start) { + return new IndexColorModel(8, PAL_NUM_COLORS, cmap, start, false); + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Patches.java b/doom/src/v/graphics/Patches.java new file mode 100644 index 0000000..e7d1f86 --- /dev/null +++ b/doom/src/v/graphics/Patches.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.patch_t; +import utils.C2JUtils; +import static v.DoomGraphicSystem.V_FLIPPEDPATCH; +import static v.DoomGraphicSystem.V_NOSCALEOFFSET; +import static v.DoomGraphicSystem.V_NOSCALEPATCH; +import static v.DoomGraphicSystem.V_NOSCALESTART; +import static v.DoomGraphicSystem.V_PREDIVIDE; +import static v.DoomGraphicSystem.V_SAFESCALE; +import static v.DoomGraphicSystem.V_SCALEOFFSET; +import static v.DoomGraphicSystem.V_SCALEPATCH; +import static v.DoomGraphicSystem.V_SCALESTART; +import v.scale.VideoScale; + +/** + * Rewritten unified patch-drawing methods with parallelism (yeah, multithread!) + * Note that now most of the functions now support FLAGS now as a separate argument, I totally needed screen id safety. + * Reimplemented counter-flags, that work the next way: + * - there is a Default Behavior chose when flag is not present + * - if the flag is present Default Behavior is changed + * - if both the flag and the opposite flag are present, then the flag that restores Default Behavior takes precedence + * + * I tried my best to preserve all of the features done by prior contributors. - Good Sign 2017/04/02 + * + * @author Good Sign + * + * About all DrawPatch functions: + * It uses FLAGS (see above) (now as a separate - Good Sign 2017/04/04) parameter, to be + * parsed afterwards. Shamelessly ripped from Doom Legacy (for menus, etc) by _D_ ;-) + * + * added:05-02-98: default params : scale patch and scale start + * + * Iniially implemented for Mocha Doom by _D_ (shamelessly ripped from Eternity Engine ;-), adapted to scale based + * on a scaling info object (VSI). + * + * Unless overriden by flags, starting x and y are automatically scaled (implied V_SCALESTART) + */ +public interface Patches> extends Columns { + + static final Logger LOGGER = Loggers.getLogger(Patches.class.getName()); + + /** + * V_DrawPatch + * + * Draws a patch to the screen without centering or scaling + */ + default void DrawPatch(E screen, patch_t patch, int x, int y, int... flags) { + DrawPatchScaled(screen, patch, null, x, y, flags); + } + + /** + * V_DrawPatch + * + * Draws a patch to the screen without centering or scaling + */ + default void DrawPatchCentered(E screen, patch_t patch, int y, int... flags) { + Patches.this.DrawPatchCenteredScaled(screen, patch, null, y, flags); + } + + /** + * V_DrawScaledPatch like V_DrawPatch, but scaled with IVideoScale object scaling + * Centers the x coordinate on a screen based on patch width and offset + * I have completely reworked column drawing code, so it resides in another class, and supports parallelism + * - Good Sign 2017/04/04 + * + * It uses FLAGS (see above) (now as a separate - Good Sign 2017/04/04) parameter, to be + * parsed afterwards. Shamelessly ripped from Doom Legacy (for menus, etc) by _D_ ;-) + */ + default void DrawPatchCenteredScaled(E screen, patch_t patch, VideoScale vs, int y, int... flags) { + final int flagsV = flags.length > 0 ? flags[0] : 0; + int dupx, dupy; + if (vs != null) { + if (C2JUtils.flags(flagsV, V_SAFESCALE)) { + dupx = dupy = vs.getSafeScaling(); + } else { + dupx = vs.getScalingX(); + dupy = vs.getScalingY(); + } + } else { + dupx = dupy = 1; + } + final boolean predevide = C2JUtils.flags(flagsV, V_PREDIVIDE); + // By default we scale, if V_NOSCALEOFFSET we dont scale unless V_SCALEOFFSET (restores Default Behavior) + final boolean scaleOffset = !C2JUtils.flags(flagsV, V_NOSCALEOFFSET) || C2JUtils.flags(flagsV, V_SCALEOFFSET); + // By default we scale, if V_NOSCALESTART we dont scale unless V_SCALESTART (restores Default Behavior) + final boolean scaleStart = !C2JUtils.flags(flagsV, V_NOSCALESTART) || C2JUtils.flags(flagsV, V_SCALESTART); + // By default we do dup, if V_NOSCALEPATCH we dont dup unless V_SCALEPATCH (restores Default Behavior) + final boolean noScalePatch = C2JUtils.flags(flagsV, V_NOSCALEPATCH) && !C2JUtils.flags(flagsV, V_SCALEPATCH); + final boolean flip = C2JUtils.flags(flagsV, V_FLIPPEDPATCH); + final int halfWidth = noScalePatch ? patch.width / 2 : patch.width * dupx / 2; + int x = getScreenWidth() / 2 - halfWidth - (scaleOffset ? patch.leftoffset * dupx : patch.leftoffset); + y = applyScaling(y, patch.topoffset, dupy, predevide, scaleOffset, scaleStart); + + if (noScalePatch) { + dupx = dupy = 1; + } + + try { + doRangeCheck(x, y, patch, dupx, dupy); + DrawPatchColumns(getScreen(screen), patch, x, y, dupx, dupy, flip); + } catch (BadRangeException ex) { + printDebugPatchInfo(patch, x, y, predevide, scaleOffset, scaleStart, dupx, dupy); + } + } + + /** + * This method should help to debug bad patches or bad placement of them + * - Good Sign 2017/04/22 + */ + default void printDebugPatchInfo(patch_t patch, int x, int y, final boolean predevide, final boolean scaleOffset, final boolean scaleStart, int dupx, int dupy) { + LOGGER.log(Level.INFO, () -> String.format( + "V_DrawPatch: bad patch (ignored)\n" + + "Patch %s at %d, %d exceeds LFB\n" + + "\tpredevide: %s\n" + + "\tscaleOffset: %s\n" + + "\tscaleStart: %s\n" + + "\tdupx: %s, dupy: %s\n" + + "\tleftoffset: %s\n" + + "\ttopoffset: %s\n", + patch.name, x, y, + predevide, scaleOffset, scaleStart, dupx, dupy, patch.leftoffset, patch.topoffset + )); + } + + /** + * V_DrawPatch + * + * V_DrawScaledPatch like V_DrawPatch, but scaled with IVideoScale object scaling + * I have completely reworked column drawing code, so it resides in another class, and supports parallelism + * - Good Sign 2017/04/04 + */ + default void DrawPatchScaled(E screen, patch_t patch, VideoScale vs, int x, int y, int... flags) { + final int flagsV = flags.length > 0 ? flags[0] : 0; + int dupx, dupy; + if (vs != null) { + if (C2JUtils.flags(flagsV, V_SAFESCALE)) { + dupx = dupy = vs.getSafeScaling(); + } else { + dupx = vs.getScalingX(); + dupy = vs.getScalingY(); + } + } else { + dupx = dupy = 1; + } + final boolean predevide = C2JUtils.flags(flagsV, V_PREDIVIDE); + // By default we scale, if V_NOSCALEOFFSET we dont scale unless V_SCALEOFFSET (restores Default Behavior) + final boolean scaleOffset = !C2JUtils.flags(flagsV, V_NOSCALEOFFSET) || C2JUtils.flags(flagsV, V_SCALEOFFSET); + // By default we scale, if V_NOSCALESTART we dont scale unless V_SCALESTART (restores Default Behavior) + final boolean scaleStart = !C2JUtils.flags(flagsV, V_NOSCALESTART) || C2JUtils.flags(flagsV, V_SCALESTART); + // By default we do dup, if V_NOSCALEPATCH we dont dup unless V_SCALEPATCH (restores Default Behavior) + final boolean noScalePatch = C2JUtils.flags(flagsV, V_NOSCALEPATCH) && !C2JUtils.flags(flagsV, V_SCALEPATCH); + final boolean flip = C2JUtils.flags(flagsV, V_FLIPPEDPATCH); + x = applyScaling(x, patch.leftoffset, dupx, predevide, scaleOffset, scaleStart); + y = applyScaling(y, patch.topoffset, dupy, predevide, scaleOffset, scaleStart); + + if (noScalePatch) { + dupx = dupy = 1; + } + + try { + doRangeCheck(x, y, patch, dupx, dupy); + DrawPatchColumns(getScreen(screen), patch, x, y, dupx, dupy, flip); + } catch (BadRangeException ex) { + // Do not abort! + printDebugPatchInfo(patch, x, y, predevide, scaleOffset, scaleStart, dupx, dupy); + } + } + + /** + * Replaces DrawPatchCol for bunny scrolled in Finale. + * Also uses my reworked column code, but that one is not parallelized + * - Good Sign 2017/04/04 + */ + default void DrawPatchColScaled(E screen, patch_t patch, VideoScale vs, int x, int col) { + final int dupx = vs.getScalingX(), dupy = vs.getScalingY(); + x -= patch.leftoffset; + x *= dupx; + + DrawColumn( + getScreen(screen), + patch.columns[col], + new Horizontal(point(x, 0), dupx), + convertPalettedBlock(patch.columns[col].data), + getScreenWidth(), + dupy + ); + } + + default int applyScaling(int c, int offset, int dup, boolean predevide, boolean scaleOffset, boolean scaleStart) { + // A very common operation, eliminates the need to pre-divide. + if (predevide) { + c /= getScalingX(); + } + + // Scale start before offsetting, it seems right to do so - Good Sign 2017/04/04 + if (scaleStart) { + c *= dup; + } + + // MAES: added this fix so that non-zero patch offsets can be + // taken into account, regardless of whether we use pre-scaled + // coords or not. Only Doomguy's face needs this hack for now. + c -= scaleOffset ? offset * dup : offset; + return c; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Plotter.java b/doom/src/v/graphics/Plotter.java new file mode 100644 index 0000000..d0b4ade --- /dev/null +++ b/doom/src/v/graphics/Plotter.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.lang.reflect.Array; +import java.util.Objects; +import static utils.GenericCopy.memcpy; +import static utils.GenericCopy.memset; +import static v.graphics.Direction.CENTER; + +/** + * + * @author Good Sign + */ +public interface Plotter { + + default Plotter setColorSource(V colorSource) { + return setColorSource(colorSource, 0); + } + + Plotter setColorSource(V colorSource, int colorPos); + + Plotter setPosition(int x, int y); + + Plotter setThickness(int dupX, int dupY); + + Plotter plot(); + + Plotter shiftX(int shift); + + Plotter shiftY(int shift); + + int getX(); + + int getY(); + + enum Style { + Thin, Thick, Deep + } + + default Plotter shift(int shiftX, int shiftY) { + return shiftX(shiftX).shiftY(shiftY); + } + + /** + * Abstract plotter - without a Plot method + */ + abstract class Abstract implements Plotter { + + protected final V screen; + protected final int rowShift; + + protected Style style; + protected V colorSource; + protected int point; + protected int x; + protected int y; + + Abstract(V screen, int rowShift) { + this.screen = screen; + this.rowShift = rowShift; + } + + @Override + @SuppressWarnings("unchecked") + public Plotter setColorSource(V colorSource, int colorPos) { + Objects.requireNonNull(colorSource); + // cache only necessary part of the source + this.colorSource = (V) Array.newInstance(colorSource.getClass().getComponentType(), 1); + memcpy(colorSource, colorPos, this.colorSource, 0, 1); + return this; + } + + @Override + public Plotter setThickness(int dupX, int dupY) { + return this; + } + + @Override + public Plotter setPosition(int x, int y) { + this.point = y * rowShift + x; + this.x = x; + this.y = y; + return this; + } + + @Override + public Plotter shiftX(int shift) { + point += shift; + x += shift; + return this; + } + + @Override + public Plotter shiftY(int shift) { + if (shift > 0) { + point += rowShift; + ++y; + } else { + point -= rowShift; + --y; + } + + return this; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + } + + class Thin extends Abstract { + + public Thin(V screen, int rowShift) { + super(screen, rowShift); + } + + @Override + public Plotter plot() { + memcpy(colorSource, 0, screen, point, 1); + return this; + } + } + + static int getThickness(int dupX) { + return Math.max(dupX >> 1, 1); + } + + /** + * You give it desired scaling level, it makes lines thicker + */ + class Thick extends Abstract { + + protected final int height; + protected int xThick; + protected int yThick; + + public Thick(V screen, int width, int height) { + super(screen, width); + this.height = height; + + // can overflow! + this.xThick = 1;//dupX >> 1; + this.yThick = 1;//dupX >> 1; + } + + @Override + public Plotter setThickness(int dupX, int dupY) { + this.xThick = dupX; + this.yThick = dupY; + return this; + } + + @Override + public Plotter plot() { + if (xThick == 0 || yThick == 0) { + memcpy(colorSource, 0, screen, point, 1); + return this; + } + return plotThick(xThick, yThick); + } + + protected Plotter plotThick(int modThickX, int modThickY) { + final int rows = y < modThickY ? y : (height < y + modThickY ? height - y : modThickY); + final int spaceLeft = x < modThickX ? 0 : modThickX; + final int spaceRight = rowShift < x + modThickX ? rowShift - x : modThickX; + + for (int row = -rows; row < rows; ++row) { + // color = colorSource[Math.abs(row)] + memset(screen, point - spaceLeft + rowShift * row, spaceLeft + spaceRight, colorSource, 0, 1); + } + + return this; + } + } + + /** + * Thick, but the direction of drawing is counted in - i.e., for round borders... + */ + class Deep extends Thick { + + protected Direction direction; + + public Deep(V screen, int width, int height) { + super(screen, width, height); + } + + @Override + public Plotter setPosition(int x, int y) { + direction = CENTER; + return super.setPosition(x, y); + } + + @Override + public Plotter shiftX(int shift) { + direction = direction.rotationHor(shift); + return super.shiftX(shift); + } + + @Override + public Plotter shiftY(int shift) { + direction = direction.rotationVert(shift); + return super.shiftY(shift); + } + + @Override + public Plotter shift(int shiftX, int shiftY) { + direction = direction.rotation(shiftX, shiftY); + return super.shift(shiftX, shiftY); + } + + @Override + public Plotter plot() { + if (xThick <= 1 || yThick <= 1) { + return super.plot(); + } + + int modThickX = xThick; + int modThickY = yThick; + + if (!direction.hasTop && !direction.hasBottom) { + modThickX >>= 1; + } + + if (!direction.hasLeft && !direction.hasRight) { + modThickY >>= 1; + } + + return plotThick(modThickX, modThickY); + } + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Points.java b/doom/src/v/graphics/Points.java new file mode 100644 index 0000000..1770811 --- /dev/null +++ b/doom/src/v/graphics/Points.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import rr.patch_t; + +/** + * + * @author Good Sign + */ +public interface Points> extends Screens { + + default void doRangeCheck(int x, int y, int width, int height) throws BadRangeException { + if (x >= 0 && y >= 0) { + final int scrWidth = this.getScreenWidth(); + final int scrHeight = this.getScreenHeight(); + if (x + width > scrWidth || y + height > scrWidth) { + throw new BadRangeException(String.format( + "Coordinates overflow screen space: (%d, %d, %d, %d) on screen %dx%d", + x, y, x + width, y + height, scrWidth, scrHeight) + ); + } + } else { + throw new IllegalArgumentException(String.format("Invalid coordinates: (%d, %d)", x, y)); + } + } + + default void doRangeCheck(int x, int y, patch_t patch) throws BadRangeException { + doRangeCheck(x, y, patch.width, patch.height); + } + + default void doRangeCheck(int x, int y, patch_t patch, int dupx, int dupy) throws BadRangeException { + doRangeCheck(x, y, patch.width * dupx, patch.height * dupy); + } + + default int point(int x, int y) { + return y * getScreenWidth() + x; + } + + default int point(int x, int y, int width) { + return y * width + x; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Rectangles.java b/doom/src/v/graphics/Rectangles.java new file mode 100644 index 0000000..b9ce9b4 --- /dev/null +++ b/doom/src/v/graphics/Rectangles.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import java.awt.Rectangle; + +/** + * Rectangles fill and copy + * + * TODO: range checks on Fill & Copy + * + * @author Good Sign + */ +public interface Rectangles> extends Blocks, Points { + + /** + * Computes a Horizontal with a row from the Rectangle at heightIndex + * @param rect + * @param heightIndex + * @return + */ + default Horizontal GetRectRow(Rectangle rect, int heightIndex) { + if (heightIndex < 0 || heightIndex > rect.height) { + throw new IndexOutOfBoundsException("Bad row index: " + heightIndex); + } + + return new Horizontal(point(rect.x, rect.y) + heightIndex * getScreenWidth(), rect.width); + } + + /** + * V_CopyRect + */ + default void CopyRect(E srcScreenType, Rectangle rectangle, E dstScreenType) { + final V srcScreen = getScreen(srcScreenType); + final V dstScreen = getScreen(dstScreenType); + final int screenWidth = getScreenWidth(); + final int point = point(rectangle.x, rectangle.y); + final Relocation rel = new Relocation(point, point, rectangle.width); + for (int h = rectangle.height; h > 0; --h, rel.shift(screenWidth)) { + screenCopy(srcScreen, dstScreen, rel); + } + } + + default void CopyRect(E srcScreenType, Rectangle rectangle, E dstScreenType, int dstPoint) { + final V srcScreen = getScreen(srcScreenType); + final V dstScreen = getScreen(dstScreenType); + final int screenWidth = getScreenWidth(); + final Relocation rel = new Relocation(point(rectangle.x, rectangle.y), dstPoint, rectangle.width); + for (int h = rectangle.height; h > 0; --h, rel.shift(screenWidth)) { + screenCopy(srcScreen, dstScreen, rel); + } + } + + /** + * V_FillRect + */ + default void FillRect(E screenType, Rectangle rectangle, V patternSrc, Horizontal pattern) { + final V screen = getScreen(screenType); + if (rectangle.height > 0) { + final Horizontal row = GetRectRow(rectangle, 0); + // Fill first line of rect + screenSet(patternSrc, pattern, screen, row); + // Fill the rest of the rect + RepeatRow(screen, row, rectangle.height - 1); + } + } + + default void FillRect(E screenType, Rectangle rectangle, V patternSrc, int point) { + final V screen = getScreen(screenType); + if (rectangle.height > 0) { + final Horizontal row = GetRectRow(rectangle, 0); + // Fill first line of rect + screenSet(patternSrc, point, screen, row); + // Fill the rest of the rect + RepeatRow(screen, row, rectangle.height - 1); + } + } + + default void FillRect(E screenType, Rectangle rectangle, int color) { + FillRect(screenType, rectangle, (byte) color); + } + + default void FillRect(E screenType, Rectangle rectangle, byte color) { + final V screen = getScreen(screenType); + if (rectangle.height > 0) { + final V filler = convertPalettedBlock(color); + final Horizontal row = GetRectRow(rectangle, 0); + // Fill first line of rect + screenSet(filler, 0, screen, row); + // Fill the rest of the rect + RepeatRow(screen, row, rectangle.height - 1); + } + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Relocation.java b/doom/src/v/graphics/Relocation.java new file mode 100644 index 0000000..294a9d0 --- /dev/null +++ b/doom/src/v/graphics/Relocation.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +/** + * Relocation represents a move of a fixed length of bytes/shorts/ints + * from one range in screen buffer to another range of the same size + * + * @author Good Sign + */ +public final class Relocation { + + public int source; + public int destination; + public int length; + + public Relocation() { + } + + public Relocation(int source, int destination, int length) { + this.source = source; + this.destination = destination; + this.length = length; + } + + public Relocation shift(int amount) { + this.source += amount; + this.destination += amount; + return this; + } + + public Relocation retarget(int source, int destination) { + this.source = source; + this.destination = destination; + return this; + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Screens.java b/doom/src/v/graphics/Screens.java new file mode 100644 index 0000000..918b874 --- /dev/null +++ b/doom/src/v/graphics/Screens.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import f.Wiper; +import java.lang.reflect.Array; +import m.IRandom; +import static utils.GenericCopy.memcpy; +import static utils.GenericCopy.memset; +import v.renderers.DoomScreen; + +/** + * Screen surface library + * + * @author Good Sign + */ +public interface Screens> { + + final int SCREENS_COUNT = DoomScreen.values().length; + + V getScreen(E screenType); + + int getScalingX(); + + int getScalingY(); + + int getScreenWidth(); + + int getScreenHeight(); + + Wiper createWiper(IRandom random); + + /** + * memset-like methods for screen surfaces + */ + /** + * Will fill destPortion on the screen with color of the specified point on it + * The point argument IS NOT a color to fill, only a POINTER to the pixel on the screen + */ + default void screenSet(V screen, int point, Horizontal destination) { + memset(screen, destination.start, destination.length, screen, point, 1); + } + + /** + * Will fill destPortion on the dstScreen by scrPortion pattern from srcScreen + */ + default void screenSet(V srcScreen, Horizontal pattern, V dstScreen, Horizontal destination) { + memset(dstScreen, destination.start, destination.length, srcScreen, pattern.start, pattern.length); + } + + /** + * Will fill destPortion on the dstScreen with color of the specified point on the srcScreen + * The point argument IS NOT a color to fill, only a POINTER to the pixel on the screen + */ + default void screenSet(V srcScreen, int point, V dstScreen, Horizontal destination) { + memset(dstScreen, destination.start, destination.length, srcScreen, point, 1); + } + + /** + * Will fill destPortion on the screen with srcPortion pattern from the same screen + */ + default void screenSet(V screen, Horizontal pattern, Horizontal destination) { + memset(screen, destination.start, destination.length, screen, pattern.start, pattern.length); + } + + /** + * memcpy-like method for screen surfaces + */ + default void screenCopy(V srcScreen, V dstScreen, Relocation relocation) { + memcpy(srcScreen, relocation.source, dstScreen, relocation.destination, relocation.length); + } + + default void screenCopy(E srcScreen, E dstScreen) { + final Object dstScreenObj = getScreen(dstScreen); + memcpy(getScreen(srcScreen), 0, dstScreenObj, 0, Array.getLength(dstScreenObj)); + } + + default Plotter createPlotter(E screen) { + return new Plotter.Thin<>(getScreen(screen), getScreenWidth()); + } + + class BadRangeException extends Exception { + + private static final long serialVersionUID = 2903441181162189295L; + + public BadRangeException(String m) { + super(m); + } + + public BadRangeException() { + } + } +} \ No newline at end of file diff --git a/doom/src/v/graphics/Wipers.java b/doom/src/v/graphics/Wipers.java new file mode 100644 index 0000000..ad0543d --- /dev/null +++ b/doom/src/v/graphics/Wipers.java @@ -0,0 +1,206 @@ +/** + * Copyright (C) 1993-1996 Id Software, Inc. + * from f_wipe.c + * + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.graphics; + +import f.Wiper; +import java.lang.reflect.Array; +import m.IRandom; +import utils.GenericCopy; +import v.graphics.Wipers.WipeFunc.WF; + +/** + * SCREEN WIPE PACKAGE + */ +public class Wipers implements ColorTransform, Melt { + + private static final Wipers instance = new Wipers(); + + /** + * They are repeated thrice for a reason - they are overloads with different arguments + * - Good Sign 2017/04/06 + * + * ASS-WIPING functions + */ + public enum WipeFunc { + doColorXFormB(instance::colorTransformB, byte[].class), + doColorXFormS(instance::colorTransformS, short[].class), + doColorXFormI(instance::colorTransformI, int[].class), + initColorXForm(instance::initTransform), + doColorXForm(doColorXFormB, doColorXFormS, doColorXFormI), + exitColorXForm(w -> false), + initScaledMelt(instance::initMeltScaled), + doScaledMelt(instance::doMeltScaled), + initMelt(instance::initMelt), + doMelt(instance::doMelt), + exitMelt(instance::exitMelt); + + private final Class supportFor; + private final WF func; + + WipeFunc(WF func) { + this.supportFor = null; + this.func = func; + } + + WipeFunc(WF func, Class supportFor) { + this.supportFor = supportFor; + this.func = func; + } + + WipeFunc(final WipeFunc... wf) { + this.supportFor = null; + this.func = wipeChoice(wf); + } + + private static WF wipeChoice(final WipeFunc[] wf) { + return (WiperImpl wiper) -> { + for (int i = 0; i < wf.length; ++i) { + if (wiper.bufferType == wf[i].supportFor) { + @SuppressWarnings("unchecked") // checked + final WF supported = (WF) wf[i].func; + return supported.invoke(wiper); + } + } + + throw new UnsupportedOperationException("Do not have support for: " + wiper.bufferType); + }; + } + + interface WF { + + public boolean invoke(WiperImpl wiper); + } + } + + public static > Wiper createWiper(IRandom rnd, Screens screens, E ws, E we, E ms) { + return new WiperImpl<>(rnd, screens, ws, we, ms); + } + + protected final static class WiperImpl> implements Wiper { + + private final Relocation relocation = new Relocation(0, 0, 1); + final IRandom random; + final Screens screens; + final Class bufferType; + final V wipeStartScr; + final V wipeEndScr; + final V wipeScr; + final int screenWidth; + final int screenHeight; + final int dupx; + final int dupy; + final int scaled_16; + final int scaled_8; + int[] y; + int ticks; + + /** when false, stop the wipe */ + volatile boolean go = false; + + private WiperImpl(IRandom RND, Screens screens, E wipeStartScreen, E wipeEndScreen, E mainScreen) { + this.random = RND; + this.wipeStartScr = screens.getScreen(wipeStartScreen); + this.wipeEndScr = screens.getScreen(wipeEndScreen); + this.wipeScr = screens.getScreen(mainScreen); + this.bufferType = this.wipeScr.getClass(); + this.screens = screens; + this.screenWidth = screens.getScreenWidth(); + this.screenHeight = screens.getScreenHeight(); + this.dupx = screens.getScalingX(); + this.dupy = screens.getScalingY(); + this.scaled_16 = dupy << 4; + this.scaled_8 = dupy << 3; + } + + void startToScreen(int source, int destination) { + screens.screenCopy(wipeStartScr, wipeScr, relocation.retarget(source, destination)); + } + + void endToScreen(int source, int destination) { + screens.screenCopy(wipeEndScr, wipeScr, relocation.retarget(source, destination)); + } + + /** + * Sets "from" screen and stores it in "screen 2" + */ + @Override + public boolean StartScreen(int x, int y, int width, int height) { + GenericCopy.memcpy(wipeScr, 0, wipeStartScr, 0, Array.getLength(wipeStartScr)); + return false; + } + + /** + * Sets "to" screen and stores it to "screen 3" + */ + @Override + public boolean EndScreen(int x, int y, int width, int height) { + // Set end screen to "screen 3" and copy visible screen to it. + GenericCopy.memcpy(wipeScr, 0, wipeEndScr, 0, Array.getLength(wipeEndScr)); + // Restore starting screen. + GenericCopy.memcpy(wipeStartScr, 0, wipeScr, 0, Array.getLength(wipeScr)); + return false; + } + + @SuppressWarnings("unchecked") + private boolean invokeCheckedFunc(WipeFunc f) { + return ((WF) f.func).invoke(this); + } + + @Override + public boolean ScreenWipe(WipeType type, int x, int y, int width, int height, int ticks) { + boolean rc; + + //System.out.println("Ticks do "+ticks); + this.ticks = ticks; + + // initial stuff + if (!go) { + go = true; + //wipe_scr = new byte[width*height]; // DEBUG + // HOW'S THAT FOR A FUNCTION POINTER, BIATCH?! + invokeCheckedFunc(type.getInitFunc()); + } + + // do a piece of wipe-in + rc = invokeCheckedFunc(type.getDoFunc()); + // V.DrawBlock(x, y, 0, width, height, wipe_scr); // DEBUG + + // final stuff + if (rc) { + go = false; + invokeCheckedFunc(type.getExitFunc()); + } + + return !go; + } + } + + public interface WipeType { + + WipeFunc getInitFunc(); + + WipeFunc getDoFunc(); + + WipeFunc getExitFunc(); + } + + private Wipers() { + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/BppMode.java b/doom/src/v/renderers/BppMode.java new file mode 100644 index 0000000..36bf9fb --- /dev/null +++ b/doom/src/v/renderers/BppMode.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import doom.CVarManager; +import doom.CommandVariable; +import doom.DoomMain; +import java.awt.Transparency; +import java.util.function.Function; +import m.Settings; +import mochadoom.Engine; +import rr.SceneRenderer; +import v.DoomGraphicSystem; + +/** + * This class helps to choose proper components for bit depth + * selected in config or through use of command line arguments + */ +public enum BppMode { + Indexed(5, BufferedRenderer::new, BppMode::SceneGen_8, Transparency.OPAQUE), + HiColor(5, BufferedRenderer16::new, BppMode::SceneGen_16, Transparency.OPAQUE), + TrueColor(8, BufferedRenderer32::new, BppMode::SceneGen_32, Transparency.OPAQUE), + AlphaTrueColor(8, BufferedRenderer32::new, BppMode::SceneGen_32, Transparency.TRANSLUCENT); + + public final int transparency; + public final int lightBits; + final RenderGen renderGen; + final ScenerGen scenerGen; + + private BppMode(int lightBits, RenderGen renderGen, ScenerGen scenerGen, int transparency) { + this.lightBits = lightBits; + this.renderGen = renderGen; + this.scenerGen = scenerGen; + this.transparency = transparency; + } + + @SuppressWarnings("unchecked") + public DoomGraphicSystem graphics(RendererFactory.WithWadLoader rf) { + return ((RenderGen) renderGen).apply(rf); + } + + @SuppressWarnings("unchecked") + public SceneRenderer sceneRenderer(DoomMain DOOM) { + return ((ScenerGen) scenerGen).apply(DOOM); + } + + public static BppMode chooseBppMode(CVarManager CVM) { + if (CVM.bool(CommandVariable.TRUECOLOR)) { + return TrueColor; + } else if (CVM.bool(CommandVariable.HICOLOR)) { + return HiColor; + } else if (CVM.bool(CommandVariable.INDEXED)) { + return Indexed; + } else if (CVM.bool(CommandVariable.ALPHATRUECOLOR)) { + return AlphaTrueColor; + } else { + return Engine.getConfig().getValue(Settings.color_depth, BppMode.class); + } + } + + @SuppressWarnings("unchecked") + private static SceneRenderer SceneGen_8(DoomMain DOOM) { + return (SceneRenderer) SceneRendererMode.getMode().indexedGen.apply((DoomMain) DOOM); + } + + @SuppressWarnings("unchecked") + private static SceneRenderer SceneGen_16(DoomMain DOOM) { + return (SceneRenderer) SceneRendererMode.getMode().hicolorGen.apply((DoomMain) DOOM); + } + + @SuppressWarnings("unchecked") + private static SceneRenderer SceneGen_32(DoomMain DOOM) { + return (SceneRenderer) SceneRendererMode.getMode().truecolorGen.apply((DoomMain) DOOM); + } + + interface ScenerGen extends Function, SceneRenderer> { + } + + interface RenderGen extends Function, SoftwareGraphicsSystem> { + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/BufferedRenderer.java b/doom/src/v/renderers/BufferedRenderer.java new file mode 100644 index 0000000..440417f --- /dev/null +++ b/doom/src/v/renderers/BufferedRenderer.java @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +class BufferedRenderer extends SoftwareIndexedVideoRenderer { + + private final WritableRaster[] rasters = new WritableRaster[SCREENS_COUNT]; + + /** + * This actually creates a raster with a fixed underlying array, but NOT the images themselves. So it's possible to + * have "imageless" rasters (unless you specifically request to make them visible, of course). + */ + BufferedRenderer(RendererFactory.WithWadLoader rf) { + super(rf); + for (DoomScreen s : DoomScreen.values()) { + final int index = s.ordinal(); + // Only create non-visible data, pegged to the raster. Create visible images only on-demand. + final DataBufferByte db = (DataBufferByte) newBuffer(s); + // should be fully compatible with IndexColorModels from SoftwareIndexedVideoRenderer + rasters[index] = Raster.createInterleavedRaster(db, width, height, width, 1, new int[]{0}, new Point(0, 0)); + } + // Thou shalt not best nullt!!! Sets currentscreen + forcePalette(); + } + + /** + * Clear the screenbuffer so when the whole screen will be recreated palettes will too + * These screens represent a complete range of palettes for a specific gamma and specific screen + */ + @Override + public final void forcePalette() { + this.currentscreen = new BufferedImage(cmaps[usegamma][usepalette], rasters[DoomScreen.FG.ordinal()], true, null); + } +} + +//$Log: BufferedRenderer.java,v $ +//Revision 1.18 2012/09/24 17:16:23 velktron +//Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +//Revision 1.17.2.3 2012/09/24 16:56:06 velktron +//New hierarchy, less code repetition. +// \ No newline at end of file diff --git a/doom/src/v/renderers/BufferedRenderer16.java b/doom/src/v/renderers/BufferedRenderer16.java new file mode 100644 index 0000000..d8fb8bd --- /dev/null +++ b/doom/src/v/renderers/BufferedRenderer16.java @@ -0,0 +1,219 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferUShort; +import java.awt.image.VolatileImage; +import java.util.concurrent.BrokenBarrierException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import v.tables.BlurryTable; +import v.tables.ColorTint; +import static v.tables.ColorTint.GREY_TINTS; +import static v.tables.ColorTint.NORMAL_TINTS; + +/** + * Redesigned to follow as closely as possible its 32-bit complement + * + * It ulitizes now the same parallelization as 32-bit TrueColor renderer, + * becasue it allows palettes and gammas to be applied properly on post-process. + * The separate LUT's are generated for this renderer + * + * Most likely, this renderer will be the least performant. + * - Good Sign 2017/04/12 + */ +class BufferedRenderer16 extends SoftwareParallelVideoRenderer { + + private static final Logger LOGGER = Loggers.getLogger(BufferedRenderer16.class.getName()); + + protected final short[] raster; + + // VolatileImage speeds up delivery to VRAM - it is 30-40 fps faster then directly rendering BufferedImage + protected VolatileImage screen; + + // indicated whether machine display in the same mode as this renderer + protected final boolean compatible = checkConfigurationHicolor(); + protected final BlurryTable blurryTable; + + /** + * This implementation will "tie" a bufferedimage to the underlying byte raster. + * + * NOTE: this relies on the ability to "tap" into a BufferedImage's backing array, in order to have fast writes + * without setpixel/getpixel. If that is not possible, then we'll need to use a special renderer. + */ + BufferedRenderer16(RendererFactory.WithWadLoader rf) { + super(rf, short[].class); + /** + * There is only sense to create and use VolatileImage if it can use native acceleration + * which is impossible if we rendering into alien color space or bit depth + */ + if (compatible) { + // if we lucky to have 16-bit accelerated screen + screen = GRAPHICS_CONF.createCompatibleVolatileImage(width, height); + currentscreen = GRAPHICS_CONF.createCompatibleImage(width, height); + } else { + currentscreen = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_555_RGB); + } + + // extract raster from the created image + currentscreen.setAccelerationPriority(1.0f); + raster = ((DataBufferUShort) ((BufferedImage) currentscreen).getRaster().getDataBuffer()).getData(); + + blurryTable = new BlurryTable(liteColorMaps); + + /** + * Create postprocess worker threads + * 320 is dividable by 16, so any scale of it would + * TODO: support for custom resolutions? + */ + final int len = raster.length, chunk = len / PARALLELISM; + for (int i = 0; i < PARALLELISM; i++) { + paletteThreads[i] = new ShortPaletteThread(i * chunk, (i + 1) * chunk); + } + } + + /** + * This method is accessed by AWTDoom to render the screen + * As we use VolatileImage that can lose its contents, it must have special care. + * doWriteScreen is called in the moment, when the VolatileImage is ready and + * we can copy to it and post-process + * + * If we use incompatible display, just draw our existing BufferedImage - it would be faster + */ + @Override + public Image getScreenImage() { + doWriteScreen(); + if (!compatible) { + return currentscreen; + } else { + do { + if (screen.validate(GRAPHICS_CONF) == VolatileImage.IMAGE_INCOMPATIBLE) { + screen.flush(); + // old vImg doesn't work with new GraphicsConfig; re-create it + screen = GRAPHICS_CONF.createCompatibleVolatileImage(width, height); + } + + final Graphics2D g = screen.createGraphics(); + g.drawImage(currentscreen, 0, 0, null); + g.dispose(); + } while (screen.contentsLost()); + } + return screen; + } + + @Override + void doWriteScreen() { + for (int i = 0; i < PARALLELISM; i++) { + executor.execute(paletteThreads[i]); + } + try { + updateBarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, e, null); + } + } + + @Override + public int getBaseColor(byte color) { + return palette[color & 0xFF]; + } + + @Override + public BlurryTable getBlurryTable() { + return blurryTable; + } + + /** + * Looks monstrous. Works swiss. + * - Good Sign 2017/04/12 + */ + private class ShortPaletteThread implements Runnable { + + private final short[] FG; + private final int start; + private final int stop; + + ShortPaletteThread(int start, int stop) { + this.start = start; + this.stop = stop; + this.FG = screens.get(DoomScreen.FG); + } + + /** + * For BFG-9000 look at BufferedRenderer32.IntPaletteThread + * But there is BFG-2704 + */ + @Override + public void run() { + final ColorTint t = (GRAYPAL_SET ? GREY_TINTS : NORMAL_TINTS).get(usepalette); + final byte[] LUT_R = t.LUT_r5[usegamma]; + final byte[] LUT_G = t.LUT_g5[usegamma]; + final byte[] LUT_B = t.LUT_b5[usegamma]; + for (int i = start; i < stop;) { + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + raster[i] = (short) (((LUT_R[(FG[i] >> 10) & 0x1F] & 0x1F) << 10) | ((LUT_G[(FG[i] >> 5) & 0x1F] & 0x1F) << 5) | (LUT_B[FG[i++] & 0x1F] & 0x1F)); + } + try { + updateBarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.WARNING, e, null); + } + } + } +} + +// +// $Log: BufferedRenderer16.java,v $ +// Revision 1.4 2012/11/06 16:07:00 velktron +// Corrected palette & color generation. +// +// Revision 1.3 2012/09/24 17:16:23 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.2.2.5 2012/09/24 16:56:06 velktron +// New hierarchy, less code repetition. +// +// Revision 1.2.2.4 2011/11/29 12:45:29 velktron +// Restored palette and gamma effects. They do work, but display hysteresis. +// +// Revision 1.2.2.3 2011/11/27 18:19:58 velktron +// Added cache clearing to keep memory down. +// +// Revision 1.2.2.2 2011/11/18 21:36:55 velktron +// More 16-bit goodness. +// +// Revision 1.2.2.1 2011/11/14 00:27:11 velktron +// A barely functional HiColor branch. Most stuff broken. DO NOT USE +// \ No newline at end of file diff --git a/doom/src/v/renderers/BufferedRenderer32.java b/doom/src/v/renderers/BufferedRenderer32.java new file mode 100644 index 0000000..59b561f --- /dev/null +++ b/doom/src/v/renderers/BufferedRenderer32.java @@ -0,0 +1,223 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.awt.Graphics2D; +import java.awt.Image; +import static java.awt.Transparency.TRANSLUCENT; +import java.awt.image.BufferedImage; +import static java.awt.image.BufferedImage.TYPE_INT_ARGB; +import static java.awt.image.BufferedImage.TYPE_INT_RGB; +import java.awt.image.DataBufferInt; +import java.awt.image.VolatileImage; +import java.util.concurrent.BrokenBarrierException; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import v.tables.BlurryTable; +import v.tables.ColorTint; +import static v.tables.ColorTint.GREY_TINTS; +import static v.tables.ColorTint.NORMAL_TINTS; + +/** + * Merged with ParallelTruecolorRenderer as it fixed both bugs of parallel and single-core versions + * Now only parallel BufferedRenderer32 available in TrueColor mode because + * single-core post-processing in software is too slow, and is the only way to apply tints and gamma properly + * Parallelization of post-processing is so effective, that on my 4-core i7 it gives me at least equal FPS with + * indexed renderer in no-JIT configuration of Java, and with JIT compiler it gives me much more FPS than indexed. + * So, you will probably want this renderer if you have at least Core2Duo processor + * - Good Sign 2017/04/12 + */ +class BufferedRenderer32 extends SoftwareParallelVideoRenderer { + + private static final Logger LOGGER = Loggers.getLogger(BufferedRenderer32.class.getName()); + + protected final int[] raster; + + // VolatileImage speeds up delivery to VRAM - it is 30-40 fps faster then directly rendering BufferedImage + protected VolatileImage screen; + + // indicated whether machine display in the same mode as this renderer + protected final boolean compatible = checkConfigurationTruecolor(); + protected final int transparency; + protected final BlurryTable blurryTable; + + /** + * This implementation will "tie" a BufferedImage to the underlying byte raster. + * + * NOTE: this relies on the ability to "tap" into a BufferedImage's backing array, in order to have fast writes + * without setPixel/getPixel. If that is not possible, then we'll need to use a special renderer. + */ + BufferedRenderer32(RendererFactory.WithWadLoader rf) { + super(rf, int[].class); + + /** + * Try to create as accelerated Images as possible - these would not lose + * more performance from attempt (in contrast to 16-bit ones) + */ + screen = GRAPHICS_CONF.createCompatibleVolatileImage(width, height); + transparency = rf.getBppMode().transparency; + + /** + * It is very probably that you have 32-bit display mode, so high chance of success, + * and if you have, for example, 24-bit mode, the TYPE_INT_RGB BufferedImage will + * still get accelerated + */ + currentscreen = compatible + ? GRAPHICS_CONF.createCompatibleImage(width, height, transparency) + : new BufferedImage(width, height, transparency == TRANSLUCENT ? TYPE_INT_ARGB : TYPE_INT_RGB); + currentscreen.setAccelerationPriority(1.0f); + + // extract raster from the created image + raster = ((DataBufferInt) ((BufferedImage) currentscreen).getRaster().getDataBuffer()).getData(); + + blurryTable = new BlurryTable(liteColorMaps); + + /** + * Create postprocess worker threads + * 320 is dividable by 16, so any scale of it would + * TODO: support for custom resolutions? + */ + final int len = raster.length, chunk = len / PARALLELISM; + for (int i = 0; i < PARALLELISM; i++) { + paletteThreads[i] = new IntPaletteThread(i * chunk, (i + 1) * chunk); + } + } + + /** + * This method is accessed by AWTDoom to render the screen + * As we use VolatileImage that can lose its contents, it must have special care. + * doWriteScreen is called in the moment, when the VolatileImage is ready and + * we can copy to it and post-process + */ + @Override + public Image getScreenImage() { + do { + if (screen.validate(GRAPHICS_CONF) == VolatileImage.IMAGE_INCOMPATIBLE) { + screen.flush(); + // old vImg doesn't work with new GraphicsConfig; re-create it + screen = GRAPHICS_CONF.createCompatibleVolatileImage(width, height); + } + doWriteScreen(); + } while (screen.contentsLost()); + return screen; + } + + @Override + void doWriteScreen() { + for (int i = 0; i < PARALLELISM; i++) { + executor.execute(paletteThreads[i]); + } + try { + updateBarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.SEVERE, e, null); + } + + final Graphics2D g = screen.createGraphics(); + g.drawImage(currentscreen, 0, 0, null); + g.dispose(); + } + + /** + * Returns pure color without tinting and gamma + */ + @Override + public int getBaseColor(byte color) { + return palette[color & 0xFF]; + } + + @Override + public BlurryTable getBlurryTable() { + return blurryTable; + } + + /** + * Looks monstrous. Works swiss. + * - Good Sign 2017/04/12 + */ + private class IntPaletteThread implements Runnable { + + private final int[] FG; + private final int start; + private final int stop; + + IntPaletteThread(int start, int stop) { + this.start = start; + this.stop = stop; + this.FG = screens.get(DoomScreen.FG); + } + + /** + * BFG-9000. Definitely not the pesky pistol in the Indexed renderer + */ + @Override + public void run() { + final ColorTint t = (GRAYPAL_SET ? GREY_TINTS : NORMAL_TINTS).get(usepalette); + final byte[] LUT_R = t.LUT_r8[usegamma]; + final byte[] LUT_G = t.LUT_g8[usegamma]; + final byte[] LUT_B = t.LUT_b8[usegamma]; + for (int i = start; i < stop;) { + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + raster[i] = (FG[i] & 0xFF000000) + ((LUT_R[(FG[i] >> 16) & 0xFF] & 0xFF) << 16) + ((LUT_G[(FG[i] >> 8) & 0xFF] & 0xFF) << 8) + (LUT_B[FG[i++] & 0xFF] & 0xFF); + } + try { + updateBarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + LOGGER.log(Level.WARNING, e, null); + } + } + } +} + +// +// $Log: BufferedRenderer32.java,v $ +// Revision 1.3 2012/11/06 16:07:00 velktron +// Corrected palette & color generation. +// +// Revision 1.2 2012/09/24 17:16:23 velktron +// Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +// Revision 1.1.2.1 2012/09/24 16:56:06 velktron +// New hierarchy, less code repetition. +// +// Revision 1.2.2.4 2011/11/29 12:45:29 velktron +// Restored palette and gamma effects. They do work, but display hysteresis. +// +// Revision 1.2.2.3 2011/11/27 18:19:58 velktron +// Added cache clearing to keep memory down. +// +// Revision 1.2.2.2 2011/11/18 21:36:55 velktron +// More 16-bit goodness. +// +// Revision 1.2.2.1 2011/11/14 00:27:11 velktron +// A barely functional HiColor branch. Most stuff broken. DO NOT USE +// \ No newline at end of file diff --git a/doom/src/v/renderers/DoomScreen.java b/doom/src/v/renderers/DoomScreen.java new file mode 100644 index 0000000..a529eaf --- /dev/null +++ b/doom/src/v/renderers/DoomScreen.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.Map; + +/** + * + * @author Good Sign + */ +public enum DoomScreen { + FG, BG, WS, WE, SB; + + @SuppressWarnings("unchecked") + static Map mapScreensToBuffers(Class bufferType, int bufferLen) { + return Arrays.stream(values()) + .collect(() -> new EnumMap<>(DoomScreen.class), + (map, screen) -> map.put(screen, (V) Array.newInstance(bufferType.getComponentType(), bufferLen)), + EnumMap::putAll); + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/RendererFactory.java b/doom/src/v/renderers/RendererFactory.java new file mode 100644 index 0000000..9fbd77a --- /dev/null +++ b/doom/src/v/renderers/RendererFactory.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.util.Objects; +import v.DoomGraphicSystem; +import v.scale.VideoScale; +import w.IWadLoader; + +/** + * Renderer choice that depends on selected (or provided through command line) BppMode + * It also ensures you create it in right order and with right components. + * + * And see - no package interface shared to public + * @author Good Sign + */ +public class RendererFactory { + + private RendererFactory() { + } + + public static Clear newBuilder() { + return new Builder<>(); + } + + private static class Builder + implements Clear, WithVideoScale, WithBppMode, WithWadLoader { + + private IWadLoader wadLoader; + private VideoScale videoScale; + private BppMode bppMode; + + @Override + public WithVideoScale setVideoScale(VideoScale videoScale) { + this.videoScale = Objects.requireNonNull(videoScale); + return this; + } + + @Override + public WithBppMode setBppMode(BppMode bppMode) { + this.bppMode = Objects.requireNonNull(bppMode); + return this; + } + + @Override + public WithWadLoader setWadLoader(IWadLoader wadLoader) { + this.wadLoader = Objects.requireNonNull(wadLoader); + return this; + } + + @Override + public DoomGraphicSystem build() { + return bppMode.graphics(this); + } + + @Override + public BppMode getBppMode() { + return bppMode; + } + + @Override + public VideoScale getVideoScale() { + return videoScale; + } + + @Override + public IWadLoader getWadLoader() { + return wadLoader; + } + } + + public interface Clear { + + WithVideoScale setVideoScale(VideoScale videoScale); + } + + public interface WithVideoScale { + + WithBppMode setBppMode(BppMode bppMode); + + VideoScale getVideoScale(); + } + + public interface WithBppMode { + + WithWadLoader setWadLoader(IWadLoader wadLoader); + + VideoScale getVideoScale(); + + BppMode getBppMode(); + } + + public interface WithWadLoader { + + DoomGraphicSystem build(); + + VideoScale getVideoScale(); + + BppMode getBppMode(); + + IWadLoader getWadLoader(); + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/SceneRendererMode.java b/doom/src/v/renderers/SceneRendererMode.java new file mode 100644 index 0000000..295f459 --- /dev/null +++ b/doom/src/v/renderers/SceneRendererMode.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import doom.CommandVariable; +import doom.DoomMain; +import java.util.function.Function; +import m.Settings; +import mochadoom.Engine; +import rr.SceneRenderer; +import rr.UnifiedRenderer; +import rr.parallel.ParallelRenderer; +import rr.parallel.ParallelRenderer2; + +/** + * This class helps to choose between scene renderers + */ +public enum SceneRendererMode { + Serial(UnifiedRenderer.Indexed::new, UnifiedRenderer.HiColor::new, UnifiedRenderer.TrueColor::new), + Parallel(SceneRendererMode::Parallel_8, SceneRendererMode::Parallel_16, SceneRendererMode::Parallel_32), + Parallel2(SceneRendererMode::Parallel2_8, SceneRendererMode::Parallel2_16, SceneRendererMode::Parallel2_32); + + private static final boolean cVarSerial = Engine.getCVM().bool(CommandVariable.SERIALRENDERER); + private static final boolean cVarParallel = Engine.getCVM().present(CommandVariable.PARALLELRENDERER); + private static final boolean cVarParallel2 = Engine.getCVM().present(CommandVariable.PARALLELRENDERER2); + private static final int[] threads = cVarSerial ? null : cVarParallel + ? parseSwitchConfig(CommandVariable.PARALLELRENDERER) + : cVarParallel2 + ? parseSwitchConfig(CommandVariable.PARALLELRENDERER2) + : new int[]{2, 2, 2}; + + final SG indexedGen; + final SG hicolorGen; + final SG truecolorGen; + + private SceneRendererMode(SG indexed, SG hi, SG truecolor) { + this.indexedGen = indexed; + this.hicolorGen = hi; + this.truecolorGen = truecolor; + } + + static int[] parseSwitchConfig(CommandVariable sw) { + // Try parsing walls, or default to 1 + final int walls = Engine.getCVM().get(sw, Integer.class, 0).orElse(1); + // Try parsing floors. If wall succeeded, but floors not, it will default to 1. + final int floors = Engine.getCVM().get(sw, Integer.class, 1).orElse(1); + // In the worst case, we will use the defaults. + final int masked = Engine.getCVM().get(sw, Integer.class, 2).orElse(2); + return new int[]{walls, floors, masked}; + } + + static SceneRendererMode getMode() { + if (cVarSerial) { + /** + * Serial renderer in command line argument will override everything else + */ + return Serial; + } else if (cVarParallel) { + /** + * The second-top priority switch is parallelrenderer (not 2) command line argument + */ + return Parallel; + } else if (cVarParallel2) { + /** + * If we have parallelrenderer2 on command line, it will still override config setting + */ + return Parallel2; + } + + /** + * We dont have overrides on command line - get mode from default.cfg (or whatever) + * Set default parallelism config in this case + * TODO: make able to choose in config, but on ONE line along with scene_renderer_mode, should be tricky! + */ + return Engine.getConfig().getValue(Settings.scene_renderer_mode, SceneRendererMode.class); + } + + private static SceneRenderer Parallel_8(DoomMain DOOM) { + return new ParallelRenderer.Indexed(DOOM, threads[0], threads[1], threads[2]); + } + + private static SceneRenderer Parallel_16(DoomMain DOOM) { + return new ParallelRenderer.HiColor(DOOM, threads[0], threads[1], threads[2]); + } + + private static SceneRenderer Parallel_32(DoomMain DOOM) { + return new ParallelRenderer.TrueColor(DOOM, threads[0], threads[1], threads[2]); + } + + private static SceneRenderer Parallel2_8(DoomMain DOOM) { + return new ParallelRenderer2.Indexed(DOOM, threads[0], threads[1], threads[2]); + } + + private static SceneRenderer Parallel2_16(DoomMain DOOM) { + return new ParallelRenderer2.HiColor(DOOM, threads[0], threads[1], threads[2]); + } + + private static SceneRenderer Parallel2_32(DoomMain DOOM) { + return new ParallelRenderer2.TrueColor(DOOM, threads[0], threads[1], threads[2]); + } + + interface SG extends Function, SceneRenderer> { + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/SoftwareGraphicsSystem.java b/doom/src/v/renderers/SoftwareGraphicsSystem.java new file mode 100644 index 0000000..722efdb --- /dev/null +++ b/doom/src/v/renderers/SoftwareGraphicsSystem.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import doom.CommandVariable; +import f.Wiper; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; +import java.util.Map; +import m.IRandom; +import m.Settings; +import mochadoom.Engine; +import rr.patch_t; +import v.DoomGraphicSystem; +import v.graphics.Blocks; +import v.graphics.Horizontal; +import v.graphics.Lines; +import v.graphics.Palettes; +import v.graphics.Patches; +import v.graphics.Plotter; +import v.graphics.Rectangles; +import v.graphics.Relocation; +import v.graphics.Wipers; +import static v.renderers.DoomScreen.FG; +import static v.renderers.DoomScreen.WE; +import static v.renderers.DoomScreen.WS; +import static v.renderers.DoomScreen.mapScreensToBuffers; +import v.scale.VideoScale; +import v.tables.GammaTables; +import v.tables.Playpal; + +/** + * A package-protected hub, concentrating together public graphics APIs + * and support default methods from their interfaces + * + * Problems: we cannot change resolution on-fly because it will require re-creating buffers, rasters, etc + * TODO: decide what needs to be reset and implement resolution change methods (flushing buffers, expanding arrays, etc) + * (dont forget to run gc!) + * + * @author Good Sign + */ +abstract class SoftwareGraphicsSystem + implements DoomGraphicSystem, Rectangles, Blocks, Patches, Lines { + + /** + * Each screen is [SCREENWIDTH*SCREENHEIGHT]; This is what the various modules (menu, automap, renderer etc.) get to + * manipulate at the pixel level. To go beyond 8 bit displays, these must be extended + */ + protected final Map screens; + protected final VideoScale vs; + protected final Class bufferType; + + /** + * They are used in HiColor and TrueColor modes and are separated from tinting and gammas + * Colormaps are now part of the base software renderer. This allows some flexibility over manipulating them. + */ + protected final V[] liteColorMaps; + protected final V palette; + + /** + * Indexed renderer changes this property often when switching gammas and palettes + * For HiColor and TrueColor renderer it may change or not, depending on compatibility of + * graphics configuration: if VolatileImage is used, this changes as soon as it may invalidate + */ + protected Image currentscreen; + + /** + * Dynamic properties: + */ + protected int width; + protected int height; + protected int bufferLength; + protected int usegamma = 0; + protected int usepalette = 0; + + /** + * @param vs video scale info + * @param playpal palette + */ + SoftwareGraphicsSystem(RendererFactory.WithWadLoader rf, final Class bufferType) { + // Defaults + this.vs = rf.getVideoScale(); + this.width = vs.getScreenWidth(); + this.height = vs.getScreenHeight(); + this.bufferType = bufferType; + this.bufferLength = width * height; + this.screens = mapScreensToBuffers(bufferType, bufferLength); + this.palette = palette(rf); + this.liteColorMaps = colormap(rf); + } + + @SuppressWarnings("unchecked") + private V palette(RendererFactory.WithWadLoader rf) { + /*final byte[] */ + playpal + = Engine.getCVM().bool(CommandVariable.GREYPAL) + ? Playpal.greypal() + : Engine.getCVM().bool(CommandVariable.NOPLAYPAL) + ? Playpal.properPlaypal(null) + : rf.getWadLoader().LoadPlaypal(); + + /** + * In Indexed mode, read PLAYPAL lump can be used directly + */ + return bufferType == byte[].class + ? (V) playpal + /** + * In HiColor or TrueColor translate PLAYPAL to real colors + */ + : bufferType == short[].class + ? (V) paletteHiColor(playpal) + : (V) paletteTrueColor(playpal); + } + + private byte[] playpal; + + @SuppressWarnings("unchecked") + private V[] colormap(RendererFactory.WithWadLoader rf) { + final boolean colormapEnabled = !Engine.getCVM().bool(CommandVariable.NOCOLORMAP) + && Engine.getConfig().equals(Settings.enable_colormap_lump, Boolean.TRUE); + + return /** + * In Indexed mode, read COLORMAP lump can be used directly + */ + bufferType == byte[].class + ? colormapEnabled + ? (V[]) rf.getWadLoader().LoadColormap() + : (V[]) BuildLightsI(paletteTrueColor(playpal)) + /** + * In HiColor or TrueColor generate colormaps with lights + */ + : bufferType == short[].class + ? colormapEnabled // HiColor, check for cfg setting and command line argument -nocolormap + ? (V[]) BuildLights15(paletteTrueColor(playpal), rf.getWadLoader().LoadColormap()) + : (V[]) BuildLights15(paletteTrueColor(playpal)) + : colormapEnabled // TrueColor, check for cfg setting and command line argument -nocolormap + ? (V[]) BuildLights24((int[]) palette, rf.getWadLoader().LoadColormap()) + : (V[]) BuildLights24((int[]) palette); + } + + /** + * Getters + */ + @Override + public final int getUsegamma() { + return usegamma; + } + + @Override + public final int getPalette() { + return usepalette; + } + + @Override + public final int getScreenHeight() { + return this.height; + } + + @Override + public final int getScreenWidth() { + return this.width; + } + + @Override + public int getScalingX() { + return vs.getScalingX(); + } + + @Override + public int getScalingY() { + return vs.getScalingY(); + } + + @Override + public final V getScreen(DoomScreen screenType) { + return screens.get(screenType); + } + + @Override + public Image getScreenImage() { + return currentscreen; + /* may be null */ } + + /** + * API route delegating + */ + @Override + public void screenCopy(V srcScreen, V dstScreen, Relocation relocation) { + Rectangles.super.screenCopy(srcScreen, dstScreen, relocation); + } + + @Override + public void screenCopy(DoomScreen srcScreen, DoomScreen dstScreen) { + Rectangles.super.screenCopy(srcScreen, dstScreen); + } + + @Override + public int getBaseColor(int color) { + return Rectangles.super.getBaseColor(color); + } + + @Override + public int point(int x, int y) { + return Rectangles.super.point(x, y); + } + + @Override + public int point(int x, int y, int width) { + return Rectangles.super.point(x, y, width); + } + + @Override + public void drawLine(Plotter plotter, int x1, int x2) { + Lines.super.drawLine(plotter, x1, x2); + } + + @Override + public void DrawPatch(DoomScreen screen, patch_t patch, int x, int y, int... flags) { + Patches.super.DrawPatch(screen, patch, x, y, flags); + } + + @Override + public void DrawPatchCentered(DoomScreen screen, patch_t patch, int y, int... flags) { + Patches.super.DrawPatchCentered(screen, patch, y, flags); + } + + @Override + public void DrawPatchCenteredScaled(DoomScreen screen, patch_t patch, VideoScale vs, int y, int... flags) { + Patches.super.DrawPatchCenteredScaled(screen, patch, vs, y, flags); + } + + @Override + public void DrawPatchScaled(DoomScreen screen, patch_t patch, VideoScale vs, int x, int y, int... flags) { + Patches.super.DrawPatchScaled(screen, patch, vs, x, y, flags); + } + + @Override + public void DrawPatchColScaled(DoomScreen screen, patch_t patch, VideoScale vs, int x, int col) { + Patches.super.DrawPatchColScaled(screen, patch, vs, x, col); + } + + @Override + public void CopyRect(DoomScreen srcScreenType, Rectangle rectangle, DoomScreen dstScreenType) { + Rectangles.super.CopyRect(srcScreenType, rectangle, dstScreenType); + } + + @Override + public void CopyRect(DoomScreen srcScreenType, Rectangle rectangle, DoomScreen dstScreenType, int dstPoint) { + Rectangles.super.CopyRect(srcScreenType, rectangle, dstScreenType, dstPoint); + } + + @Override + public void FillRect(DoomScreen screenType, Rectangle rectangle, V patternSrc, Horizontal pattern) { + Rectangles.super.FillRect(screenType, rectangle, patternSrc, pattern); + } + + @Override + public void FillRect(DoomScreen screenType, Rectangle rectangle, V patternSrc, int point) { + Rectangles.super.FillRect(screenType, rectangle, patternSrc, point); + } + + @Override + public void FillRect(DoomScreen screenType, Rectangle rectangle, int color) { + Rectangles.super.FillRect(screenType, rectangle, color); + } + + @Override + public void FillRect(DoomScreen screenType, Rectangle rectangle, byte color) { + Rectangles.super.FillRect(screenType, rectangle, color); + } + + @Override + public V ScaleBlock(V block, VideoScale vs, int width, int height) { + return Rectangles.super.ScaleBlock(block, vs, width, height); + } + + @Override + public void TileScreen(DoomScreen dstScreen, V block, Rectangle blockArea) { + Rectangles.super.TileScreen(dstScreen, block, blockArea); + } + + @Override + public void TileScreenArea(DoomScreen dstScreen, Rectangle screenArea, V block, Rectangle blockArea) { + Rectangles.super.TileScreenArea(dstScreen, screenArea, block, blockArea); + } + + @Override + public void DrawBlock(DoomScreen dstScreen, V block, Rectangle sourceArea, int destinationPoint) { + Rectangles.super.DrawBlock(dstScreen, block, sourceArea, destinationPoint); + } + + @Override + public Plotter createPlotter(DoomScreen screen) { + return DoomGraphicSystem.super.createPlotter(screen); + } + + /** + * I_SetPalette + * + * Any bit-depth specific palette manipulation is performed by the VideoRenderer. It can range from simple + * (paintjob) to complex (multiple BufferedImages with locked data bits...) ugh! + * + * In order to change palette properly, we must invalidate + * the colormap cache if any, otherwise older colormaps will persist. + * The screen must be fully updated then + * + * @param palette index (normally between 0-14). + */ + @Override + public void setPalette(int palette) { + this.usepalette = palette % Palettes.NUM_PALETTES; + this.forcePalette(); + } + + @Override + public void setUsegamma(int gamma) { + this.usegamma = gamma % GammaTables.LUT.length; + + /** + * Because of switching gamma stops powerup palette except for invlunerablity + * Settings.fixgammapalette handles the fix + */ + if (Engine.getConfig().equals(Settings.fix_gamma_palette, Boolean.FALSE)) { + this.usepalette = 0; + } + + this.forcePalette(); + } + + @Override + public V[] getColorMap() { + return this.liteColorMaps; + } + + public DataBuffer newBuffer(DoomScreen screen) { + final V buffer = screens.get(screen); + if (buffer.getClass() == int[].class) { + return new DataBufferInt((int[]) buffer, ((int[]) buffer).length); + } else if (buffer.getClass() == short[].class) { + return new DataBufferUShort((short[]) buffer, ((short[]) buffer).length); + } else if (buffer.getClass() == byte[].class) { + return new DataBufferByte((byte[]) buffer, ((byte[]) buffer).length); + } + + throw new UnsupportedOperationException(String.format("SoftwareVideoRenderer does not support %s buffers", buffer.getClass())); + } + + @Override + public Wiper createWiper(IRandom random) { + return Wipers.createWiper(random, this, WS, WE, FG); + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/SoftwareIndexedVideoRenderer.java b/doom/src/v/renderers/SoftwareIndexedVideoRenderer.java new file mode 100644 index 0000000..d9501eb --- /dev/null +++ b/doom/src/v/renderers/SoftwareIndexedVideoRenderer.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import java.awt.image.IndexColorModel; +import m.MenuMisc; +import v.graphics.Palettes; +import v.tables.BlurryTable; +import v.tables.GammaTables; + +/** + * @author Good Sign + * @author velktron + */ +abstract class SoftwareIndexedVideoRenderer extends SoftwareGraphicsSystem { + + /** + * Indexed renderers keep separate color models for each colormap (intended as gamma levels) and palette levels + */ + protected final IndexColorModel[][] cmaps = new IndexColorModel[GammaTables.LUT.length][Palettes.NUM_PALETTES]; + protected final BlurryTable blurryTable; + + SoftwareIndexedVideoRenderer(RendererFactory.WithWadLoader rf) { + super(rf, byte[].class); + + /** + * create gamma levels + * Now we can reuse existing array of cmaps, not allocating more memory + * each time we change gamma or pick item + */ + cmapIndexed(cmaps, palette); + blurryTable = new BlurryTable(liteColorMaps); + } + + @Override + public int getBaseColor(byte color) { + return color; + } + + @Override + public byte[] convertPalettedBlock(byte... src) { + return src; + } + + @Override + public BlurryTable getBlurryTable() { + return blurryTable; + } + + @Override + public boolean writeScreenShot(String name, DoomScreen screen) { + // munge planar buffer to linear + //DOOM.videoInterface.ReadScreen(screens[screen.ordinal()]); + MenuMisc.WritePNGfile(name, screens.get(screen), width, height, cmaps[usegamma][usepalette]); + return true; + } +} \ No newline at end of file diff --git a/doom/src/v/renderers/SoftwareParallelVideoRenderer.java b/doom/src/v/renderers/SoftwareParallelVideoRenderer.java new file mode 100644 index 0000000..1ed6171 --- /dev/null +++ b/doom/src/v/renderers/SoftwareParallelVideoRenderer.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.renderers; + +import doom.CommandVariable; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsEnvironment; +import java.awt.image.ColorModel; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import m.MenuMisc; +import m.Settings; +import mochadoom.Engine; + +/** + * Base for HiColor and TrueColor parallel renderers + * + * @author Good Sign + * @author velktron + */ +abstract class SoftwareParallelVideoRenderer extends SoftwareGraphicsSystem { + + // How many threads it will use, but default it uses all avalable cores + private static final int[] EMPTY_INT_PALETTED_BLOCK = new int[0]; + private static final short[] EMPTY_SHORT_PALETTED_BLOCK = new short[0]; + protected static final int PARALLELISM = Engine.getConfig().getValue(Settings.parallelism_realcolor_tint, Integer.class); + protected static final GraphicsConfiguration GRAPHICS_CONF = GraphicsEnvironment.getLocalGraphicsEnvironment() + .getDefaultScreenDevice().getDefaultConfiguration(); + + protected final boolean GRAYPAL_SET = Engine.getCVM().bool(CommandVariable.GREYPAL); + + /** + * It will render much faster on machines with display already in HiColor mode + * Maybe even some acceleration will be possible + */ + static boolean checkConfigurationHicolor() { + final ColorModel cm = GRAPHICS_CONF.getColorModel(); + final int cps = cm.getNumComponents(); + return cps == 3 && cm.getComponentSize(0) == 5 && cm.getComponentSize(1) == 5 && cm.getComponentSize(2) == 5; + } + + /** + * It will render much faster on machines with display already in TrueColor mode + * Maybe even some acceleration will be possible + */ + static boolean checkConfigurationTruecolor() { + final ColorModel cm = GRAPHICS_CONF.getColorModel(); + final int cps = cm.getNumComponents(); + return cps == 3 && cm.getComponentSize(0) == 8 && cm.getComponentSize(1) == 8 && cm.getComponentSize(2) == 8; + } + + /** + * We do not need to clear caches anymore - pallettes are applied on post-process + * - Good Sign 2017/04/12 + * + * MEGA HACK FOR SUPER-8BIT MODES + */ + protected final HashMap colcache = new HashMap<>(); + + // Threads stuff + protected final Runnable[] paletteThreads = new Runnable[PARALLELISM]; + protected final Executor executor = Executors.newFixedThreadPool(PARALLELISM); + protected final CyclicBarrier updateBarrier = new CyclicBarrier(PARALLELISM + 1); + + SoftwareParallelVideoRenderer(RendererFactory.WithWadLoader rf, Class bufferType) { + super(rf, bufferType); + } + + abstract void doWriteScreen(); + + @Override + public boolean writeScreenShot(String name, DoomScreen screen) { + // munge planar buffer to linear + //DOOM.videoInterface.ReadScreen(screens[screen.ordinal()]); + V screenBuffer = screens.get(screen); + if (screenBuffer.getClass() == short[].class) { + MenuMisc.WritePNGfile(name, (short[]) screenBuffer, width, height); + } else { + MenuMisc.WritePNGfile(name, (int[]) screenBuffer, width, height); + } + return true; + } + + /** + * Used to decode textures, patches, etc... It converts to the proper palette, + * but does not apply tinting or gamma - yet + */ + @Override + @SuppressWarnings(value = "unchecked") + public V convertPalettedBlock(byte... data) { + final boolean isShort = bufferType == short[].class; + /** + * We certainly do not need to cache neither single color value, nor empty data + * - Good Sign 2017/04/09 + */ + if (data.length > 1) { + if (isShort) { + return colcache.computeIfAbsent(Arrays.hashCode(data), (h) -> { + //System.out.printf("Generated cache for %d\n",data.hashCode()); + short[] stuff = new short[data.length]; + for (int i = 0; i < data.length; i++) { + stuff[i] = (short) getBaseColor(data[i]); + } + return (V) stuff; + }); + } else { + return colcache.computeIfAbsent(Arrays.hashCode(data), (h) -> { + //System.out.printf("Generated cache for %d\n",data.hashCode()); + int[] stuff = new int[data.length]; + for (int i = 0; i < data.length; i++) { + stuff[i] = getBaseColor(data[i]); + } + return (V) stuff; + }); + } + } else if (data.length == 0) { + return (V) (isShort ? EMPTY_SHORT_PALETTED_BLOCK : EMPTY_INT_PALETTED_BLOCK); + } + return (V) (isShort ? new short[]{(short) getBaseColor(data[0])} : new int[]{getBaseColor(data[0])}); + } +} \ No newline at end of file diff --git a/doom/src/v/scale/VideoScale.java b/doom/src/v/scale/VideoScale.java new file mode 100644 index 0000000..c5bcec7 --- /dev/null +++ b/doom/src/v/scale/VideoScale.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.scale; + +/** + * Interface for an object that conveys screen resolution/scaling information, meant to replace the static declarations + * in Defines. + * + * @author velktron + * + */ +public interface VideoScale { + + //It is educational but futile to change this + //scaling e.g. to 2. Drawing of status bar, + //menues etc. is tied to the scale implied + //by the graphics. + public static double INV_ASPECT_RATIO = 0.625; // 0.75, ideally + + // + // For resize of screen, at start of game. + // It will not work dynamically, see visplanes. + // + public static final int BASE_WIDTH = 320; + public static final int BASE_HEIGHT = (int) (INV_ASPECT_RATIO * 320); // 200 + + int getScreenWidth(); + + int getScreenHeight(); + + int getScalingX(); + + int getScalingY(); + + /** + * Safest global scaling for fixed stuff like menus, titlepic etc + */ + int getSafeScaling(); + + /** + * Get floating point screen multiplier. Not recommended, as it causes visual glitches. Replace with safe scale, + * whenever possible + */ + float getScreenMul(); + + /** + * Future, should signal aware objects that they should refresh their resolution-dependent state, structures, + * variables etc. + * + * @return + */ + boolean changed(); +} \ No newline at end of file diff --git a/doom/src/v/scale/VideoScaleInfo.java b/doom/src/v/scale/VideoScaleInfo.java new file mode 100644 index 0000000..c4b1e81 --- /dev/null +++ b/doom/src/v/scale/VideoScaleInfo.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.scale; + +class VideoScaleInfo implements VideoScale { + + protected float scale; + protected int width; + protected int height; + protected int bestScaleX; + protected int bestScaleY; + protected int bestSafeScale; + + /** Scale is intended as a multiple of the base resolution, 320 x 200. + * If changing the ratio is also desired, then keep in mind that + * the base width is always considered fixed, while the base height + * is not. + * + * @param scale + */ + public VideoScaleInfo(float scale) { + this.scale = scale; + width = (int) (BASE_WIDTH * scale); + height = (int) (scale * BASE_WIDTH * INV_ASPECT_RATIO); + bestScaleX = (int) Math.floor((float) width / (float) BASE_WIDTH); + bestScaleY = (int) Math.floor((float) height / (float) BASE_HEIGHT); + bestSafeScale = Math.min(bestScaleX, bestScaleY); + } + + /** It's possible to specify other aspect ratios, too, keeping in mind + * that there are maximum width and height limits to take into account, + * and that scaling of graphics etc. will be rather problematic. Default + * ratio is 0.625, 0.75 will give a nice 4:3 ratio. + * + * TODO: pretty lame... + * + * @param scale + * @param ratio + */ + public VideoScaleInfo(float scale, float ratio) { + this.scale = scale; + width = (int) (BASE_WIDTH * scale); + height = (int) (scale * BASE_WIDTH * ratio); + bestScaleX = (int) Math.floor((float) width / (float) BASE_WIDTH); + bestScaleY = (int) Math.floor((float) height / (float) BASE_HEIGHT); + bestSafeScale = Math.min(bestScaleX, bestScaleY); + + } + + @Override + public int getScreenWidth() { + return width; + } + + @Override + public int getScreenHeight() { + return height; + } + + @Override + public int getScalingX() { + return bestScaleX; + } + + @Override + public int getScalingY() { + return bestScaleY; + } + + @Override + public int getSafeScaling() { + return bestSafeScale; + } + + @Override + public boolean changed() { + return false; + } + + @Override + public float getScreenMul() { + return scale; + } + +} \ No newline at end of file diff --git a/doom/src/v/scale/VisualSettings.java b/doom/src/v/scale/VisualSettings.java new file mode 100644 index 0000000..ff4d71a --- /dev/null +++ b/doom/src/v/scale/VisualSettings.java @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.scale; + +import doom.CVarManager; +import doom.CommandVariable; +import doom.ConfigManager; +import m.Settings; + +public class VisualSettings { + + /** Default video scale is "triple VANILLA: 3 x (320 x 200) */ + public static final VideoScale VANILLA = new VideoScaleInfo(1.0f); + public static final VideoScale DOUBLE_VANILLA = new VideoScaleInfo(2.0f); + public static final VideoScale TRIPLE_VANILLA = new VideoScaleInfo(3.0f); + public static final VideoScale DEFAULT_SCALE = TRIPLE_VANILLA; + + private static final int SCALE_MAX = 5; + + /** + * Parses the command line for resolution-specific commands, and creates + * an appropriate IVideoScale object. + * + * @param CVM the command line config manager + * @param CM the config manager + * @return the video scale info instance + */ + public final static VideoScale parse(CVarManager CVM, ConfigManager CM) { + + { // check multiply + // -multiply parameter defined from linux doom. + // It gets priority over all others, if present. + final int multiply = CVM.get(CommandVariable.MULTIPLY, Integer.class, 0).orElse(-1); + + // If -multiply was successful, trump any others. + // Implied to be a solid multiple of the VANILLA resolution. + if (multiply > 0 && multiply <= SCALE_MAX) { + return new VideoScaleInfo(multiply); + } + } // forget multiply + + // At least one of them is not a dud. + final int mulx, muly, mulf; + + // check width & height + final int width = CVM.get(CommandVariable.WIDTH, Integer.class, 0).orElse(-1); + final int height = CVM.get(CommandVariable.HEIGHT, Integer.class, 0).orElse(-1); + + // Nothing to do? + if (height == -1 && width == -1) { + // check multiply from mochadoom settings + int multiply = CM.getValue(Settings.multiply, Integer.class); + if (multiply > 0 && multiply <= SCALE_MAX) { + return new VideoScaleInfo(multiply); + } + return DEFAULT_SCALE; + } + + // Break them down to the nearest multiple of the base width or height. + mulx = Math.round((float) width / VideoScale.BASE_WIDTH); + muly = Math.round((float) height / VideoScale.BASE_HEIGHT); + + // Do not accept zero or sub-VANILLA resolutions + if (mulx > 0 || muly > 0) { + // Use the maximum multiplier. We don't support skewed + // aspect ratios yet. + mulf = Math.max(mulx, muly); + if (mulf >= 1 && mulf <= SCALE_MAX) { + return new VideoScaleInfo(mulf); + } + } + + // In all other cases... + return DEFAULT_SCALE; + } +} \ No newline at end of file diff --git a/doom/src/v/tables/BlurryTable.java b/doom/src/v/tables/BlurryTable.java new file mode 100644 index 0000000..5a7c903 --- /dev/null +++ b/doom/src/v/tables/BlurryTable.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +import java.util.TreeMap; +import m.Settings; +import mochadoom.Engine; +import v.graphics.Colors; +import static v.graphics.Lights.COLORMAP_BLURRY; +import static v.graphics.Lights.COLORMAP_FIXED; +import static v.graphics.Palettes.PAL_NUM_COLORS; + +/** + * Colormap-friendly vanilla-like BlurryMap for HiColor && TrueColor modes + * (though it shares plain "BLURRYMAP" for Indexed too) + * + * DOOM's colormap #6 was deciphered to be actually applying greyscale averaging filter. + * So, the vanilla effect is something like "n% darker and greyscale", where n% varies + * I think I've succeeded in replicating it for real color modes + * - Good Sign 2017/04/15 + * + * Now should be 100%, I've accounted for shift of number of generated lights for 24 bit color + * + * @author Good Sign + */ +public class BlurryTable implements FuzzMix, Colors { + + /** + * Indexed LUT, e.g. classic "BLURRYMAP" (unaffected) + */ + private final byte[] LUT_idx; + + private final byte[] LUT_r8; + private final byte[] LUT_g8; + private final byte[] LUT_b8; + private final byte[] LUT_a8; + + private final byte[] LUT_r5; + private final byte[] LUT_g5; + private final byte[] LUT_b5; + + private final boolean semiTranslucent = Engine.getConfig().equals(Settings.semi_translucent_fuzz, Boolean.TRUE); + private final boolean fuzzMix = Engine.getConfig().equals(Settings.fuzz_mix, Boolean.TRUE); + + /** + * Only support indexed "BLURRYMAP" with indexed colorMap + * @param colorMap + */ + public BlurryTable(byte[][] colorMap) { + this.LUT_b5 = null; + this.LUT_g5 = null; + this.LUT_r5 = null; + this.LUT_b8 = null; + this.LUT_g8 = null; + this.LUT_r8 = null; + this.LUT_a8 = null; + this.LUT_idx = colorMap[COLORMAP_BLURRY]; + } + + /** + * HiColor BlurryTable will only support int[][] colormap + * @param liteColorMaps + */ + public BlurryTable(short[][] liteColorMaps) { + this.LUT_b5 = new byte[32]; + this.LUT_g5 = new byte[32]; + this.LUT_r5 = new byte[32]; + this.LUT_b8 = null; + this.LUT_g8 = null; + this.LUT_r8 = null; + this.LUT_a8 = null; + this.LUT_idx = null; + + /** + * Prepare to sort colors - we will be using the ratio that is next close to apply for current color + */ + final TreeMap sortedRatios = new TreeMap<>(this::CompareColors555); + + for (int i = 0; i < PAL_NUM_COLORS; ++i) { + // first get "BLURRYMAP" color components + final int[] blurryColor = getRGB555(liteColorMaps[COLORMAP_BLURRY][i], new int[3]); + // then gen color components from unmodified (fixed) palette + final int[] fixedColor = getRGB555(liteColorMaps[COLORMAP_FIXED][i], new int[3]); + // make grayscale avegrage (or what you set in cfg) colors out of these components + final short avgColor = GreyscaleFilter.grey555(blurryColor[0], blurryColor[1], blurryColor[2]); + final short avgOrig = GreyscaleFilter.grey555(fixedColor[0], fixedColor[1], fixedColor[2]); + // get grayscale color components + final int[] blurryAvg = getRGB555(avgColor, new int[3]); + final int[] fixedAvg = getRGB555(avgOrig, new int[3]); + + // now, calculate the ratios + final float ratioR = fixedAvg[0] > 0 ? blurryAvg[0] / (float) fixedAvg[0] : 0.0f, + ratioG = fixedAvg[1] > 0 ? blurryAvg[1] / (float) fixedAvg[1] : 0.0f, + ratioB = fixedAvg[2] > 0 ? blurryAvg[2] / (float) fixedAvg[2] : 0.0f; + + // best ratio is weighted towards red and blue, but should not be multiplied or it will be too dark + final float bestRatio = GreyscaleFilter.component(ratioR, ratioG, ratioB);//ratioR * ratioR * ratioG * ratioB * ratioB; + + // associate normal color from colormaps avegrage with this ratio + sortedRatios.put(avgOrig, bestRatio); + } + + // now we have built our sorted maps, time to calculate color component mappings + for (int i = 0; i <= 0x1F; ++i) { + final short rgb555 = toRGB555(i, i, i); + // now the best part - approximation. we just pick the closest grayscale color ratio + final float ratio = sortedRatios.floorEntry(rgb555).getValue(); + LUT_r5[i] = LUT_g5[i] = LUT_b5[i] = (byte) ((int) (i * ratio) & 0x1F); + } + // all done + } + + /** + * TrueColor BlurryTable will only support int[][] colormap + * @param liteColorMaps + */ + public BlurryTable(int[][] liteColorMaps) { + this.LUT_b5 = null; + this.LUT_g5 = null; + this.LUT_r5 = null; + this.LUT_a8 = new byte[256]; + this.LUT_b8 = new byte[256]; + this.LUT_g8 = new byte[256]; + this.LUT_r8 = new byte[256]; + this.LUT_idx = null; + + /** + * Prepare to sort colors - we will be using the ratio that is next close to apply for current color + */ + final TreeMap sortedRatios = new TreeMap<>(this::CompareColors888); + + for (int i = 0; i < PAL_NUM_COLORS; ++i) { + // first get "BLURRYMAP" color components. 24 bit lighting is richer (256 vs 32) so we need to multiply + final int[] blurryColor = getRGB888(liteColorMaps[COLORMAP_BLURRY << 3][i], new int[3]); + // then gen color components from unmodified (fixed) palette + final int[] fixedColor = getRGB888(liteColorMaps[COLORMAP_FIXED][i], new int[3]); + // make grayscale avegrage (or what you set in cfg) colors out of these components + final int avgColor = GreyscaleFilter.grey888(blurryColor[0], blurryColor[1], blurryColor[2]); + final int avgOrig = GreyscaleFilter.grey888(fixedColor[0], fixedColor[1], fixedColor[2]); + // get grayscale color components + final int[] blurryAvg = getRGB888(avgColor, new int[3]); + final int[] fixedAvg = getRGB888(avgOrig, new int[3]); + + // now, calculate the ratios + final float ratioR = fixedAvg[0] > 0 ? blurryAvg[0] / (float) fixedAvg[0] : 0.0f, + ratioG = fixedAvg[1] > 0 ? blurryAvg[1] / (float) fixedAvg[1] : 0.0f, + ratioB = fixedAvg[2] > 0 ? blurryAvg[2] / (float) fixedAvg[2] : 0.0f; + + // weight ratio towards red and blue and multiply to make darker + final float bestRatio = GreyscaleFilter.component(ratioR, ratioG, ratioB);//ratioR * ratioR * ratioG * ratioB * ratioB; + + // associate normal color from colormaps avegrage with this ratio + sortedRatios.put(avgOrig, bestRatio); + } + + // now we have built our sorted maps, time to calculate color component mappings + for (int i = 0; i <= 0xFF; ++i) { + final int rgb = toRGB888(i, i, i); + // now the best part - approximation. we just pick the closest grayscale color ratio + final float ratio = sortedRatios.floorEntry(rgb).getValue(); + LUT_r8[i] = LUT_g8[i] = LUT_b8[i] = (byte) ((int) (i * ratio) & 0xFF); + // for alpha it is different: we use the same ratio as for greyscale color, but the base alpha is min 50% + LUT_a8[i] = (byte) (ratio * (Math.max(i, 0x7F) / (float) 0xFF) * 0xFF); + } + // all done + } + + /** + * For indexes + */ + public byte computePixel(byte pixel) { + return LUT_idx[pixel & 0xFF]; + } + + /** + * For HiColor pixels + */ + public short computePixel(short pixel) { + if (fuzzMix) { // if blurry feature enabled, everything else does not apply + return fuzzMixHi(pixel); + } + final int rgb[] = getRGB555(pixel, new int[4]); + return toRGB555(LUT_r5[rgb[0]], LUT_g5[rgb[1]], LUT_b5[rgb[2]]); + } + + /** + * In high detail mode in AlphaTrueColor color mode will compute special greyscale-to-ratio translucency + */ + public int computePixel(int pixel) { + if (fuzzMix) { // if blurry feature enabled, everything else does not apply + return fuzzMixTrue(pixel); + } + + if (!semiTranslucent) { + return computePixelFast(pixel); + } + final int argb[] = getARGB8888(pixel, new int[4]); + // the alpha from previous frame would stay until the pixel will not belong to FUZZ holder + argb[0] = Math.min(argb[0], GreyscaleFilter.component(argb[1], argb[2], argb[3])); + return toARGB8888(LUT_a8[argb[0]], LUT_r8[argb[1]], LUT_g8[argb[2]], LUT_b8[argb[3]]); + } + + /** + * For low detail mode, do not compute translucency + */ + public int computePixelFast(int pixel) { + if (fuzzMix) { // if blurry feature enabled, everything else does not apply + return fuzzMixTrueLow(pixel); + } + + final int rgb[] = getRGB888(pixel, new int[3]); + return 0xFF000000 + (toRGB888(LUT_r8[rgb[0]], LUT_g8[rgb[1]], LUT_b8[rgb[2]]) & 0xFFFFFF); + } +} \ No newline at end of file diff --git a/doom/src/v/tables/ColorTint.java b/doom/src/v/tables/ColorTint.java new file mode 100644 index 0000000..7ba29c8 --- /dev/null +++ b/doom/src/v/tables/ColorTint.java @@ -0,0 +1,149 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Default generated tints for berserk, radsuit, bonus pickup and so on. + * I think they may be invalid if the game uses custom COLORMAP, so we need an ability + * to regenerate them when loading such lump. + * Thus, it is an Enum... but only almost. + * + * Added new LUT's for HiColor and TrueColor renderers + * They are capable of tinting and gamma correcting full direct colors(not indexed) on the fly + * - Good Sign + */ +public class ColorTint { + + public final static ColorTint NORMAL = new ColorTint(0, 0, 0, .0f), + RED_11 = new ColorTint(255, 2, 3, 0.11f), + RED_22 = new ColorTint(255, 0, 0, 0.22f), + RED_33 = new ColorTint(255, 0, 0, 0.33f), + RED_44 = new ColorTint(255, 0, 0, 0.44f), + RED_55 = new ColorTint(255, 0, 0, 0.55f), + RED_66 = new ColorTint(255, 0, 0, 0.66f), + RED_77 = new ColorTint(255, 0, 0, 0.77f), + RED_88 = new ColorTint(255, 0, 0, 0.88f), + BERSERK_SLIGHT = new ColorTint(215, 185, 68, 0.12f), + BERSERK_SOMEWHAT = new ColorTint(215, 185, 68, 0.25f), + BERSERK_NOTICABLE = new ColorTint(215, 185, 68, 0.375f), + BERSERK_HEAVY = new ColorTint(215, 185, 68, 0.50f), + RADSUIT = new ColorTint(3, 253, 3, 0.125f), + GREY_NORMAL = new ColorTint(NORMAL.mid(), NORMAL.mid5(), NORMAL.purepart), + GREY_RED_11 = new ColorTint(RED_11.mid(), RED_11.mid5(), RED_11.purepart), + GREY_RED_22 = new ColorTint(RED_22.mid(), RED_22.mid5(), RED_22.purepart), + GREY_RED_33 = new ColorTint(RED_33.mid(), RED_33.mid5(), RED_33.purepart), + GREY_RED_44 = new ColorTint(RED_44.mid(), RED_44.mid5(), RED_44.purepart), + GREY_RED_55 = new ColorTint(RED_55.mid(), RED_55.mid5(), RED_55.purepart), + GREY_RED_66 = new ColorTint(RED_66.mid(), RED_66.mid5(), RED_66.purepart), + GREY_RED_77 = new ColorTint(RED_77.mid(), RED_77.mid5(), RED_77.purepart), + GREY_RED_88 = new ColorTint(RED_88.mid(), RED_88.mid5(), RED_88.purepart), + GREY_BERSERK_SLIGHT = new ColorTint(BERSERK_SLIGHT.mid(), BERSERK_SLIGHT.mid5(), BERSERK_SLIGHT.purepart), + GREY_BERSERK_SOMEWHAT = new ColorTint(BERSERK_SOMEWHAT.mid(), BERSERK_SOMEWHAT.mid5(), BERSERK_SOMEWHAT.purepart), + GREY_BERSERK_NOTICABLE = new ColorTint(BERSERK_NOTICABLE.mid(), BERSERK_NOTICABLE.mid5(), BERSERK_NOTICABLE.purepart), + GREY_BERSERK_HEAVY = new ColorTint(BERSERK_HEAVY.mid(), BERSERK_HEAVY.mid5(), BERSERK_HEAVY.purepart), + GREY_RADSUIT = new ColorTint(RADSUIT.mid(), RADSUIT.mid5(), RADSUIT.purepart); + + public static final List NORMAL_TINTS = Collections.unmodifiableList(Arrays.asList( + NORMAL, + RED_11, RED_22, RED_33, RED_44, RED_55, RED_66, RED_77, RED_88, + BERSERK_SLIGHT, BERSERK_SOMEWHAT, BERSERK_NOTICABLE, BERSERK_HEAVY, RADSUIT + )); + + public static final List GREY_TINTS = Collections.unmodifiableList(Arrays.asList( + GREY_NORMAL, + GREY_RED_11, GREY_RED_22, GREY_RED_33, GREY_RED_44, GREY_RED_55, GREY_RED_66, GREY_RED_77, GREY_RED_88, + GREY_BERSERK_SLIGHT, GREY_BERSERK_SOMEWHAT, GREY_BERSERK_NOTICABLE, GREY_BERSERK_HEAVY, GREY_RADSUIT + )); + + /*public static List generateTints(byte cmaps[][]) { + }*/ + ColorTint(int r, int g, int b, float tint) { + this(r * tint, (r >> 3) * tint, g * tint, (g >> 3) * tint, b * tint, (b >> 3) * tint, 1 - tint); + } + + ColorTint(float mid8, float mid5, float purepart) { + this(mid8, mid5, mid8, mid5, mid8, mid5, purepart); + } + + ColorTint(float r, float r5, float g, float g5, float b, float b5, float purepart) { + this.r = r; + this.r5 = r5; + this.g = g; + this.g5 = g5; + this.b = b; + this.b5 = b5; + this.purepart = purepart; + for (int j = 0; j < GammaTables.LUT.length; ++j) { + for (int i = 0; i <= 0xFF; ++i) { + LUT_r8[j][i] = (byte) GammaTables.LUT[j][tintRed8(i)]; + LUT_g8[j][i] = (byte) GammaTables.LUT[j][tintGreen8(i)]; + LUT_b8[j][i] = (byte) GammaTables.LUT[j][tintBlue8(i)]; + if (i <= 0x1F) { + LUT_r5[j][i] = (byte) (GammaTables.LUT[j][tintRed5(i) << 3] >> 3); + LUT_g5[j][i] = (byte) (GammaTables.LUT[j][tintGreen5(i) << 3] >> 3); + LUT_b5[j][i] = (byte) (GammaTables.LUT[j][tintBlue5(i) << 3] >> 3); + } + } + } + } + + private final float r, g, b; + private final float r5, g5, b5; + private final float purepart; + public final byte[][] LUT_r8 = new byte[5][0x100]; + public final byte[][] LUT_g8 = new byte[5][0x100]; + public final byte[][] LUT_b8 = new byte[5][0x100]; + public final byte[][] LUT_r5 = new byte[5][0x20]; + public final byte[][] LUT_g5 = new byte[5][0x20]; + public final byte[][] LUT_b5 = new byte[5][0x20]; + + public float mid() { + return (r + g + b) / 3; + } + + public float mid5() { + return (r5 + g5 + b5) / 3; + } + + public final int tintGreen8(int green8) { + return Math.min((int) (green8 * purepart + g), 0xFF); + } + + public final int tintGreen5(int green5) { + return Math.min((int) (green5 * purepart + g5), 0x1F); + } + + public final int tintBlue8(int blue8) { + return Math.min((int) (blue8 * purepart + b), 0xFF); + } + + public final int tintBlue5(int blue5) { + return Math.min((int) (blue5 * purepart + b5), 0x1F); + } + + public final int tintRed8(int red8) { + return Math.min((int) (red8 * purepart + r), 0xFF); + } + + public final int tintRed5(int red5) { + return Math.min((int) (red5 * purepart + r5), 0x1F); + } +} \ No newline at end of file diff --git a/doom/src/v/tables/FuzzMix.java b/doom/src/v/tables/FuzzMix.java new file mode 100644 index 0000000..d755a7c --- /dev/null +++ b/doom/src/v/tables/FuzzMix.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +/** + * FuzzMix: Unique feature by Maes for HiColor detailed mode + * This should be preserved, but I've moved it to appropriate place + * Option to enable the feature in cfg: fuzz_mix + * + * Note: the TrueColor alpha half-brite will only work + * properly with cfg: color_depth AlphaTrueColor also set + * + * Made it an interface, it is as easy to apply to anything as setting pixel + * - Good Sign 2017/04/16 + * + * @author velktron + */ +public interface FuzzMix { + + /** + * Was used by: + * R_DrawFuzzColumn.HiColor + * R_DrawFuzzColumnLow.HiColor + * + * Now used by BlurryTable::computePixel + * only if the option fuzz_mix enabled + */ + default short fuzzMixHi(short rgb) { + // super-fast half-brite trick + // 3DEF and >> 1: ok hue, but too dark + // 7BDE, no shift: good compromise + // 739C, no shift: results in too obvious tinting. + return (short) (rgb & 0x7BDE); + } + + /** + * Was used by: + * R_DrawFuzzColumn.TrueColor + * + * Now used by BlurryTable::computePixel + * only if the option fuzz_mix enabled + * + * AX: This is what makes it blurry + */ + default int fuzzMixTrue(int rgb) { + // Proper half-brite alpha! + return rgb & 0x10FFFFFF; + } + + /** + * Was used by: + * R_DrawFuzzColumnLow.TrueColor + * + * Now used by BlurryTable::computePixel + * only if the option fuzz_mix enabled + * + * AX: This is what made it dark and ugly + */ + default int fuzzMixTrueLow(int rgb) { + // super-fast half-brite trick + // 3DEF and >> 1: ok hue, but too dark + // FF7C7C7C, no shift: good compromise + // FF707070, no shift: results in too obvious tinting. + + return (rgb >> 1) & 0xFF7F7F7F; + } +} \ No newline at end of file diff --git a/doom/src/v/tables/GammaTables.java b/doom/src/v/tables/GammaTables.java new file mode 100644 index 0000000..bc461ec --- /dev/null +++ b/doom/src/v/tables/GammaTables.java @@ -0,0 +1,118 @@ +// +// Copyright (C) 1993-1996 Id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Gamma correction LUT stuff. +// Functions to draw patches (by post) directly to screen. +// Functions to blit a block to the screen. +// +// from v_video.c +// +package v.tables; + +import m.Settings; +import mochadoom.Engine; + +public class GammaTables { + + // Now where did these came from? + public final static int[][] LUT + = { + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255}, + {2, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 30, 31, + 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, + 146, 147, 148, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 214, 215, 216, 217, 218, + 219, 220, 221, 222, 222, 223, 224, 225, 226, 227, 228, 229, 230, 230, 231, 232, + 233, 234, 235, 236, 237, 237, 238, 239, 240, 241, 242, 243, 244, 245, 245, 246, + 247, 248, 249, 250, 251, 252, 252, 253, 254, 255}, + {4, 7, 9, 11, 13, 15, 17, 19, 21, 22, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 40, 42, + 43, 45, 46, 47, 48, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, + 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 153, 154, 155, 156, 157, 158, 159, + 160, 160, 161, 162, 163, 164, 165, 166, 166, 167, 168, 169, 170, 171, 172, 172, 173, + 174, 175, 176, 177, 178, 178, 179, 180, 181, 182, 183, 183, 184, 185, 186, 187, 188, + 188, 189, 190, 191, 192, 193, 193, 194, 195, 196, 197, 197, 198, 199, 200, 201, 201, + 202, 203, 204, 205, 206, 206, 207, 208, 209, 210, 210, 211, 212, 213, 213, 214, 215, + 216, 217, 217, 218, 219, 220, 221, 221, 222, 223, 224, 224, 225, 226, 227, 228, 228, + 229, 230, 231, 231, 232, 233, 234, 235, 235, 236, 237, 238, 238, 239, 240, 241, 241, + 242, 243, 244, 244, 245, 246, 247, 247, 248, 249, 250, 251, 251, 252, 253, 254, 254, + 255}, + {8, 12, 16, 19, 22, 24, 27, 29, 31, 34, 36, 38, 40, 41, 43, 45, 47, 49, 50, 52, 53, 55, + 57, 58, 60, 61, 63, 64, 65, 67, 68, 70, 71, 72, 74, 75, 76, 77, 79, 80, 81, 82, 84, 85, + 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 135, 136, 137, 138, 139, 140, + 141, 142, 143, 143, 144, 145, 146, 147, 148, 149, 150, 150, 151, 152, 153, 154, 155, + 155, 156, 157, 158, 159, 160, 160, 161, 162, 163, 164, 165, 165, 166, 167, 168, 169, + 169, 170, 171, 172, 173, 173, 174, 175, 176, 176, 177, 178, 179, 180, 180, 181, 182, + 183, 183, 184, 185, 186, 186, 187, 188, 189, 189, 190, 191, 192, 192, 193, 194, 195, + 195, 196, 197, 197, 198, 199, 200, 200, 201, 202, 202, 203, 204, 205, 205, 206, 207, + 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 215, 216, 216, 217, 218, 219, + 219, 220, 221, 221, 222, 223, 223, 224, 225, 225, 226, 227, 227, 228, 229, 229, 230, + 231, 231, 232, 233, 233, 234, 235, 235, 236, 237, 237, 238, 238, 239, 240, 240, 241, + 242, 242, 243, 244, 244, 245, 246, 246, 247, 247, 248, 249, 249, 250, 251, 251, 252, + 253, 253, 254, 254, 255}, + {16, 23, 28, 32, 36, 39, 42, 45, 48, 50, 53, 55, 57, 60, 62, 64, 66, 68, 69, 71, 73, 75, 76, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 93, 94, 96, 97, 98, 100, 101, 102, 103, 105, 106, + 107, 108, 109, 110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 128, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 143, 144, 145, 146, 147, 148, 149, 150, 150, 151, 152, 153, 154, 155, 155, + 156, 157, 158, 159, 159, 160, 161, 162, 163, 163, 164, 165, 166, 166, 167, 168, 169, + 169, 170, 171, 172, 172, 173, 174, 175, 175, 176, 177, 177, 178, 179, 180, 180, 181, + 182, 182, 183, 184, 184, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, + 193, 194, 195, 195, 196, 196, 197, 198, 198, 199, 200, 200, 201, 202, 202, 203, 203, + 204, 205, 205, 206, 207, 207, 208, 208, 209, 210, 210, 211, 211, 212, 213, 213, 214, + 214, 215, 216, 216, 217, 217, 218, 219, 219, 220, 220, 221, 221, 222, 223, 223, 224, + 224, 225, 225, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 232, 232, 233, 233, + 234, 234, 235, 235, 236, 236, 237, 237, 238, 239, 239, 240, 240, 241, 241, 242, 242, + 243, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, + 251, 252, 252, 253, 254, 254, 255, 255} + }; + + static { + if (Engine.getConfig().equals(Settings.fix_gamma_ramp, Boolean.TRUE)) { + for (int i = 0; i < 128; --LUT[0][i++]) { + } + } + } + + private GammaTables() { + } +} \ No newline at end of file diff --git a/doom/src/v/tables/GreyscaleFilter.java b/doom/src/v/tables/GreyscaleFilter.java new file mode 100644 index 0000000..7fe369c --- /dev/null +++ b/doom/src/v/tables/GreyscaleFilter.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +import m.Settings; +import mochadoom.Engine; + +/** + * + * @author Good Sign + */ +public enum GreyscaleFilter { + Lightness, + Average, + Luminance, // this one is the default for invulnerability map + Luminosity; + + private static GreyscaleFilter FILTER; + + public static int component(int r, int g, int b) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getComponent(r, g, b); + } + + public static float component(float r, float g, float b) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getComponent(r, g, b); + } + + public static int grey888(int rgb888) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getGrey888(rgb888); + } + + public static int grey888(int r8, int g8, int b8) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getGrey888(r8, g8, b8); + } + + public static short grey555(int r5, int g5, int b5) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getGrey555(r5, g5, b5); + } + + public static short grey555(short rgb555) { + if (FILTER == null) { + readSetting(); + } + return FILTER.getGrey555(rgb555); + } + + private static void readSetting() { + FILTER = Engine.getConfig().getValue(Settings.greyscale_filter, GreyscaleFilter.class); + } + + public int getComponent(int r, int g, int b) { + switch (this) { + case Lightness: + return (Math.max(Math.max(r, g), b) + Math.min(Math.min(r, g), b)) / 2; + case Average: + return (r + g + b) / 3; + case Luminance: + return (int) (0.299f * r + 0.587f * g + 0.114f * b); + case Luminosity: + return (int) (0.2126f * r + 0.7152f * g + 0.0722f * b); + } + + // should not happen + return 0; + } + + public float getComponent(float r, float g, float b) { + switch (this) { + case Lightness: + return (Math.max(Math.max(r, g), b) + Math.min(Math.min(r, g), b)) / 2; + case Average: + return (r + g + b) / 3; + case Luminance: + return 0.299f * r + 0.587f * g + 0.114f * b; + case Luminosity: + return 0.2126f * r + 0.7152f * g + 0.0722f * b; + } + + // should not happen + return 0.0f; + } + + public int getGrey888(int r8, int g8, int b8) { + final int component = getComponent(r8, g8, b8) & 0xFF; + return 0xFF000000 + (component << 16) + (component << 8) + component; + } + + public short getGrey555(int r5, int g5, int b5) { + final int component = getComponent(r5, g5, b5) & 0x1F; + return (short) ((component << 10) + (component << 5) + component); + } + + public int getGrey888(int rgb888) { + return getGrey888((rgb888 >> 16) & 0xFF, (rgb888 >> 8) & 0xFF, rgb888 & 0xFF); + } + + public short getGrey555(short rgb555) { + return getGrey555((rgb555 >> 10) & 0x1F, (rgb555 >> 5) & 0x1F, rgb555 & 0x1F); + } +} \ No newline at end of file diff --git a/doom/src/v/tables/LightsAndColors.java b/doom/src/v/tables/LightsAndColors.java new file mode 100644 index 0000000..6018322 --- /dev/null +++ b/doom/src/v/tables/LightsAndColors.java @@ -0,0 +1,186 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +import doom.DoomMain; +import doom.player_t; +import static p.MobjFlags.MF_TRANSLATION; +import static p.MobjFlags.MF_TRANSSHIFT; +import v.renderers.BppMode; + +/** + * Combined colormap and light LUTs. + * Used for z-depth cuing per column/row, + * and other lighting effects (sector ambient, flash). + * + * @author velktron + * + * @param The data type of the SCREEN + */ +public class LightsAndColors { + + private final LCData LC_DATA; + + /** For HiColor, these are, effectively, a bunch of 555 RGB palettes, + * for TrueColor they are a bunch of 32-bit ARGB palettes etc. + * Only for indexed they represent index remappings. + */ + /** "peg" this to the one from RendererData */ + public V[] colormaps; + + /** lighttable_t** */ + public V[] walllights; + + /** Use in conjunction with player.fixedcolormap */ + public V fixedcolormap; + + /** + * Color tables for different players, translate a limited part to another + * (color ramps used for suit colors). + */ + public byte[][] translationtables; + + // bumped light from gun blasts + public int extralight; + + public V[][] scalelight; + public V[] scalelightfixed; + public V[][] zlight; + public V[] spritelights; + + public LightsAndColors(final DoomMain DM) { + this.LC_DATA = new LCData(DM.bppMode); + } + + public int lightBits() { + return LC_DATA.bpp.lightBits; + } + + public int lightBright() { + return LC_DATA.LIGHTBRIGHT; + } + + public int lightLevels() { + return LC_DATA.LIGHTLEVELS; + } + + public int lightScaleShift() { + return LC_DATA.LIGHTSCALESHIFT; + } + + public int lightSegShift() { + return LC_DATA.LIGHTSEGSHIFT; + } + + public int lightZShift() { + return LC_DATA.LIGHTZSHIFT; + } + + public int maxLightScale() { + return LC_DATA.MAXLIGHTSCALE; + } + + public int maxLightZ() { + return LC_DATA.MAXLIGHTZ; + } + + public int numColorMaps() { + return LC_DATA.NUMCOLORMAPS; + } + + /** + * player_t.fixedcolormap have a range of 0..31 in vanilla. + * We must respect it. However, we can have more lightlevels then vanilla. + * So we must scale player_t.fixedcolormap by the difference with vanilla lightBits + * + * @param player + * @return index in rich bit liteColorMaps + */ + public V getFixedColormap(player_t player) { + if (LC_DATA.bpp.lightBits > 5) { + return colormaps[player.fixedcolormap << (LC_DATA.bpp.lightBits - 5)]; + } + + return colormaps[player.fixedcolormap]; + } + + public final byte[] getTranslationTable(long mobjflags) { + return translationtables[(int) ((mobjflags & MF_TRANSLATION) >> MF_TRANSSHIFT)]; + } + + private static class LCData { + + final BppMode bpp; + + /** + * These two are tied by an inverse relationship. E.g. 256 levels, 0 shift + * 128 levels, 1 shift ...etc... 16 levels, 4 shift (default). Or even less, + * if you want. + * + * By setting it to the max however you get smoother light and get rid of + * lightsegshift globally, too. Of course, by increasing the number of light + * levels, you also put more memory pressure, and due to their being only + * 256 colors to begin with, visually, there won't be many differences. + */ + final int LIGHTLEVELS; + final int LIGHTSEGSHIFT; + + /** Number of diminishing brightness levels. + There a 0-31, i.e. 32 LUT in the COLORMAP lump. + TODO: how can those be distinct from the light levels??? + */ + final int NUMCOLORMAPS; + + // These are a bit more tricky to figure out though. + /** Maximum index used for light levels of sprites. In practice, + * it's capped by the number of light levels??? + * + * Normally set to 48 (32 +16???) + */ + final int MAXLIGHTSCALE; + + /** Used to scale brightness of walls and sprites. Their "scale" is shifted by + * this amount, and this results in an index, which is capped by MAXLIGHTSCALE. + * Normally it's 12 for 32 levels, so 11 for 64, 10 for 128, ans 9 for 256. + * + */ + final int LIGHTSCALESHIFT; + + /** This one seems arbitrary. Will auto-fit to 128 possible levels? */ + final int MAXLIGHTZ; + + final int LIGHTBRIGHT; + + /** Normally 20 for 32 colormaps, applied to distance. + * Formula: 25-LBITS + * + */ + final int LIGHTZSHIFT; + + LCData(final BppMode bpp) { + this.bpp = bpp; + LIGHTLEVELS = 1 << bpp.lightBits; + MAXLIGHTZ = LIGHTLEVELS * 4; + LIGHTBRIGHT = 2; + LIGHTSEGSHIFT = 8 - bpp.lightBits; + NUMCOLORMAPS = LIGHTLEVELS; + MAXLIGHTSCALE = 3 * LIGHTLEVELS / 2; + LIGHTSCALESHIFT = 17 - bpp.lightBits; + LIGHTZSHIFT = 25 - bpp.lightBits; + } + } +} \ No newline at end of file diff --git a/doom/src/v/tables/Playpal.java b/doom/src/v/tables/Playpal.java new file mode 100644 index 0000000..d9d5cc1 --- /dev/null +++ b/doom/src/v/tables/Playpal.java @@ -0,0 +1,162 @@ +/** + * Copyright (C) 2017 Good Sign + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package v.tables; + +import java.util.stream.IntStream; +import v.graphics.Palettes; +import static v.graphics.Palettes.NUM_PALETTES; +import static v.graphics.Palettes.PAL_NUM_COLORS; +import static v.tables.ColorTint.NORMAL_TINTS; + +/** + * Palette generation failsafe. Uses only data from the first palette, and + * generates the rest by tinting according to the Doom wiki specs. Uses info + * from: http://doom.wikia.com/wiki/PLAYPAL + */ +public class Playpal { + + private final static int playpal[] = {0x00, 0x00, 0x00, 0x1F, 0x17, 0x0B, + 0x17, 0x0F, 0x07, 0x4B, 0x4B, 0x4B, 0xFF, 0xFF, 0xFF, 0x1B, 0x1B, + 0x1B, 0x13, 0x13, 0x13, 0x0B, 0x0B, 0x0B, 0x07, 0x07, 0x07, 0x2F, + 0x37, 0x1F, 0x23, 0x2B, 0x0F, 0x17, 0x1F, 0x07, 0x0F, 0x17, 0x00, + 0x4F, 0x3B, 0x2B, 0x47, 0x33, 0x23, 0x3F, 0x2B, 0x1B, 0xFF, 0xB7, + 0xB7, 0xF7, 0xAB, 0xAB, 0xF3, 0xA3, 0xA3, 0xEB, 0x97, 0x97, 0xE7, + 0x8F, 0x8F, 0xDF, 0x87, 0x87, 0xDB, 0x7B, 0x7B, 0xD3, 0x73, 0x73, + 0xCB, 0x6B, 0x6B, 0xC7, 0x63, 0x63, 0xBF, 0x5B, 0x5B, 0xBB, 0x57, + 0x57, 0xB3, 0x4F, 0x4F, 0xAF, 0x47, 0x47, 0xA7, 0x3F, 0x3F, 0xA3, + 0x3B, 0x3B, 0x9B, 0x33, 0x33, 0x97, 0x2F, 0x2F, 0x8F, 0x2B, 0x2B, + 0x8B, 0x23, 0x23, 0x83, 0x1F, 0x1F, 0x7F, 0x1B, 0x1B, 0x77, 0x17, + 0x17, 0x73, 0x13, 0x13, 0x6B, 0x0F, 0x0F, 0x67, 0x0B, 0x0B, 0x5F, + 0x07, 0x07, 0x5B, 0x07, 0x07, 0x53, 0x07, 0x07, 0x4F, 0x00, 0x00, + 0x47, 0x00, 0x00, 0x43, 0x00, 0x00, 0xFF, 0xEB, 0xDF, 0xFF, 0xE3, + 0xD3, 0xFF, 0xDB, 0xC7, 0xFF, 0xD3, 0xBB, 0xFF, 0xCF, 0xB3, 0xFF, + 0xC7, 0xA7, 0xFF, 0xBF, 0x9B, 0xFF, 0xBB, 0x93, 0xFF, 0xB3, 0x83, + 0xF7, 0xAB, 0x7B, 0xEF, 0xA3, 0x73, 0xE7, 0x9B, 0x6B, 0xDF, 0x93, + 0x63, 0xD7, 0x8B, 0x5B, 0xCF, 0x83, 0x53, 0xCB, 0x7F, 0x4F, 0xBF, + 0x7B, 0x4B, 0xB3, 0x73, 0x47, 0xAB, 0x6F, 0x43, 0xA3, 0x6B, 0x3F, + 0x9B, 0x63, 0x3B, 0x8F, 0x5F, 0x37, 0x87, 0x57, 0x33, 0x7F, 0x53, + 0x2F, 0x77, 0x4F, 0x2B, 0x6B, 0x47, 0x27, 0x5F, 0x43, 0x23, 0x53, + 0x3F, 0x1F, 0x4B, 0x37, 0x1B, 0x3F, 0x2F, 0x17, 0x33, 0x2B, 0x13, + 0x2B, 0x23, 0x0F, 0xEF, 0xEF, 0xEF, 0xE7, 0xE7, 0xE7, 0xDF, 0xDF, + 0xDF, 0xDB, 0xDB, 0xDB, 0xD3, 0xD3, 0xD3, 0xCB, 0xCB, 0xCB, 0xC7, + 0xC7, 0xC7, 0xBF, 0xBF, 0xBF, 0xB7, 0xB7, 0xB7, 0xB3, 0xB3, 0xB3, + 0xAB, 0xAB, 0xAB, 0xA7, 0xA7, 0xA7, 0x9F, 0x9F, 0x9F, 0x97, 0x97, + 0x97, 0x93, 0x93, 0x93, 0x8B, 0x8B, 0x8B, 0x83, 0x83, 0x83, 0x7F, + 0x7F, 0x7F, 0x77, 0x77, 0x77, 0x6F, 0x6F, 0x6F, 0x6B, 0x6B, 0x6B, + 0x63, 0x63, 0x63, 0x5B, 0x5B, 0x5B, 0x57, 0x57, 0x57, 0x4F, 0x4F, + 0x4F, 0x47, 0x47, 0x47, 0x43, 0x43, 0x43, 0x3B, 0x3B, 0x3B, 0x37, + 0x37, 0x37, 0x2F, 0x2F, 0x2F, 0x27, 0x27, 0x27, 0x23, 0x23, 0x23, + 0x77, 0xFF, 0x6F, 0x6F, 0xEF, 0x67, 0x67, 0xDF, 0x5F, 0x5F, 0xCF, + 0x57, 0x5B, 0xBF, 0x4F, 0x53, 0xAF, 0x47, 0x4B, 0x9F, 0x3F, 0x43, + 0x93, 0x37, 0x3F, 0x83, 0x2F, 0x37, 0x73, 0x2B, 0x2F, 0x63, 0x23, + 0x27, 0x53, 0x1B, 0x1F, 0x43, 0x17, 0x17, 0x33, 0x0F, 0x13, 0x23, + 0x0B, 0x0B, 0x17, 0x07, 0xBF, 0xA7, 0x8F, 0xB7, 0x9F, 0x87, 0xAF, + 0x97, 0x7F, 0xA7, 0x8F, 0x77, 0x9F, 0x87, 0x6F, 0x9B, 0x7F, 0x6B, + 0x93, 0x7B, 0x63, 0x8B, 0x73, 0x5B, 0x83, 0x6B, 0x57, 0x7B, 0x63, + 0x4F, 0x77, 0x5F, 0x4B, 0x6F, 0x57, 0x43, 0x67, 0x53, 0x3F, 0x5F, + 0x4B, 0x37, 0x57, 0x43, 0x33, 0x53, 0x3F, 0x2F, 0x9F, 0x83, 0x63, + 0x8F, 0x77, 0x53, 0x83, 0x6B, 0x4B, 0x77, 0x5F, 0x3F, 0x67, 0x53, + 0x33, 0x5B, 0x47, 0x2B, 0x4F, 0x3B, 0x23, 0x43, 0x33, 0x1B, 0x7B, + 0x7F, 0x63, 0x6F, 0x73, 0x57, 0x67, 0x6B, 0x4F, 0x5B, 0x63, 0x47, + 0x53, 0x57, 0x3B, 0x47, 0x4F, 0x33, 0x3F, 0x47, 0x2B, 0x37, 0x3F, + 0x27, 0xFF, 0xFF, 0x73, 0xEB, 0xDB, 0x57, 0xD7, 0xBB, 0x43, 0xC3, + 0x9B, 0x2F, 0xAF, 0x7B, 0x1F, 0x9B, 0x5B, 0x13, 0x87, 0x43, 0x07, + 0x73, 0x2B, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xDB, 0xDB, 0xFF, 0xBB, + 0xBB, 0xFF, 0x9B, 0x9B, 0xFF, 0x7B, 0x7B, 0xFF, 0x5F, 0x5F, 0xFF, + 0x3F, 0x3F, 0xFF, 0x1F, 0x1F, 0xFF, 0x00, 0x00, 0xEF, 0x00, 0x00, + 0xE3, 0x00, 0x00, 0xD7, 0x00, 0x00, 0xCB, 0x00, 0x00, 0xBF, 0x00, + 0x00, 0xB3, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x8B, + 0x00, 0x00, 0x7F, 0x00, 0x00, 0x73, 0x00, 0x00, 0x67, 0x00, 0x00, + 0x5B, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x43, 0x00, 0x00, 0xE7, 0xE7, + 0xFF, 0xC7, 0xC7, 0xFF, 0xAB, 0xAB, 0xFF, 0x8F, 0x8F, 0xFF, 0x73, + 0x73, 0xFF, 0x53, 0x53, 0xFF, 0x37, 0x37, 0xFF, 0x1B, 0x1B, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0x00, 0xE3, 0x00, 0x00, 0xCB, 0x00, 0x00, + 0xB3, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x83, 0x00, 0x00, 0x6B, 0x00, + 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0xFF, 0xEB, 0xDB, 0xFF, 0xD7, 0xBB, + 0xFF, 0xC7, 0x9B, 0xFF, 0xB3, 0x7B, 0xFF, 0xA3, 0x5B, 0xFF, 0x8F, + 0x3B, 0xFF, 0x7F, 0x1B, 0xF3, 0x73, 0x17, 0xEB, 0x6F, 0x0F, 0xDF, + 0x67, 0x0F, 0xD7, 0x5F, 0x0B, 0xCB, 0x57, 0x07, 0xC3, 0x4F, 0x00, + 0xB7, 0x47, 0x00, 0xAF, 0x43, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xD7, 0xFF, 0xFF, 0xB3, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0x6B, 0xFF, + 0xFF, 0x47, 0xFF, 0xFF, 0x23, 0xFF, 0xFF, 0x00, 0xA7, 0x3F, 0x00, + 0x9F, 0x37, 0x00, 0x93, 0x2F, 0x00, 0x87, 0x23, 0x00, 0x4F, 0x3B, + 0x27, 0x43, 0x2F, 0x1B, 0x37, 0x23, 0x13, 0x2F, 0x1B, 0x0B, 0x00, + 0x00, 0x53, 0x00, 0x00, 0x47, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x2F, + 0x00, 0x00, 0x23, 0x00, 0x00, 0x17, 0x00, 0x00, 0x0B, 0x00, 0x00, + 0x00, 0xFF, 0x9F, 0x43, 0xFF, 0xE7, 0x4B, 0xFF, 0x7B, 0xFF, 0xFF, + 0x00, 0xFF, 0xCF, 0x00, 0xCF, 0x9F, 0x00, 0x9B, 0x6F, 0x00, 0x6B, + 0xA7, 0x6B, 0x6B}; + + /** + * Why not restore idea of grayscale palette? + * - Good Sign 2017/04/12 + * + * for (int i = 0; i < 256; i++) { + * greypal[3 * i + 2] = greypal[3 * i + 1] = greypal[3 * i] = (playpal[3 * i] + playpal[3 * i + 1] + playpal[3 * i + 2])/3; + * } + */ + private final static int greypal[] = IntStream.range(0, Palettes.PAL_NUM_COLORS) + .flatMap(i + -> IntStream.of(i = GreyscaleFilter.component(playpal[3 * i], playpal[3 * i + 1], playpal[3 * i + 2]), i, i) + ).toArray(); + + /** + * Get grey palette in PLAYPAL bytes format + */ + public static byte[] greypal() { + return properPlaypal(greypal, null); + } + + /** + * Get full standard palette in PLAYPAL format provided full or improper version + * MAES: FIX for incomplete palette lumps such as those in EGADOOM. + * Generate the palette programmatically _anyway_ + */ + public static byte[] properPlaypal(byte[] lumpData) { + return properPlaypal(playpal, lumpData); + } + + private static byte[] properPlaypal(int[] data, byte[] lumpData) { + final int palstride = PAL_NUM_COLORS * 3; + final byte[] palette = new byte[palstride * NUM_PALETTES]; + final int[] rgb = new int[3]; + for (int i = 0; i < PAL_NUM_COLORS; i++) { + rgb[0] = data[3 * i]; + rgb[1] = data[1 + 3 * i]; + rgb[2] = data[2 + 3 * i]; + for (int t = 0; t < NUM_PALETTES; t++) { + final ColorTint tint = NORMAL_TINTS.get(t); + palette[palstride * t + 3 * i] = (byte) tint.tintRed8(rgb[0]); + palette[palstride * t + 3 * i + 1] = (byte) tint.tintGreen8(rgb[1]); + palette[palstride * t + 3 * i + 2] = (byte) tint.tintBlue8(rgb[2]); + } + } + + /** + * If we have part or a whole palette, repair it using ours, + * otherwise just use ours. Math.min to avoid larger palette to cause exception. + */ + if (lumpData != null) { + System.arraycopy(lumpData, 0, palette, 0, Math.min(lumpData.length, palette.length)); + } + + return palette; + } + + private Playpal() { + } +} \ No newline at end of file diff --git a/doom/src/w/AidedReadableDoomObject.java b/doom/src/w/AidedReadableDoomObject.java new file mode 100644 index 0000000..178885e --- /dev/null +++ b/doom/src/w/AidedReadableDoomObject.java @@ -0,0 +1,15 @@ +package w; + +import java.io.DataInputStream; +import java.io.IOException; + +/** This is for objects that can be read from disk, but cannot + * self-determine their own length for some reason. + * + * @author Maes + * + */ +public interface AidedReadableDoomObject { + + public void read(DataInputStream f, int len) throws IOException; +} \ No newline at end of file diff --git a/doom/src/w/CacheableDoomObject.java b/doom/src/w/CacheableDoomObject.java new file mode 100644 index 0000000..71ec62d --- /dev/null +++ b/doom/src/w/CacheableDoomObject.java @@ -0,0 +1,47 @@ +package w; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** All objects that can be deserialized from raw byte buffers such as those + * read from WAD lumps should implement this method, so that the WadLoader + * can cache them, and recursive calls to sub-objects can be made. + * + * E.g. an object of type A consists of a header and a list of objects of type B. + * Calling A.unpack(buf) will cause A to unmarshal its own header, set the list of + * B objects, and then call B.unpack() for each of them, by passing the same buffer + * along. + * + * This system works cleanly, and allows to simulate Doom's "cached memory" while + * returning proper objects of the correct type and keeping close to Java's + * "correct" way of doing things. + * * + * For example, if a patch_t is read from disk, the WadLoader uses its unpack() method + * to read it from a lump read from disk, and creates a new patch_t object, which is placed + * in the lump cache (which holds CacheableDoomObject, incidentally). The next time this + * same patch_t is requested, the reference to the already cached patch_t will be returned, + * if it hasn't been forcedly flushed from the cache. Voila', lump caching! + * + * The principle can be applied to ARRAYS of similar objects too: using the same buffer, + * iterative serial unpacking is possible, while still mantaining a "cached" reference + * to their array (TODO: actually, this needs to be implemented more efficiently. Look in + * WadLoader) + * + * The opposite would be a "PackableDoomObject", aka objects that can pack themselves into + * a byte buffer for transmission purposes, although Doom doesn't really need to write as + * much as it needs reading. + * + * For the purpose of saving/loading games, which need to read/write to variable disk + * structures ALL the time, use the ReadableDoomObject/WritableDoomObject interfaces. + * Their difference is that they are highly mutable and supposed to be read from files + * or input/output streams, and that a continuous reference to them as deserialized + * objects (e.g. in the caching mechanism) is not needed. + * + * + * @author Velktron + * + */ +public interface CacheableDoomObject { + + public void unpack(ByteBuffer buf) throws IOException; +} \ No newline at end of file diff --git a/doom/src/w/CacheableDoomObjectContainer.java b/doom/src/w/CacheableDoomObjectContainer.java new file mode 100644 index 0000000..f305310 --- /dev/null +++ b/doom/src/w/CacheableDoomObjectContainer.java @@ -0,0 +1,47 @@ +package w; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** A container allowing for caching of arrays of CacheableDoomObjects + * + * It's a massive improvement over the older system, allowing for proper + * caching and auto-unpacking of arrays of CacheableDoomObjects and much + * cleaner code throughout. + * + * The container itself is a CacheableDoomObject....can you feel the + * abuse? ;-) + * + */ +public class CacheableDoomObjectContainer implements CacheableDoomObject { + + private T[] stuff; + + public CacheableDoomObjectContainer(T[] stuff) { + this.stuff = stuff; + } + + public T[] getStuff() { + return stuff; + } + + @Override + public void unpack(ByteBuffer buf) throws IOException { + for (int i = 0; i < stuff.length; i++) { + stuff[i].unpack(buf); + } + } + + /** Statically usable method + * + * @param buf + * @param stuff + * @throws IOException + */ + public static void unpack(ByteBuffer buf, CacheableDoomObject[] stuff) throws IOException { + for (int i = 0; i < stuff.length; i++) { + stuff[i].unpack(buf); + } + } + +} \ No newline at end of file diff --git a/doom/src/w/DoomBuffer.java b/doom/src/w/DoomBuffer.java new file mode 100644 index 0000000..4f77f1b --- /dev/null +++ b/doom/src/w/DoomBuffer.java @@ -0,0 +1,273 @@ +package w; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** Very similar to the concept of ReadableDoomObjects + * but made to work with byte buffers instead. + * + * This is normally NOT used to pass data around: I am + * using it as a workaround to store raw byte buffers + * into a "CacheableDoomObject" array, as Java + * doesn't seem to like storing both ByteBuffers and + * CacheableDoomObjects in the same array. WTF... + * + * @author admin + * + */ +public class DoomBuffer implements CacheableDoomObject { + + public DoomBuffer() { + + } + + public DoomBuffer(ByteBuffer b) { + this.buffer = b; + } + + private ByteBuffer buffer; + + public static void readObjectArray(ByteBuffer buf, CacheableDoomObject[] s, int len) throws IOException { + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i].unpack(buf); + } + } + + public static void readIntArray(ByteBuffer buf, int[] s, int len) throws IOException { + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = buf.getInt(); + } + } + + public static void putIntArray(ByteBuffer buf, int[] s, int len, ByteOrder bo) throws IOException { + buf.order(bo); + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + buf.putInt(s[i]); + } + } + + public static void putBooleanIntArray(ByteBuffer buf, boolean[] s, int len, ByteOrder bo) throws IOException { + buf.order(bo); + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + buf.putInt(s[i] ? 1 : 0); + } + } + + public static void putBooleanInt(ByteBuffer buf, boolean s, ByteOrder bo) throws IOException { + buf.order(bo); + buf.putInt(s ? 1 : 0); + } + + public static void readCharArray(ByteBuffer buf, char[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = buf.getChar(); + } + } + + public static void readShortArray(ByteBuffer buf, short[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = buf.getShort(); + } + } + + public void readShortArray(short[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = this.buffer.getShort(); + + } + } + + public void readCharArray(char[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = this.buffer.getChar(); + + } + } + + public void readCharArray(int[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = this.buffer.getChar(); + + } + } + + /** Reads a length specified string from a buffer. */ + public static String readString(ByteBuffer buf) throws IOException { + int len = buf.getInt(); + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + buf.get(bb, 0, len); + + return new String(bb, 0, len); + } + + /** MAES: Reads a specified number of bytes from a buffer into a new String. + * With many lengths being implicit, we need to actually take the loader by the hand. + * + * @param buf + * @param len + * @return + * @throws IOException + */ + public static String getString(ByteBuffer buf, int len) throws IOException { + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + buf.get(bb, 0, len); + + return new String(bb, 0, len); + } + + /** MAES: Reads a maximum specified number of bytes from a buffer into a new String, + * considering the bytes as representing a null-terminated, C-style string. + * + * @param buf + * @param len + * @return + * @throws IOException + */ + public static String getNullTerminatedString(ByteBuffer buf, int len) throws IOException { + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + buf.get(bb, 0, len); + // Detect null-termination. + for (int i = 0; i < len; i++) { + if (bb[i] == 0x00) { + len = i; + break; + } + } + + return new String(bb, 0, len); + } + + /** MAES: Reads a specified number of bytes from a buffer into a new String. + * With many lengths being implicit, we need to actually take the loader by the hand. + * + * @param buf + * @param len + * @return + * @throws IOException + */ + public static char[] getCharSeq(ByteBuffer buf, int len) throws IOException { + return (getString(buf, len)).toCharArray(); + } + + @Override + public void unpack(ByteBuffer buf) + throws IOException { + this.buffer = buf; + + } + + public ByteBuffer getBuffer() { + return buffer; + } + + public void setOrder(ByteOrder bo) { + this.buffer.order(bo); + } + + public void rewind() { + this.buffer.rewind(); + } + + public static int getBEInt(byte b3, byte b2, byte b1, byte b0) { + return (b3 << 24 | b2 << 16 | b1 << 8 | b0); + } + + public static int getBEInt(byte[] buf, int offset) { + return (buf[offset] << 24 | buf[offset + 1] << 16 | buf[offset + 2] << 8 | buf[offset + 3]); + } + + public static int getBEInt(byte[] buf) { + return (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]); + } + + public static int getLEInt(byte b0, byte b1, byte b2, byte b3) { + return (b3 << 24 | b2 << 16 | b1 << 8 | b0); + } + + public static int getLEInt(byte[] buf) { + return (buf[3] << 24 | buf[2] << 16 | buf[1] << 24 | buf[0]); + } + + public static short getBEShort(byte[] buf) { + return (short) (buf[0] << 8 | buf[1]); + } + + public static short getLEShort(byte[] buf) { + return (short) (buf[0] << 8 | buf[1]); + } + +} \ No newline at end of file diff --git a/doom/src/w/DoomIO.java b/doom/src/w/DoomIO.java new file mode 100644 index 0000000..ea92837 --- /dev/null +++ b/doom/src/w/DoomIO.java @@ -0,0 +1,501 @@ +package w; + +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + */ +//Created on 24.07.2004 by RST. +//$Id: DoomIO.java,v 1.3 2013/06/03 10:30:20 velktron Exp $ +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import m.Swap; +import mochadoom.Loggers; + +/** + * An extension of RandomAccessFile, which handles readString/WriteString specially + * and offers several Doom related (and cross-OS) helper functions for reading/writing + * arrays of multiple objects or fixed-length strings from/to disk. + * + * TO DEVELOPERS: this is the preferrered method of I/O for anything implemented. + * In addition, Doomfiles can be passed to objects implementing the IReadableDoomObject + * and IWritableDoomObject interfaces, which will "autoread" or "autowrite" themselves + * to the implied stream. + * + * TODO: in the light of greater future portabililty and compatibility in certain + * environments, PERHAPS this should have been implemented using Streams. Perhaps + * it's possible to change the underlying implementation without (?) changing too + * much of the exposed interface, but it's not a priority for me right now. + * + */ +public class DoomIO { + + private static final Logger LOGGER = Loggers.getLogger(DoomIO.class.getName()); + + private DoomIO() { + + } + + /** Writes a Vector to a RandomAccessFile. */ + public static void writeVector(DataOutputStream dos, float v[]) throws IOException { + for (int n = 0; n < 3; n++) { + dos.writeFloat(v[n]); + } + } + + /** Writes a Vector to a RandomAccessFile. */ + public static float[] readVector(DataInputStream dis) throws IOException { + float res[] = {0, 0, 0}; + for (int n = 0; n < 3; n++) { + res[n] = dis.readFloat(); + } + + return res; + } + + /** Reads a length specified string from a file. */ + public static final String readString(DataInputStream dis) throws IOException { + int len = dis.readInt(); + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + dis.read(bb, 0, len); + + return new String(bb, 0, len, Charset.forName("ISO-8859-1")); + } + + /** MAES: Reads a specified number of bytes from a file into a new String. + * With many lengths being implicit, we need to actually take the loader by the hand. + * + * @param len + * @return + * @throws IOException + */ + public final static String readString(DataInputStream dis, int len) throws IOException { + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + dis.read(bb, 0, len); + + return new String(bb, 0, len); + } + + public static String readString(InputStream f, int len) throws IOException { + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + + f.read(bb, 0, len); + + return new String(bb, 0, len, Charset.forName("ISO-8859-1")); + } + + /** MAES: Reads a specified number of bytes from a file into a new, NULL TERMINATED String. + * With many lengths being implicit, we need to actually take the loader by the hand. + * + * @param len + * @return + * @throws IOException + */ + public static final String readNullTerminatedString(InputStream dis, int len) throws IOException { + + if (len == -1) { + return null; + } + + if (len == 0) { + return ""; + } + + byte bb[] = new byte[len]; + int terminator = len; + + dis.read(bb, 0, len); + + for (int i = 0; i < bb.length; i++) { + if (bb[i] == 0) { + terminator = i; + break; // stop on first null + } + + } + + // This is the One True Encoding for Doom. + return new String(bb, 0, terminator, Charset.forName("ISO-8859-1")); + } + + /** MAES: Reads multiple strings with a specified number of bytes from a file. + * If the array is not large enough, only partial reads will occur. + * + * @param len + * @return + * @throws IOException + */ + public static final String[] readMultipleFixedLengthStrings(DataInputStream dis, String[] dest, int num, int len) throws IOException { + + // Some sanity checks... + if (num <= 0 || len < 0) { + return null; + } + + if (len == 0) { + for (int i = 0; i < dest.length; i++) { + dest[i] = new String(""); + } + return dest; + } + + for (int i = 0; i < num; i++) { + dest[i] = readString(dis, len); + } + return dest; + } + + /** Writes a length specified string (Pascal style) to a file. + * + * */ + public static void writeString(DataOutputStream dos, String s) { + try { + if (s == null) { + dos.writeInt(-1); + return; + } + + dos.writeInt(s.length()); + if (s.length() != 0) { + dos.writeBytes(s); + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, String.format("writeString %s to DoomFile failed!", s)); + } + } + + /** Writes a String with a specified len to a file. + * This is useful for fixed-size String fields in + * files. Any leftover space will be filled with 0x00s. + * + * @param s + * @param len + * @throws IOException + */ + public static void writeString(DataOutputStream dos, String s, int len) throws IOException { + + if (s == null) { + return; + } + + if (s.length() != 0) { + byte[] dest = s.getBytes("ISO-8859-1"); + dos.write(dest, 0, Math.min(len, dest.length)); + // Fill in with 0s if something's left. + if (dest.length < len) { + for (int i = 0; i < len - dest.length; i++) { + dos.write((byte) 0x00); + } + } + } + } + + public static void readObjectArray(DataInputStream dis, IReadableDoomObject[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i].read(dis); + } + } + + public static void readObjectArrayWithReflection(DataInputStream dis, IReadableDoomObject[] s, int len) throws Exception { + + if (len == 0) { + return; + } + Class c = s.getClass().getComponentType(); + + for (int i = 0; i < Math.min(len, s.length); i++) { + if (s[i] == null) { + s[i] = (IReadableDoomObject) c.getDeclaredConstructor().newInstance(); + } + s[i].read(dis); + } + } + + public static void readObjectArray(DataInputStream dis, IReadableDoomObject[] s, int len, Class c) throws Exception { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + if (s[i] == null) { + s[i] = (IReadableDoomObject) c.getDeclaredConstructor().newInstance(); + } + s[i].read(dis); + } + } + + public static final void readIntArray(DataInputStream dis, int[] s, int len, ByteOrder bo) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = dis.readInt(); + if (bo == ByteOrder.LITTLE_ENDIAN) { + s[i] = Swap.LONG(s[i]); + } + } + } + + public static final void readShortArray(DataInputStream dis, short[] s, int len, ByteOrder bo) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = dis.readShort(); + if (bo == ByteOrder.LITTLE_ENDIAN) { + s[i] = Swap.SHORT(s[i]); + } + } + } + + public static final void readIntArray(DataInputStream dis, int[] s, ByteOrder bo) throws IOException { + readIntArray(dis, s, s.length, bo); + } + + public static final void readShortArray(DataInputStream dis, short[] s, ByteOrder bo) throws IOException { + readShortArray(dis, s, s.length, bo); + } + + public static void readBooleanArray(DataInputStream dis, boolean[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = dis.readBoolean(); + } + } + + /** Reads an array of "int booleans" into an array or + * proper booleans. 4 bytes per boolean are used! + * + * @param s + * @param len + * @throws IOException + */ + public final static void readBooleanIntArray(DataInputStream dis, boolean[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i] = readIntBoolean(dis); + } + } + + public static final void readBooleanIntArray(DataInputStream dis, boolean[] s) throws IOException { + readBooleanIntArray(dis, s, s.length); + } + + public static final void writeBoolean(DataOutputStream dos, boolean[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + dos.writeBoolean(s[i]); + } + } + + public static final void writeObjectArray(DataOutputStream dos, IWritableDoomObject[] s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.length); i++) { + s[i].write(dos); + } + } + + public static final void writeListOfObjects(DataOutputStream dos, List s, int len) throws IOException { + + if ((s == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, s.size()); i++) { + s.get(i).write(dos); + } + } + + public final static void readBooleanArray(DataInputStream dis, boolean[] s) throws IOException { + readBooleanArray(dis, s, s.length); + } + + public final static void readIntBooleanArray(DataInputStream dis, boolean[] s) throws IOException { + readBooleanIntArray(dis, s, s.length); + } + + public static final void writeCharArray(DataOutputStream dos, char[] charr, int len) throws IOException { + + if ((charr == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, charr.length); i++) { + dos.writeChar(charr[i]); + } + } + + /** Will read an array of proper Unicode chars. + * + * @param charr + * @param len + * @throws IOException + */ + public static final void readCharArray(DataInputStream dis, char[] charr, int len) throws IOException { + + if ((charr == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, charr.length); i++) { + charr[i] = dis.readChar(); + } + } + + /** Will read a bunch of non-unicode chars into a char array. + * Useful when dealing with legacy text files. + * + * @param charr + * @param len + * @throws IOException + */ + public static final void readNonUnicodeCharArray(DataInputStream dis, char[] charr, int len) throws IOException { + + if ((charr == null) || (len == 0)) { + return; + } + + for (int i = 0; i < Math.min(len, charr.length); i++) { + charr[i] = (char) dis.readUnsignedByte(); + } + } + + /** Writes an item reference. + public void writeItem(gitem_t item) throws IOException { + if (item == null) + writeInt(-1); + else + writeInt(item.index); + } + */ + /** Reads the item index and returns the game item. + public gitem_t readItem() throws IOException { + int ndx = readInt(); + if (ndx == -1) + return null; + else + return GameItemList.itemlist[ndx]; + } + * @throws IOException + */ + public static final long readUnsignedLEInt(DataInputStream dis) throws IOException { + int tmp = dis.readInt(); + return 0xFFFFFFFFL & Swap.LONG(tmp); + } + + public static final int readLEInt(DataInputStream dis) throws IOException { + int tmp = dis.readInt(); + return Swap.LONG(tmp); + } + + public static final int readLEInt(InputStream dis) throws IOException { + int tmp = new DataInputStream(dis).readInt(); + return Swap.LONG(tmp); + } + + public static final void writeLEInt(DataOutputStream dos, int value) throws IOException { + dos.writeInt(Swap.LONG(value)); + } + +// 2-byte number + public static int SHORT_little_endian_TO_big_endian(int i) { + return ((i >> 8) & 0xff) + ((i << 8) & 0xff00); + } + + // 4-byte number + public static int INT_little_endian_TO_big_endian(int i) { + return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >> 8) + ((i >> 24) & 0xff); + } + + public static final short readLEShort(DataInputStream dis) throws IOException { + short tmp = dis.readShort(); + return Swap.SHORT(tmp); + } + + /** Reads a "big boolean" using 4 bytes. + * + * @return + * @throws IOException + */ + public static final boolean readIntBoolean(DataInputStream dis) throws IOException { + return (dis.readInt() != 0); + + } + +} \ No newline at end of file diff --git a/doom/src/w/IPackableDoomObject.java b/doom/src/w/IPackableDoomObject.java new file mode 100644 index 0000000..c0cb36b --- /dev/null +++ b/doom/src/w/IPackableDoomObject.java @@ -0,0 +1,9 @@ +package w; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public interface IPackableDoomObject { + + public void pack(ByteBuffer buf) throws IOException; +} \ No newline at end of file diff --git a/doom/src/w/IReadWriteDoomObject.java b/doom/src/w/IReadWriteDoomObject.java new file mode 100644 index 0000000..0399aa4 --- /dev/null +++ b/doom/src/w/IReadWriteDoomObject.java @@ -0,0 +1,5 @@ +package w; + +public interface IReadWriteDoomObject extends IReadableDoomObject, IWritableDoomObject { + +} \ No newline at end of file diff --git a/doom/src/w/IReadableDoomObject.java b/doom/src/w/IReadableDoomObject.java new file mode 100644 index 0000000..f2a7bed --- /dev/null +++ b/doom/src/w/IReadableDoomObject.java @@ -0,0 +1,17 @@ +package w; + +import java.io.DataInputStream; +import java.io.IOException; + +/** This is an interface implemented by objects that must be read form disk. + * Every object is supposed to do its own umarshalling. This way, + * structured and hierchical reads are possible. Another superior innovation + * of Mocha Doom ;-) Err....ok :-p + * + * @author Velktron + * + */ +public interface IReadableDoomObject { + + public void read(DataInputStream f) throws IOException; +} \ No newline at end of file diff --git a/doom/src/w/IWadLoader.java b/doom/src/w/IWadLoader.java new file mode 100644 index 0000000..1e39165 --- /dev/null +++ b/doom/src/w/IWadLoader.java @@ -0,0 +1,350 @@ +package w; + +import data.Defines; +import doom.SourceCode.W_Wad; +import static doom.SourceCode.W_Wad.W_CacheLumpName; +import static doom.SourceCode.W_Wad.W_CacheLumpNum; +import static doom.SourceCode.W_Wad.W_CheckNumForName; +import static doom.SourceCode.W_Wad.W_GetNumForName; +import static doom.SourceCode.W_Wad.W_InitMultipleFiles; +import static doom.SourceCode.W_Wad.W_LumpLength; +import static doom.SourceCode.W_Wad.W_ReadLump; +import static doom.SourceCode.W_Wad.W_Reload; +import java.nio.ByteBuffer; +import java.util.function.IntFunction; +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; +import rr.patch_t; +import utils.GenericCopy.ArraySupplier; +import v.graphics.Lights; +import static v.graphics.Palettes.PAL_NUM_COLORS; +import static v.graphics.Palettes.PAL_NUM_STRIDES; +import v.tables.Playpal; + +public interface IWadLoader { + + static final Logger LOGGER = Loggers.getLogger(IWadLoader.class.getName()); + + /** + * W_Reload Flushes any of the reloadable lumps in memory and reloads the + * directory. + * + * @throws Exception + */ + @W_Wad.C(W_Reload) + public abstract void Reload() throws Exception; + + /** + * W_InitMultipleFiles + * + * Pass a null terminated list of files to use (actually + * a String[] array in Java). + * + * All files are optional, but at least one file + * must be found. + * + * Files with a .wad extension are idlink files + * with multiple lumps. + * + * Other files are single lumps with the base filename + * for the lump name. + * + * Lump names can appear multiple times. + * The name searcher looks backwards, so a later file + * does override all earlier ones. + * + * @param filenames + * + */ + @W_Wad.C(W_InitMultipleFiles) + public abstract void InitMultipleFiles(String[] filenames) throws Exception; + + /** + * W_InitFile + * + * Just initialize from a single file. + * + * @param filename + * + */ + public abstract void InitFile(String filename) throws Exception; + + /** + * W_NumLumps + * + * Returns the total number of lumps loaded in this Wad manager. Awesome. + * + */ + public abstract int NumLumps(); + + /** + * Returns actual lumpinfo_t object for a given name. Useful if you want to + * access something on a file, I guess? + * + * @param name + * @return + */ + public abstract lumpinfo_t GetLumpinfoForName(String name); + + /** + * W_GetNumForName + * Calls W_CheckNumForName, but bombs out if not found. + */ + @W_Wad.C(W_GetNumForName) + public abstract int GetNumForName(String name); + + /** + * + * @param lumpnum + * @return + */ + public abstract String GetNameForNum(int lumpnum); + + // + // W_LumpLength + // Returns the buffer size needed to load the given lump. + // + @W_Wad.C(W_LumpLength) + public abstract int LumpLength(int lump); + + /** + * W_CacheLumpNum Modified to read a lump as a specific type of + * CacheableDoomObject. If the class is not identified or is null, then a + * generic DoomBuffer object is left in the lump cache and returned. + * + * @param + */ + @W_Wad.C(W_CacheLumpNum) + public abstract T CacheLumpNum(int lump, int tag, Class what); + + /** + * Return a cached lump based on its name, as raw bytes, no matter what. + * It's rare, but has its uses. + * + * @param name + * @param tag + * @param what + * @return + */ + public abstract byte[] CacheLumpNameAsRawBytes(String name, int tag); + + /** + * Return a cached lump based on its num, as raw bytes, no matter what. + * It's rare, but has its uses. + * + * @param name + * @param tag + * @param what + * @return + */ + public abstract byte[] CacheLumpNumAsRawBytes(int num, int tag); + + /** + * Get a DoomBuffer of the specified lump name + * + * @param name + * @param tag + * @return + */ + @W_Wad.C(W_CacheLumpName) + public abstract DoomBuffer CacheLumpName(String name, int tag); + + /** + * Get a DoomBuffer of the specified lump num + * + * @param lump + * @return + */ + public abstract DoomBuffer CacheLumpNumAsDoomBuffer(int lump); + + /** + * Specific method for loading cached patches by name, since it's by FAR the + * most common operation. + * + * @param name + * @return + */ + public abstract patch_t CachePatchName(String name); + + /** + * Specific method for loading cached patches, since it's by FAR the most + * common operation. + * + * @param name + * @param tag + * @return + */ + public abstract patch_t CachePatchName(String name, int tag); + + /** + * Specific method for loading cached patches by number. + * + * @param num + * @return + */ + public abstract patch_t CachePatchNum(int num); + + @W_Wad.C(W_CacheLumpName) + public abstract T CacheLumpName(String name, int tag, Class what); + + /** + * A lump with size 0 is a marker. This means that it + * can/must be skipped, and if we want actual data we must + * read the next one. + * + * @param lump + * @return + */ + public abstract boolean isLumpMarker(int lump); + + public abstract String GetNameForLump(int lump); + + @W_Wad.C(W_CheckNumForName) + public abstract int CheckNumForName(String name/* , int namespace */); + + /** + * Return ALL possible results for a given name, in order to resolve name clashes without + * using namespaces + * + * @param name + * @return + */ + public abstract int[] CheckNumsForName(String name); + + public abstract lumpinfo_t GetLumpInfo(int i); + + /** + * A way to cleanly close open file handles still pointed at by lumps. + * Is also called upon finalize + */ + public void CloseAllHandles(); + + /** + * Null the disk lump associated with a particular object, + * if any. This will NOT induce a garbage collection, unless + * you also null any references you have to that object. + * + * @param lump + */ + void UnlockLumpNum(int lump); + + void UnlockLumpNum(CacheableDoomObject lump); + + public T[] CacheLumpNumIntoArray(int lump, int num, ArraySupplier what, IntFunction arrGen); + + /** + * Verify whether a certain lump number is valid and has + * the expected name. + * + * @param lump + * @param lumpname + * @return + */ + boolean verifyLumpName(int lump, String lumpname); + + /** + * The index of a known loaded wadfile + * + * @param wad1 + * @return + */ + public abstract int GetWadfileIndex(wadfile_info_t wad1); + + /** + * The number of loaded wadfile + * + * @return + */ + public abstract int GetNumWadfiles(); + + /** + * Force a lump (in memory) to be equal to a dictated content. Useful + * for when you are e.g. repairing palette lumps or doing other sanity + * checks. + * + * @param lump + * @param obj + */ + void InjectLumpNum(int lump, CacheableDoomObject obj); + + /** + * Read a lump into a bunch of bytes straight. No caching, no frills. + * + * @param lump + * @return + */ + @W_Wad.C(W_ReadLump) + byte[] ReadLump(int lump); + + /** + * Use your own buffer, of proper size of course. + * + * @param lump + * @param buf + */ + void ReadLump(int lump, byte[] buf); + + /** + * Use your own buffer, of proper size AND offset. + * + * @param lump + * @param buf + */ + void ReadLump(int lump, byte[] buf, int offset); + + /** + * Loads PLAYPAL from wad lump. Repairs if necessary. + * Also, performs sanity check on *repaired* PLAYPAL. + * + * @return byte[] of presumably 256 colors, 3 bytes each + */ + default byte[] LoadPlaypal() { + // Copy over the one you read from disk... + int pallump = GetNumForName("PLAYPAL"); + byte[] playpal = Playpal.properPlaypal(CacheLumpNumAsRawBytes(pallump, Defines.PU_STATIC)); + + final int minLength = PAL_NUM_COLORS * PAL_NUM_STRIDES; + if (playpal.length < minLength) { + throw new IllegalArgumentException(String.format( + "Invalid PLAYPAL: has %d entries instead of %d. Try -noplaypal mode", + playpal.length, minLength)); + } + + LOGGER.log(Level.FINE, String.format("VI_Init: set palettes: %d colors", playpal.length / PAL_NUM_STRIDES)); + + InjectLumpNum(pallump, new DoomBuffer(ByteBuffer.wrap(playpal))); + return playpal; + } + + /** + * Loads COLORMAP from wad lump. + * Performs sanity check on it. + * + * @return byte[][] of presumably 34 colormaps 256 entries each with an entry being index in PLAYPAL + */ + default byte[][] LoadColormap() { + // Load in the light tables, + // 256 byte align tables. + final int lump = GetNumForName("COLORMAP"); + final int length = LumpLength(lump) + PAL_NUM_COLORS; + final byte[][] colormap = new byte[(length / PAL_NUM_COLORS)][PAL_NUM_COLORS]; + final int minLength = Lights.COLORMAP_STD_LENGTH_15; + if (colormap.length < minLength) { + throw new IllegalArgumentException(String.format( + "Invalid COLORMAP: has %d entries, minimum is %d. Try -nocolormap mode", + colormap.length, minLength)); + } + + LOGGER.log(Level.FINE, String.format("VI_Init: set colormaps: %d", colormap.length)); + + final byte[] tmp = new byte[length]; + ReadLump(lump, tmp); + + for (int i = 0; i < colormap.length; ++i) { + System.arraycopy(tmp, i * PAL_NUM_COLORS, colormap[i], 0, PAL_NUM_COLORS); + } + + return colormap; + } +} \ No newline at end of file diff --git a/doom/src/w/IWritableDoomObject.java b/doom/src/w/IWritableDoomObject.java new file mode 100644 index 0000000..c25b36c --- /dev/null +++ b/doom/src/w/IWritableDoomObject.java @@ -0,0 +1,9 @@ +package w; + +import java.io.DataOutputStream; +import java.io.IOException; + +public interface IWritableDoomObject { + + public void write(DataOutputStream dos) throws IOException; +} \ No newline at end of file diff --git a/doom/src/w/InputStreamSugar.java b/doom/src/w/InputStreamSugar.java new file mode 100644 index 0000000..c1b7fda --- /dev/null +++ b/doom/src/w/InputStreamSugar.java @@ -0,0 +1,293 @@ +package w; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import utils.C2JUtils; + +/** + * As we know, Java can be a bit awkward when handling streams e.g. you can't + * really skip at will without doing some nasty crud. This class helps doing + * such crud. E.g. if we are dealing with a stream that has an underlying file, + * we can try and skip directly by using the file channel, otherwise we can try + * (eww) closing the stream, reopening it (ASSUMING WE KNOW THE SOURCE'S URI AND + * TYPE), and then skipping. + * + * @author Maes + */ +public class InputStreamSugar { + + public static final int UNKNOWN_TYPE = 0x0; + + public static final int FILE = 0x1; // Local file. Easiest case + + public static final int NETWORK_FILE = 0x2; + + public static final int ZIP_FILE = 0x4; // Zipped file + + public static final int BAD_URI = -1; // Bad or unparseable + + /** + * Creates an inputstream from a local file, network resource, or zipped + * file (also over a network). If an entry name is specifid AND the type is + * specified to be zip, then a zipentry with that name will be sought. + * + * @param resource + * @param contained + * @param type + * @return + */ + public static final InputStream createInputStreamFromURI(String resource, + ZipEntry entry, int type) { + + InputStream is = null; + URL u; + + // No entry specified or no zip type, try everything BUT zip. + if (entry == null || !C2JUtils.flags(type, ZIP_FILE)) { + is = getDirectInputStream(resource); + } else { + // Entry specified AND type specified to be zip + // We might want to open even a zip file without looking + // for any particular entry. + if (entry != null && C2JUtils.flags(type, ZIP_FILE)) { + + ZipInputStream zis; + // Try it as a NET zip file + try { + u = new URI(resource).toURL(); + zis = new ZipInputStream(u.openStream()); + } catch (Exception e) { + // Local zip file? + try { + // Open resource as local file-backed zip input stream, + // and search proper entry. + zis = new ZipInputStream(new FileInputStream(resource)); + } catch (Exception e1) { + // Well, it's not that either. + // At this point we almost ran out of options + // Try a local file and that's it. + is = getDirectInputStream(resource); + return is; + } + } + + // All OK? + is = getZipEntryStream(zis, entry.getName()); + if (is != null) { + return is; + } + } + } + + // At this point, you'll either get a stream or jack. + return getDirectInputStream(resource); + } + + /** Match zip entries in a ZipInputStream based only on their name. + * Luckily (?) ZipEntries do not keep references to their originating + * streams, so opening/closing ZipInputStreams all the time won't result + * in a garbage hell...I hope. + * + * @param zis + * @param entryname + * @return + */ + private static InputStream getZipEntryStream(ZipInputStream zis, String entryname) { + + ZipEntry ze = null; + try { + while ((ze = zis.getNextEntry()) != null) { + // Directories cannot be opened + if (ze.isDirectory()) { + continue; + } + + if (ze.getName().equals(entryname)) { + return zis; + } + } + } catch (IOException e) { + // Get jack + return null; + } + + // Get jack + return null; + } + + private final static InputStream getDirectInputStream(String resource) { + InputStream is = null; + URL u; + + try { // Is it a net resource? + u = new URI(resource).toURL(); + is = u.openStream(); + } catch (Exception e) { + // OK, not a valid URL or no network. We don't care. + // Try opening as a local file. + try { + is = new FileInputStream(resource); + } catch (FileNotFoundException e1) { + // Well, it's not that either. + // At this point we really ran out of options + // and you'll get null + } + } + + return is; + } + + /** + * Attempt to do the Holy Grail of Java Streams, aka seek to a particular + * position. With some types of stream, this is possible if you poke deep + * enough. With others, it's not, and you can only close & reopen them + * (provided you know how to do that) and then skip to a particular position + * + * @param is + * @param pos + * The desired position + * @param uri + * Information which can help reopen a stream, e.g. a filename, URL, + * or zip file. + * @peram entry If we must look into a zipfile entry + * @return the skipped stream. Might be a totally different object. + * @throws IOException + */ + public static final InputStream streamSeek(InputStream is, long pos, + long size, String uri, ZipEntry entry, int type) + throws IOException { + if (is == null) { + return is; + } + + // If we know our actual position in the stream, we can aid seeking + // forward + + /* + * Too buggy :-/ pity if (knownpos>=0 && knownpos<=pos){ if + * (pos==knownpos) return is; try{ final long mustskip=pos-knownpos; + * long skipped=0; while (skipped 0) { + try { + long available = is.available(); + long guesspos = size - available; + // The stream is at a position before or equal to + // our desired one. We can attempt skipping forward. + if (guesspos > 0 && guesspos <= pos) { + long skipped = 0; + long mustskip = pos - guesspos; + // Repeat skipping until proper amount reached + while (skipped < mustskip) { + skipped += is.skip(mustskip - skipped); + } + return is; + } + } catch (Exception e) { + // We couldn't skip cleanly. Swallow up and try normally. + } + } + + // Cast succeeded + if (is instanceof FileInputStream) { + try { + ((FileInputStream) is).getChannel().position(pos); + return is; + } catch (IOException e) { + // Ouch. Do a dumb close & reopening. + is.close(); + is = createInputStreamFromURI(uri, null, 1); + is.skip(pos); + return is; + } + } + + // Cast succeeded + if (is instanceof ZipInputStream) { + // ZipInputStreams are VERY dumb. so... + is.close(); + is = createInputStreamFromURI(uri, entry, type); + is.skip(pos); + return is; + + } + + try { // Is it a net resource? We have to reopen it :-/ + // long a=System.nanoTime(); + URL u = new URI(uri).toURL(); + InputStream nis = u.openStream(); + nis.skip(pos); + is.close(); + // long b=System.nanoTime(); + // System.out.printf("Network stream seeked WITH closing %d\n",(b-a)/1000); + return nis; + } catch (Exception e) { + + } + + // TODO: zip handling? + return is; + } + + public static List getAllEntries(ZipInputStream zis) + throws IOException { + ArrayList zes = new ArrayList<>(); + + ZipEntry z; + + while ((z = zis.getNextEntry()) != null) { + zes.add(z); + } + + return zes; + } + + /** Attempts to return a stream size estimate. Only guaranteed to work 100% + * for streams representing local files, and zips (if you have the entry). + * + * @param is + * @param z + * @return + */ + public static long getSizeEstimate(InputStream is, ZipEntry z) { + if (is instanceof FileInputStream) { + try { + return ((FileInputStream) is).getChannel().size(); + } catch (IOException e) { + + } + } + + if (is instanceof FileInputStream) { + if (z != null) { + return z.getSize(); + } + } + + // Last ditch + try { + return is.available(); + } catch (IOException e) { + try { + return is.available(); + } catch (IOException e1) { + return -1; + } + } + } + +} \ No newline at end of file diff --git a/doom/src/w/JadDecompress.java b/doom/src/w/JadDecompress.java new file mode 100644 index 0000000..4ef1c5c --- /dev/null +++ b/doom/src/w/JadDecompress.java @@ -0,0 +1,64 @@ +package w; + +import java.util.logging.Level; +import java.util.logging.Logger; +import mochadoom.Loggers; + +public class JadDecompress { + + private static final Logger LOGGER = Loggers.getLogger(JadDecompress.class.getName()); + + public final static int WINDOW_SIZE = 4096; + + public final static int LOOKAHEAD_SIZE = 16; + + public final static int LENSHIFT = 4; + + /* this must be log2(LOOKAHEAD_SIZE) */ + public static void decode(byte[] input, byte[] output) { + /* + * #ifdef JAGUAR decomp_input = input; decomp_output = output; + * gpufinished = zero; gpucodestart = (int)&decomp_start; while + * (!I_RefreshCompleted () ) ; #else + */ + int getidbyte = 0; + int len; + int pos; + int i; + int source_ptr, input_ptr = 0, output_ptr = 0; + int idbyte = 0; + + while (true) { + + /* get a new idbyte if necessary */ + if (getidbyte == 0) { + idbyte = 0xFF & input[input_ptr++]; + } + getidbyte = (getidbyte + 1) & 7; + + if ((idbyte & 1) != 0) { + /* decompress */ + pos = (0xFF & input[input_ptr++]) << LENSHIFT; + pos = pos | ((0xFF & input[input_ptr]) >> LENSHIFT); + source_ptr = output_ptr - pos - 1; + + len = ((0xFF & input[input_ptr++]) & 0xf) + 1; + + if (len == 1) { + break; + } + for (i = 0; i < len; i++) { + output[output_ptr++] = output[source_ptr++]; + } + } else { + output[output_ptr++] = input[input_ptr++]; + } + + idbyte = idbyte >> 1; + + } + + LOGGER.log(Level.INFO, String.format("Expanded %d to %d", input_ptr, output_ptr)); + } + +} \ No newline at end of file diff --git a/doom/src/w/WadLoader.java b/doom/src/w/WadLoader.java new file mode 100644 index 0000000..60e9678 --- /dev/null +++ b/doom/src/w/WadLoader.java @@ -0,0 +1,1418 @@ +// Emacs style mode select -*- Java -*- +// ----------------------------------------------------------------------------- +// +// $Id: WadLoader.java,v 1.64 2014/03/28 00:55:32 velktron Exp $ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Handles WAD file header, directory, lump I/O. +// +// ----------------------------------------------------------------------------- +package w; + +import static data.Defines.PU_CACHE; +import doom.SourceCode; +import doom.SourceCode.W_Wad; +import static doom.SourceCode.W_Wad.W_CacheLumpName; +import static doom.SourceCode.W_Wad.W_CheckNumForName; +import i.DummySystem; +import i.IDoomSystem; +import i.Strings; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.IntFunction; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import mochadoom.Loggers; +import rr.patch_t; +import utils.C2JUtils; +import utils.GenericCopy.ArraySupplier; +import static utils.GenericCopy.malloc; + +public class WadLoader implements IWadLoader { + + private static final Logger LOGGER = Loggers.getLogger(WadLoader.class.getName()); + + protected IDoomSystem I; + + ///// CONSTRUCTOR + public WadLoader(IDoomSystem I) { + this(); + this.I = I; + } + + public WadLoader() { + lumpinfo = new lumpinfo_t[0]; + zone = new HashMap<>(); + wadfiles = new ArrayList<>(); + this.I = new DummySystem(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + CloseAllHandles(); + })); + } + + //// FIELDS + /** Location of each lump on disk. */ + public lumpinfo_t[] lumpinfo; + + public int numlumps; + + /** + * MAES: probably array of byte[]??? void** lumpcache; + * + * Actually, loaded objects will be deserialized here as the general type + * "CacheableDoomObject" (in the worst case they will be byte[] or + * ByteBuffer). + * + * Not to brag, but this system is FAR superior to the inline unmarshaling + * used in other projects ;-) + */ + private CacheableDoomObject[] lumpcache; + + private boolean[] preloaded; + + /** Added for Boom compliance */ + private List wadfiles; + + /** + * #define strcmpi strcasecmp MAES: this is just capitalization. However we + * can't manipulate String object in Java directly like this, so this must + * be a return type. + * + * TODO: maybe move this in utils? + */ + public String strupr(String s) { + return s.toUpperCase(); + } + + /* ditto */ + public void strupr(char[] s) { + for (int i = 0; i < s.length; i++) { + s[i] = Character.toUpperCase(s[i]); + } + } + + // + // LUMP BASED ROUTINES. + // + // + // W_AddFile + // All files are optional, but at least one file must be + // found (PWAD, if all required lumps are present). + // Files with a .wad extension are wadlink files + // with multiple lumps. + // Other files are single lumps with the base filename + // for the lump name. + // + // If filename starts with a tilde, the file is handled + // specially to allow map reloads. + // But: the reload feature is a fragile hack... + int reloadlump; + + // MAES: was char* + String reloadname; + + /** + * This is where lumps are actually read + loaded from a file. + * + * @param filename + * @throws Exception + */ + private void AddFile(String uri, ZipEntry entry, int type) throws Exception { + wadinfo_t header = new wadinfo_t(); + int lump_p; // MAES: was lumpinfo_t* , but we can use it as an array + // pointer. + InputStream handle, storehandle; + long length; + int startlump; + + filelump_t[] fileinfo = new filelump_t[1]; // MAES: was * + filelump_t singleinfo = new filelump_t(); + + // handle reload indicator. + if (uri.charAt(0) == '~') { + uri = uri.substring(1); + reloadname = uri; + reloadlump = numlumps; + } + + // open the resource and add to directory + // It can be any streamed type handled by the "sugar" utilities. + try { + handle = InputStreamSugar.createInputStreamFromURI(uri, entry, type); + } catch (Exception e) { + I.Error(" couldn't open resource %s \n", uri); + return; + } + + // Create and set wadfile info + wadfile_info_t wadinfo = new wadfile_info_t(); + wadinfo.handle = handle; + wadinfo.name = uri; + wadinfo.entry = entry; + wadinfo.type = type; + + // System.out.println(" adding " + filename + "\n"); + // We start at the number of lumps. This allows appending stuff. + startlump = this.numlumps; + + String checkname = (wadinfo.entry != null ? wadinfo.entry.getName() : uri); + // If not "WAD" then we check for single lumps. + if (!C2JUtils.checkForExtension(checkname, "wad")) { + + fileinfo[0] = singleinfo; + singleinfo.filepos = 0; + singleinfo.size = InputStreamSugar.getSizeEstimate(handle, wadinfo.entry); + + // Single lumps. Only use 8 characters + singleinfo.actualname = singleinfo.name = C2JUtils.removeExtension(uri).toUpperCase(); + + // MAES: check out certain known types of extension + if (C2JUtils.checkForExtension(uri, "lmp")) { + wadinfo.src = wad_source_t.source_lmp; + } else if (C2JUtils.checkForExtension(uri, "deh")) { + wadinfo.src = wad_source_t.source_deh; + } else if (C2JUtils.checkForExtension(uri, null)) { + wadinfo.src = wad_source_t.source_deh; + } + + numlumps++; + + } else { + // MAES: 14/06/10 this is historical, for this is the first time I + // implement reading something from RAF into Doom's structs. + // Kudos to the JAKE2 team who solved this problem before me. + // MAES: 25/10/11: In retrospect, this solution, while functional, was + // inelegant and limited. + + DataInputStream dis = new DataInputStream(handle); + + // Read header in one go. Usually doesn't cause trouble? + header.read(dis); + + if (header.identification.compareTo("IWAD") != 0) { + // Homebrew levels? + if (header.identification.compareTo("PWAD") != 0) { + I.Error("Wad file %s doesn't have IWAD or PWAD id\n", checkname); + } else { + wadinfo.src = wad_source_t.source_pwad; + } + + // modifiedgame = true; + } else { + wadinfo.src = wad_source_t.source_iwad; + } + + length = header.numlumps; + // Init everything: + fileinfo = malloc(filelump_t::new, filelump_t[]::new, (int) length); + + dis.close(); + + handle = InputStreamSugar.streamSeek(handle, header.infotableofs, wadinfo.maxsize, uri, entry, type); + + // FIX: sometimes reading from zip files doesn't work well, so we pre-cache the TOC + byte[] TOC = new byte[(int) (length * filelump_t.sizeof())]; + + int read = 0; + while (read < TOC.length) { + // Make sure we have all of the TOC, sometimes ZipInputStream "misses" bytes. + // when wrapped. + read += handle.read(TOC, read, TOC.length - read); + } + + ByteArrayInputStream bais = new ByteArrayInputStream(TOC); + + // MAES: we can't read raw structs here, and even less BLOCKS of + // structs. + dis = new DataInputStream(bais); + DoomIO.readObjectArray(dis, fileinfo, (int) length); + + numlumps += header.numlumps; + wadinfo.maxsize = estimateWadSize(header, lumpinfo); + + } // end loading wad + + // At this point, a WADFILE or LUMPFILE been successfully loaded, + // and so is added to the list + this.wadfiles.add(wadinfo); + + // Fill in lumpinfo + // MAES: this was a realloc(lumpinfo, numlumps*sizeof(lumpinfo_t)), + // so we have to increase size and copy over. Maybe this should be + // an ArrayList? + int oldsize = lumpinfo.length; + lumpinfo_t[] newlumpinfo = malloc(lumpinfo_t::new, lumpinfo_t[]::new, numlumps); + + try { + System.arraycopy(lumpinfo, 0, newlumpinfo, 0, oldsize); + } catch (Exception e) { + // if (!lumpinfo) + I.Error("Couldn't realloc lumpinfo"); + } + + // Bye bye, old lumpinfo! + lumpinfo = newlumpinfo; + + // MAES: lum_p was an alias for lumpinfo[startlump]. I know it's a + // bit crude as an approximation but heh... + lump_p = startlump; + + // MAES: if reloadname is null, handle is stored...else an invalid + // handle? + storehandle = (reloadname != null) ? null : handle; + + // This iterates through single files. + int fileinfo_p = 0; + + for (int i = startlump; i < numlumps; i++, lump_p++, fileinfo_p++) { + lumpinfo[lump_p].handle = storehandle; + lumpinfo[lump_p].position = fileinfo[fileinfo_p].filepos; + lumpinfo[lump_p].size = fileinfo[fileinfo_p].size; + // Make all lump names uppercase. Searches should also be uppercase only. + lumpinfo[lump_p].name = fileinfo[fileinfo_p].name.toUpperCase(); + lumpinfo[lump_p].hash = lumpinfo[lump_p].name.hashCode(); + // lumpinfo[lump_p].stringhash = name8.getLongHash(strupr(lumpinfo[lump_p].name)); + // LumpNameHash(lumpinfo[lump_p].name); + lumpinfo[lump_p].intname = name8.getIntName(strupr(lumpinfo[lump_p].name)); + //System.out.println(lumpinfo[lump_p]); + lumpinfo[lump_p].wadfile = wadinfo; // MAES: Add Boom provenience info + } + + if (reloadname != null) { + handle.close(); + } + } + + /** Try to guess a realistic wad size limit based only on the number of lumps and their + * STATED contents, in case it's not possible to get an accurate stream size otherwise. + * Of course, they may be way off with deliberately malformed files etc. + * + * @param header + * @param lumpinfo2 + * @return + */ + private long estimateWadSize(wadinfo_t header, lumpinfo_t[] lumpinfo) { + + long maxsize = header.infotableofs + header.numlumps * 16; + + for (int i = 0; i < lumpinfo.length; i++) { + if ((lumpinfo[i].position + lumpinfo[i].size) > maxsize) { + maxsize = lumpinfo[i].position + lumpinfo[i].size; + } + } + + return maxsize; + } + + /* (non-Javadoc) + * @see w.IWadLoader#Reload() + */ + @Override + @SuppressWarnings("null") + public void Reload() throws Exception { + wadinfo_t header = new wadinfo_t(); + int lumpcount; + int lump_p; // Maes: same as in W_WADload + int i; + DataInputStream handle = null; + int length; + filelump_t[] fileinfo; + + if (reloadname == null) { + return; + } + + try { + handle = new DataInputStream(new BufferedInputStream(new FileInputStream(reloadname))); + } catch (Exception e) { + I.Error("W_Reload: couldn't open %s", reloadname); + } + + header.read(handle); + // Actual number of lumps in file... + lumpcount = (int) header.numlumps; + header.infotableofs = header.infotableofs; + length = lumpcount; + fileinfo = new filelump_t[length]; + + handle.reset(); + handle.skip(header.infotableofs); + + // MAES: we can't read raw structs here, and even less BLOCKS of + // structs. + DoomIO.readObjectArrayWithReflection(handle, fileinfo, length); + + /* + * for (int j=0;j zes = InputStreamSugar.getAllEntries(zip); + zip.close(); + for (ZipEntry zz : zes) { + // The name of a zip file will be used as an identifier + if (!zz.isDirectory()) { + this.AddFile(s, zz, type); + } + } + } + + @Override + public void InitFile(String filename) throws Exception { + String[] names = new String[1]; + + names[0] = filename; + // names[1] = null; + InitMultipleFiles(names); + } + + @Override + public final int NumLumps() { + return numlumps; + } + + /** + * W_CheckNumForName2 Returns -1 if name not found. + * + * A slightly better implementation, uses string hashes + * as direct comparators (though 64-bit long descriptors + * could be used). It's faster than the old method, but + * still short from the HashMap's performance by + * an order of magnitude. + * + * @param name + * @return + * + * UNUSED + + public int CheckNumForName2(String name) { + + // scan backwards so patch lump files take precedence + int lump_p = numlumps; + + // make the name into two integers for easy compares + // case insensitive + + long hash = name8.getLongHash(name); + // System.out.print("Looking for "+name + " with hash " + // +Long.toHexString(hash)); + while (lump_p-- != 0) + if (lumpinfo[lump_p].stringhash == hash) { + // System.out.print(" found "+lumpinfo[lump_p]+"\n" ); + return lump_p; + } + + // TFB. Not found. + return -1; + } */ + /** + * Old, shitty method for CheckNumForName. It's an overly literal + * translation of how the C original worked, which was none too good + * even without the overhead of converting a string to + * its integer representation. It's so bad, that it's two orders + * of magnitude slower than a HashMap implemetation, and one from + * a direct hash/longname comparison with linear search. + * + * @param name + * @return + * + + public int CheckNumForName3(String name) { + + int v1; + int v2; + // lumpinfo_t lump_p; + + int lump_p; + // make the name into two integers for easy compares + // case insensitive + name8 union = new name8(strupr(name)); + + v1 = union.x[0]; + v2 = union.x[1]; + + // scan backwards so patch lump files take precedence + lump_p = numlumps; + + while (lump_p-- != 0) { + int a = name8.stringToInt(lumpinfo[lump_p].name, 0); + int b = name8.stringToInt(lumpinfo[lump_p].name, 4); + if ((a == v1) && (b == v2)) { + return lump_p; + } + } + + // TFB. Not found. + return -1; + } */ + + @Override + public lumpinfo_t GetLumpinfoForName(String name) { + + int v1; + int v2; + // lumpinfo_t lump_p; + + int lump_p; + // make the name into two integers for easy compares + // case insensitive + name8 union = new name8(strupr(name)); + + v1 = union.x[0]; + v2 = union.x[1]; + + // scan backwards so patch lump files take precedence + lump_p = numlumps; + + while (lump_p-- != 0) { + int a = name8.stringToInt(lumpinfo[lump_p].name, 0); + int b = name8.stringToInt(lumpinfo[lump_p].name, 4); + if ((a == v1) && (b == v2)) { + return lumpinfo[lump_p]; + } + } + + // TFB. Not found. + return null; + } + + @Override + public int GetNumForName(String name) { + int i; + + i = CheckNumForName(name.toUpperCase()); + + if (i == -1) { + I.Error("W_GetNumForName: %s not found! hash: %s", name, Long.toHexString(name8.getLongHash(name))); + } + + return i; + } + + @Override + public String GetNameForNum(int lumpnum) { + if (lumpnum >= 0 && lumpnum < this.numlumps) { + return this.lumpinfo[lumpnum].name; + } + return null; + } + + // + // W_LumpLength + // Returns the buffer size needed to load the given lump. + // + /* (non-Javadoc) + * @see w.IWadLoader#LumpLength(int) + */ + @Override + public int LumpLength(int lump) { + if (lump >= numlumps) { + I.Error("W_LumpLength: %d >= numlumps", lump); + } + + return (int) lumpinfo[lump].size; + } + + @Override + public final byte[] ReadLump(int lump) { + lumpinfo_t l = lumpinfo[lump]; + byte[] buf = new byte[(int) l.size]; + ReadLump(lump, buf, 0); + return buf; + + } + + @Override + public final void ReadLump(int lump, byte[] buf) { + ReadLump(lump, buf, 0); + } + + /** + * W_ReadLump Loads the lump into the given buffer, which must be >= + * W_LumpLength(). SKIPS CACHING + * + * @throws IOException + */ + @Override + public final void ReadLump(int lump, byte[] buf, int offset) { + int c = 0; + lumpinfo_t l; + InputStream handle = null; + + if (lump >= this.numlumps) { + I.Error("W_ReadLump: %d >= numlumps", lump); + return; + } + + l = lumpinfo[lump]; + + if (l.handle == null) { + // reloadable file, so use open / read / close + try { + // FIXME: reloadable files can only be that. Files. + handle = InputStreamSugar.createInputStreamFromURI(this.reloadname, null, 0); + } catch (Exception e) { + I.Error("W_ReadLump: couldn't open %s, due to: %s", reloadname, e.getMessage()); + } + } else { + handle = l.handle; + } + + try { + + handle = InputStreamSugar.streamSeek(handle, l.position, + l.wadfile.maxsize, l.wadfile.name, l.wadfile.entry, l.wadfile.type); + + // read buffered. Unfortunately that interferes badly with + // guesstimating the actual stream position. + BufferedInputStream bis = new BufferedInputStream(handle, 8192); + + while (c < l.size) { + c += bis.read(buf, offset + c, (int) (l.size - c)); + } + + // Well, that's a no-brainer. + //l.wadfile.knownpos=l.position+c; + if (c < l.size) { + LOGGER.log(Level.SEVERE, String.format("W_ReadLump: only read %d of %d on lump %d %d", c, l.size, + lump, l.position)); + } + + if (l.handle == null) { + handle.close(); + } else { + l.handle = handle; + } + + I.BeginRead(); + // ??? I_EndRead (); + + } catch (Exception e) { + I.Error("W_ReadLump: could not read lump %d, due to: %s", lump, e.getMessage()); + } + + } + + /** + * The most basic of the Wadloader functions. Will attempt to read a lump + * off disk, based on the specific class type (it will call the unpack() + * method). If not possible to call the unpack method, it will leave a + * DoomBuffer object in its place, with the raw byte contents. + */ + @Override + @SuppressWarnings("unchecked") + public T CacheLumpNum(int lump, int tag, Class what) { + + if (lump >= numlumps) { + I.Error("W_CacheLumpNum: %d >= numlumps", lump); + } + + // Nothing cached here... + // SPECIAL case : if no class is specified (null), the lump is re-read anyway + // and you get a raw doombuffer. Plus, it won't be cached. + if ((lumpcache[lump] == null) || (what == null)) { + // read the lump in + // System.out.println("cache miss on lump "+lump); + // Fake Zone system: mark this particular lump with the tag specified + // ptr = Z_Malloc (W_LumpLength (lump), tag, &lumpcache[lump]); + // Read as a byte buffer anyway. + ByteBuffer thebuffer = ByteBuffer.wrap(ReadLump(lump)); + + // Class type specified + if (what != null) { + try { + // Can it be uncached? If so, deserialize it. + // MAES: this should be done whenever single lumps + // are read. DO NOT DELEGATE TO THE READ OBJECTS THEMSELVES. + // In case of sequential reads of similar objects, use + // CacheLumpNumIntoArray instead. + thebuffer.rewind(); + lumpcache[lump] = what.getDeclaredConstructor().newInstance(); + lumpcache[lump].unpack(thebuffer); + + // Track it for freeing + Track(lumpcache[lump], lump); + + if (what == patch_t.class) { + ((patch_t) lumpcache[lump]).name = this.lumpinfo[lump].name; + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, + String.format("Could not auto-instantiate lump %d of class %s", lump, String.valueOf(what)), e); + } + + } else { + // Class not specified? Then gimme a containing DoomBuffer! + DoomBuffer db = new DoomBuffer(thebuffer); + lumpcache[lump] = db; + } + } + + return (T) lumpcache[lump]; + } + + /** A very useful method when you need to load a lump which can consist + * of an arbitrary number of smaller fixed-size objects (assuming that you + * know their number/size and the size of the lump). Practically used + * by the level loader, to handle loading of sectors, segs, things, etc. + * since their size/lump/number relationship is well-defined. + * + * It possible to do this in other (more verbose) ways, but it's + * extremely convenient this way, as a lot of common and repetitive code + * is only written once, and generically, here. Trumps the older + * method in v 1.43 of WadLoader, which is deprecated. + * + * @param lump The lump number to load. + * @param num number of objects to read * + * @return a properly sized array of the correct type. + */ + @Override + public T[] CacheLumpNumIntoArray(int lump, int num, ArraySupplier what, IntFunction arrGen) { + if (lump >= numlumps) { + I.Error("CacheLumpNumIntoArray: %d >= numlumps", lump); + } + + /** + * Impossible condition unless you hack generics somehow + * - Good Sign 2017/05/07 + */ + /*if (!implementsInterface(what, CacheableDoomObject.class)){ + I.Error("CacheLumpNumIntoArray: %s does not implement CacheableDoomObject", what.getName()); + }*/ + // Nothing cached here... + if ((lumpcache[lump] == null) && (what != null)) { + //System.out.println("cache miss on lump " + lump); + // Read as a byte buffer anyway. + ByteBuffer thebuffer = ByteBuffer.wrap(ReadLump(lump)); + T[] stuff = malloc(what, arrGen, num); + + // Store the buffer anyway (as a CacheableDoomObjectContainer) + lumpcache[lump] = new CacheableDoomObjectContainer<>(stuff); + + // Auto-unpack it, if possible. + try { + thebuffer.rewind(); + lumpcache[lump].unpack(thebuffer); + } catch (IOException e) { + LOGGER.log(Level.WARNING, String.format( + "Could not auto-unpack lump %s into an array of objects of class %s", lump, what + ), e); + } + + // Track it (as ONE lump) + Track(lumpcache[lump], lump); + } + + if (lumpcache[lump] == null) { + return null; + } + + @SuppressWarnings("unchecked") + final CacheableDoomObjectContainer cont = (CacheableDoomObjectContainer) lumpcache[lump]; + return cont.getStuff(); + } + + public CacheableDoomObject CacheLumpNum(int lump) { + return lumpcache[lump]; + } + + @Override + public byte[] CacheLumpNameAsRawBytes(String name, int tag) { + return ((DoomBuffer) this.CacheLumpNum(this.GetNumForName(name), tag, null)).getBuffer().array(); + } + + @Override + public byte[] CacheLumpNumAsRawBytes(int num, int tag) { + return ((DoomBuffer) this.CacheLumpNum(num, tag, null)).getBuffer().array(); + } + + @Override + public DoomBuffer CacheLumpName(String name, int tag) { + return this.CacheLumpNum(this.GetNumForName(name), tag, DoomBuffer.class); + } + + @Override + public DoomBuffer CacheLumpNumAsDoomBuffer(int lump) { + return this.CacheLumpNum(lump, 0, DoomBuffer.class); + } + + + @Override + public patch_t CachePatchName(String name) { + return this.CacheLumpNum(this.GetNumForName(name), PU_CACHE, patch_t.class); + + } + + @Override + public patch_t CachePatchName(String name, int tag) { + return this.CacheLumpNum(this.GetNumForName(name), tag, patch_t.class); + } + + @Override + public patch_t CachePatchNum(int num) { + return this.CacheLumpNum(num, PU_CACHE, patch_t.class); + } + + @Override + @W_Wad.C(W_CacheLumpName) + public T CacheLumpName(String name, int tag, Class what) { + return this.CacheLumpNum(this.GetNumForName(name.toUpperCase()), tag, what); + } + + // + // W_Profile + // + /* USELESS + char[][] info = new char[2500][10]; + + int profilecount; + + void Profile() throws IOException { + int i; + // memblock_t block = null; + Object ptr; + char ch; + FileWriter f; + int j; + String name; + + for (i = 0; i < numlumps; i++) { + ptr = lumpcache[i]; + if ((ptr == null)) { + ch = ' '; + continue; + } else { + // block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block.tag < PU_PURGELEVEL) + ch = 'S'; + else + ch = 'P'; + } + info[i][profilecount] = ch; + } + profilecount++; + + f = new FileWriter(new File("waddump.txt")); + // name[8] = 0; + + for (i = 0; i < numlumps; i++) { + name = lumpinfo[i].name; + + f.write(name); + + for (j = 0; j < profilecount; j++) + f.write(" " + info[i][j]); + + f.write("\n"); + } + f.close(); + } */ + + @Override + public boolean isLumpMarker(int lump) { + return (lumpinfo[lump].size == 0); + } + + /* (non-Javadoc) + * @see w.IWadLoader#GetNameForLump(int) + */ + @Override + public String GetNameForLump(int lump) { + return lumpinfo[lump].name; + } + + // /////////////////// HASHTABLE SYSTEM /////////////////// + // + // killough 1/31/98: Initialize lump hash table + // + /** + * Maes 12/12/2010: Some credit must go to Killough for first + * Introducing the hashtable system into Boom. On early releases I had + * copied his implementation, but it proved troublesome later on and slower + * than just using the language's built-in hash table. Lesson learned, kids: + * don't reinvent the wheel. + * + * TO get an idea of how superior using a hashtable is, on 1000000 random + * lump searches the original takes 48 seconds, searching for precomputed + * hashes takes 2.84, and using a HashMap takes 0.2 sec. + * + * And the best part is that Java provides a perfectly reasonable implementation. + * + */ + HashMap doomhash; + + protected void InitLumpHash() { + + doomhash = new HashMap<>(numlumps); + + //for (int i = 0; i < numlumps; i++) + // lumpinfo[i].index = -1; // mark slots empty + // Insert nodes to the beginning of each chain, in first-to-last + // lump order, so that the last lump of a given name appears first + // in any chain, observing pwad ordering rules. killough + for (int i = 0; i < numlumps; i++) { // hash function: + doomhash.put(lumpinfo[i].name.toUpperCase(), Integer.valueOf(i)); + } + } + + @Override + @SourceCode.Compatible + @W_Wad.C(W_CheckNumForName) + public int CheckNumForName(String name/* , int namespace */) { + final Integer r = doomhash.get(name); + if (r != null) { + return r; + } + return -1; + } + + @Override + public int[] CheckNumsForName(String name) { + list.clear(); + + // Dumb search, no chained hashtables I'm afraid :-/ + // Move backwards, so list is compiled with more recent ones first. + for (int i = numlumps - 1; i >= 0; i--) { + if (name.compareToIgnoreCase(lumpinfo[i].name) == 0) { + list.add(i); + } + } + + final int num = list.size(); + int[] result = new int[num]; + for (int i = 0; i < num; i++) { + result[i] = list.get(i); + } + + // Might be empty/null, so check that out. + return result; + } + + private final ArrayList list = new ArrayList<>(); + + @Override + public lumpinfo_t GetLumpInfo(int i) { + return this.lumpinfo[i]; + } + + @Override + public void CloseAllHandles() { + List d = new ArrayList<>(); + + for (int i = 0; i < this.lumpinfo.length; i++) { + if (!d.contains(lumpinfo[i].handle)) { + d.add(lumpinfo[i].handle); + } + } + + int count = 1; + + for (InputStream e : d) { + try { + e.close(); + //System.err.printf("%s file handle closed",e.toString()); + count++; + } catch (IOException e1) { + LOGGER.log(Level.SEVERE, String.format("Could not close file handle (%d/%d)", count, d.size()), e1); + } + } + + //System.err.printf("%d file handles closed",count); + } + + public static final int ns_global = 0; + public static final int ns_flats = 1; + public static final int ns_sprites = 2; + + /** + * Based on Boom's W_CoalesceMarkedResource + * Sort of mashes similar namespaces together so that they form + * a continuous space (single start and end, e.g. so that multiple + * S_START and S_END as well as special DEUTEX lumps mash together + * under a common S_START/S_END boundary). Also also sort of performs + * a "bubbling down" of marked lumps at the end of the namespace. + * + * It's convenient for sprites, but can be replaced by alternatives + * for flats. + * + * killough 4/17/98: add namespace tags + * + * @param start_marker + * @param end_marker + * @param namespace + * @return + */ + public int CoalesceMarkedResource(String start_marker, + String end_marker, li_namespace namespace) { + int result = 0; + lumpinfo_t[] marked = new lumpinfo_t[numlumps]; + // C2JUtils.initArrayOfObjects(marked, lumpinfo_t.class); + int num_marked = 0, num_unmarked = 0; + boolean is_marked = false, mark_end = false; + lumpinfo_t lump; + + // Scan for specified start mark + for (int i = 0; i < numlumps; i++) { + lump = lumpinfo[i]; + if (IsMarker(start_marker, lump.name)) // start marker found + { // If this is the first start marker, add start marker to marked lumps + if (num_marked == 0) { + marked[num_marked] = new lumpinfo_t(); + marked[num_marked].name = new String(start_marker); + marked[num_marked].size = 0; // killough 3/20/98: force size to be 0 + marked[num_marked].namespace = li_namespace.ns_global; // killough 4/17/98 + marked[num_marked].handle = lump.handle; + // No real use for this yet + marked[num_marked].wadfile = lump.wadfile; + num_marked = 1; + //System.err.printf("%s identified as FIRST starter mark for %s index %d\n",lump.name, + // start_marker,i); + } + is_marked = true; // start marking lumps + } else if (IsMarker(end_marker, lump.name)) // end marker found + { + // System.err.printf("%s identified as end mark for %s index %d\n",lump.name, + // end_marker,i); + mark_end = true; // add end marker below + is_marked = false; // stop marking lumps + } else if (is_marked || lump.namespace == namespace) { + // if we are marking lumps, + // move lump to marked list + // sf: check for namespace already set + + // sf 26/10/99: + // ignore sprite lumps smaller than 8 bytes (the smallest possible) + // in size -- this was used by some dmadds wads + // as an 'empty' graphics resource + if (namespace != li_namespace.ns_sprites || lump.size > 8) { + marked[num_marked] = lump.clone(); + // System.err.printf("Marked %s as %d for %s\n",lump.name,num_marked,namespace); + marked[num_marked++].namespace = namespace; // killough 4/17/98 + result++; + } + } else { + lumpinfo[num_unmarked++] = lump.clone(); // else move down THIS list + } + } + + // Append marked list to end of unmarked list + System.arraycopy(marked, 0, lumpinfo, num_unmarked, num_marked); + + numlumps = num_unmarked + num_marked; // new total number of lumps + + if (mark_end) // add end marker + { + lumpinfo[numlumps].size = 0; // killough 3/20/98: force size to be 0 + //lumpinfo[numlumps].wadfile = NULL; + lumpinfo[numlumps].namespace = li_namespace.ns_global; // killough 4/17/98 + lumpinfo[numlumps++].name = end_marker; + } + + return result; + } + + public final static boolean IsMarker(String marker, String name) { + // Safeguard against nameless marker lumps e.g. in Galaxia.wad + if (name == null || name.length() == 0) { + return false; + } + return name.equalsIgnoreCase(marker) + || // doubled first character test for single-character prefixes only + // FF_* is valid alias for F_*, but HI_* should not allow HHI_* + (marker.charAt(1) == '_' && name.charAt(0) == marker.charAt(0) + && name.substring(1).equalsIgnoreCase(marker)); + } + + @Override + public void UnlockLumpNum(int lump) { + lumpcache[lump] = null; + } + + @Override + public void InjectLumpNum(int lump, CacheableDoomObject obj) { + lumpcache[lump] = obj; + } + + //// Merged remnants from LumpZone here. + HashMap zone; + + /** Add a lump to the tracking */ + public void Track(CacheableDoomObject lump, int index) { + zone.put(lump, index); + } + + @Override + public void UnlockLumpNum(CacheableDoomObject lump) { + // Remove it from the reference + Integer lumpno = zone.remove(lump); + + // Force nulling. This should trigger garbage collection, + // and reclaim some memory, provided you also nulled any other + // reference to a certain lump. Therefore, make sure you null + // stuff right after calling this method, if you want to make sure + // that they won't be referenced anywhere else. + if (lumpno != null) { + lumpcache[lumpno] = null; + //System.out.printf("Lump %d %d freed\n",lump.hashCode(),lumpno); + } + } + + @Override + public boolean verifyLumpName(int lump, String lumpname) { + + // Lump number invalid + if (lump < 0 || lump > numlumps - 1) { + return false; + } + + String name = GetNameForLump(lump); + + // Expected lump name not found + if (name == null || lumpname.compareToIgnoreCase(name) != 0) { + return false; + } + + // Everything should be OK now... + return true; + } + + @Override + public int GetWadfileIndex(wadfile_info_t wad1) { + return wadfiles.indexOf(wad1); + } + + @Override + public int GetNumWadfiles() { + return wadfiles.size(); + } + +} + +//$Log: WadLoader.java,v $ +//Revision 1.64 2014/03/28 00:55:32 velktron +//Cleaner, generic-based design to minimize warnings and suppressions. used whenever possible. +// +//Revision 1.63 2013/06/03 10:36:33 velktron +//Jaguar handling (actualname) +// +//Revision 1.62.2.1 2013/01/09 14:24:12 velktron +//Uses the rest of the crap +// +//Revision 1.62 2012/11/08 17:16:12 velktron +//Made GetLumpForNum generic. +// +//Revision 1.61 2012/09/25 16:33:36 velktron +//Dummy Doomsystem for easy testing. +// +//Revision 1.60 2012/09/24 17:16:22 velktron +//Massive merge between HiColor and HEAD. There's no difference from now on, and development continues on HEAD. +// +//Revision 1.57.2.5 2012/09/19 21:46:46 velktron +//Simpler call for getPATCH +// +//Revision 1.57.2.4 2012/09/04 15:08:34 velktron +//New GetNumsForName function. +// +//Revision 1.57.2.3 2012/06/14 22:38:20 velktron +//Uses new disk flasher. +// +//Revision 1.57.2.2 2011/12/08 00:40:40 velktron +//Fix for Galaxia.wad nameless lumps. +// +//Revision 1.57.2.1 2011/12/05 12:05:13 velktron +//Fixed a vexing bug with ZIP file header & TOC reading. +// +//Revision 1.57 2011/11/09 19:07:40 velktron +//Adapted to handling ZIP files +// +//Revision 1.56 2011/11/03 21:14:30 velktron +//Added -FINALLY!- resource access testing before adding them -_- +// +//Revision 1.55 2011/11/01 22:09:11 velktron +//Some more progress on URI handling. Not essential/not breaking. +// +//Revision 1.54 2011/10/25 19:45:51 velktron +//More efficient use of bis ;-) +// +//Revision 1.53 2011/10/25 19:42:48 velktron +//Added advanced streaming input support and more convenient byte[] ReadLump methods. +// +//Revision 1.52 2011/10/24 02:07:08 velktron +//DoomFile model abandoned. Now streams are used whenever possible, with possible future expandability to use e.g. URL streams or other types of resources other than RandomAccessFiles. +// +//Revision 1.51 2011/10/23 22:50:42 velktron +//Added InjectLumpNum function to force generated contents. +// +//Revision 1.50 2011/10/23 18:16:31 velktron +//Cleanup, moved logs to the end of the file +// +//Revision 1.49 2011/10/19 12:34:29 velktron +//Using extractFileBase in C2JUtils now, got rid of filelength +// +//Revision 1.48 2011/09/29 15:18:31 velktron +//Resource coalescing correctly handles wadfiles +// +//Revision 1.47 2011/09/27 15:57:09 velktron +//Full wadinfo (lump "ownership") system in place, borrowed from prBoom+ with a twist ;-) +// +//Revision 1.46 2011/09/16 11:17:22 velktron +//Added verifyLumpName function +// +//Revision 1.45 2011/09/02 16:29:59 velktron +//Minor interface change +// +//Revision 1.44 2011/08/24 14:55:42 velktron +//Deprecated old CacheLumpNumIntoArray method, much cleaner system introduced. +// +//Revision 1.43 2011/08/23 16:10:20 velktron +//Got rid of Z remnants, commenter out Profile (useless as it is) +// +//Revision 1.42 2011/08/23 16:08:43 velktron +//Integrated Zone functionality in WadLoader, Boom-like UnlockLump. Makes things MUCH easier. +// +//Revision 1.41 2011/08/02 13:49:56 velktron +//Fixed missing handle on generated lumpinfo_t +// +//Revision 1.40 2011/08/01 22:09:14 velktron +//Flats coalescing. +// +//Revision 1.39 2011/08/01 21:42:56 velktron +//Added BOOM CoaleseResources function. +// +//Revision 1.38 2011/07/13 16:34:18 velktron +//Started adding some BOOM wad handling stuff. Still WIP though. +// +//Revision 1.37 2011/07/05 13:26:30 velktron +//Added handle closing functionality. +// +//Revision 1.36 2011/06/12 21:52:11 velktron +//Made CheckNumForName uppercase-proof, at last. +// +//Revision 1.35 2011/06/03 16:35:27 velktron +//Default fakezone +// +//Revision 1.34 2011/06/02 14:23:20 velktron +//Added ability to "peg" an IZone manager. +// +//Revision 1.33 2011/05/23 17:00:39 velktron +//Got rid of verbosity +// +//Revision 1.32 2011/05/22 21:08:28 velktron +//Added better filename handling. +// +//Revision 1.31 2011/05/18 16:58:11 velktron +//Changed to DoomStatus +// +//Revision 1.30 2011/05/13 11:20:07 velktron +//Why the hell did this not implement IReadableDoomObject? +// +//Revision 1.29 2011/05/13 11:17:48 velktron +//Changed default read buffer behavior. Now it's ALWAYS reset when reading from disk, and not up to the CacheableDoomObject. This does not affect bulk/stream reads. +// +//Revision 1.28 2011/05/10 10:39:18 velktron +//Semi-playable Techdemo v1.3 milestone +// +//Revision 1.27 2011/01/26 00:04:45 velktron +//DEUTEX flat support, Unrolled drawspan precision fix. +// +//Revision 1.26 2011/01/10 16:40:54 velktron +//Some v1.3 commits: OSX fix, limit-removing flat management (to fix), +// +//Revision 1.25 2010/12/22 01:23:15 velktron +//Definitively fixed plain DrawColumn. +//Fixed PATCH/TEXTURE and filelump/wadloader capitalization. +//Brought back some testers. +// +//Revision 1.24 2010/12/14 17:55:59 velktron +//Fixed weapon bobbing, added translucent column drawing, separated rendering commons. +// +//Revision 1.23 2010/12/13 16:03:20 velktron +//More fixes in the wad loading code +// +//Revision 1.22 2010/12/12 21:27:17 velktron +//Fixed hashtable bug. Now using Java's one, faster AND easier to follow. +// +//Revision 1.21 2010/10/08 16:55:50 velktron +//Duh +// +//Revision 1.20 2010/09/27 02:27:29 velktron +//BEASTLY update +// +//Revision 1.19 2010/09/24 17:58:39 velktron +//Menus and HU functional -mostly. +// +//Revision 1.18 2010/09/23 20:36:45 velktron +//*** empty log message *** +// +//Revision 1.17 2010/09/23 15:11:57 velktron +//A bit closer... +// +//Revision 1.16 2010/09/22 16:40:02 velktron +//MASSIVE changes in the status passing model. +//DoomMain and DoomGame unified. +//Doomstat merged into DoomMain (now status and game functions are one). +// +//Most of DoomMain implemented. Possible to attempt a "classic type" start but will stop when reading sprites. +// +//Revision 1.15 2010/09/13 15:39:17 velktron +//Moving towards an unified gameplay approach... +// +//Revision 1.14 2010/09/09 01:13:19 velktron +//MUCH better rendering and testers. +// +//Revision 1.13 2010/09/07 16:23:00 velktron +//*** empty log message *** +// +//Revision 1.12 2010/09/03 15:30:34 velktron +//More work on unified renderer +// +//Revision 1.11 2010/09/02 15:56:54 velktron +//Bulk of unified renderer copyediting done. +// +//Some changes like e.g. global separate limits class and instance methods for seg_t and node_t introduced. +// +//Revision 1.10 2010/08/30 15:53:19 velktron +//Screen wipes work...Finale coded but untested. +//GRID.WAD included for testing. +// +//Revision 1.9 2010/08/23 14:36:08 velktron +//Menu mostly working, implemented Killough's fast hash-based GetNumForName, although it can probably be finetuned even more. +// +//Revision 1.8 2010/08/13 14:06:36 velktron +//Endlevel screen fully functional! +// +//Revision 1.7 2010/08/11 16:31:34 velktron +//Map loading works! Check out LevelLoaderTester for more. +// +//Revision 1.6 2010/08/10 16:41:57 velktron +//Threw some work into map loading. +// +//Revision 1.5 2010/07/22 15:37:53 velktron +//MAJOR changes in Menu system. +// +//Revision 1.4 2010/07/15 14:01:49 velktron +//Added reflector Method stuff for function pointers. +// +//Revision 1.3 2010/07/06 15:20:23 velktron +//Several changes in the WAD loading routine. Now lumps are directly unpacked as "CacheableDoomObjects" and only defaulting will result in "raw" DoomBuffer reads. +// +//Makes caching more effective. +// +//Revision 1.2 2010/06/30 11:44:40 velktron +//Added a tester for patches (one of the most loosely-coupled structs in Doom!) +//and fixed some minor stuff all around. +// +//Revision 1.1 2010/06/30 08:58:50 velktron +//Let's see if this stuff will finally commit.... +// +// +//Most stuff is still being worked on. For a good place to start and get an +//idea of what is being done, I suggest checking out the "testers" package. +// +//Revision 1.1 2010/06/29 11:07:34 velktron +//Release often, release early they say... +// +//Commiting ALL stuff done so far. A lot of stuff is still broken/incomplete, +//and there's still mixed C code in there. I suggest you load everything up in +//Eclpise and see what gives from there. +// +//A good place to start is the testers/ directory, where you can get an idea of +//how a few of the implemented stuff works. diff --git a/doom/src/w/animenum_t.java b/doom/src/w/animenum_t.java new file mode 100644 index 0000000..4c66e8c --- /dev/null +++ b/doom/src/w/animenum_t.java @@ -0,0 +1,9 @@ +package w; + +public enum animenum_t { + + ANIM_ALWAYS, + ANIM_RANDOM, + ANIM_LEVEL + +} \ No newline at end of file diff --git a/doom/src/w/filelump_t.java b/doom/src/w/filelump_t.java new file mode 100644 index 0000000..b2f5f08 --- /dev/null +++ b/doom/src/w/filelump_t.java @@ -0,0 +1,70 @@ +package w; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** filelumps are on-disk structures. lumpinfos are almost the same, but are memory only. + * + * @author Maes + * + */ +public class filelump_t implements IReadableDoomObject, IWritableDoomObject { + + public long filepos; + public long size; // Is INT 32-bit in file! + public String name; // Whatever appears inside the wadfile + public String actualname; // Sanitized name, e.g. after compression markers + + public boolean big_endian = false; // E.g. Jaguar + public boolean compressed = false; // Compressed lump + + public void read(DataInputStream f) throws IOException { + // MAES: Byte Buffers actually make it convenient changing byte order on-the-fly. + // But RandomAccessFiles (and inputsteams) don't :-S + + if (!big_endian) { + filepos = DoomIO.readUnsignedLEInt(f); + size = DoomIO.readUnsignedLEInt(f); + + } else { + filepos = f.readInt(); + size = f.readInt(); + + } + + // Names used in the reading subsystem should be upper case, + // but check for compressed status first + name = DoomIO.readNullTerminatedString(f, 8); + + char[] stuff = name.toCharArray(); + + // It's a compressed lump + if (stuff[0] > 0x7F) { + this.compressed = true; + stuff[0] &= 0x7F; + } + + actualname = new String(stuff).toUpperCase(); + + } + + public static int sizeof() { + return (4 + 4 + 8); + } + + @Override + public void write(DataOutputStream dos) + throws IOException { + if (!big_endian) { + DoomIO.writeLEInt(dos, (int) filepos); + DoomIO.writeLEInt(dos, (int) size); + } else { + dos.writeInt((int) filepos); + dos.writeInt((int) size); + } + DoomIO.writeString(dos, name, 8); + + } + +} \ No newline at end of file diff --git a/doom/src/w/li_namespace.java b/doom/src/w/li_namespace.java new file mode 100644 index 0000000..f530fb8 --- /dev/null +++ b/doom/src/w/li_namespace.java @@ -0,0 +1,12 @@ +package w; + +/** killough 4/17/98: namespace tags, to prevent conflicts between resources */ +public enum li_namespace { + ns_global, + ns_sprites, + ns_flats, + ns_colormaps, + ns_prboom, + ns_demos, + ns_hires //e6y +} // haleyjd 05/21/02: renamed from "namespace" \ No newline at end of file diff --git a/doom/src/w/lumpinfo_t.java b/doom/src/w/lumpinfo_t.java new file mode 100644 index 0000000..2d359d9 --- /dev/null +++ b/doom/src/w/lumpinfo_t.java @@ -0,0 +1,72 @@ +package w; + +import java.io.InputStream; + +/* +typedef struct +{ + // WARNING: order of some fields important (see info.c). + + char name[9]; + int size; + + // killough 4/17/98: namespace tags, to prevent conflicts between resources + enum { + ns_global=0, + ns_sprites, + ns_flats, + ns_colormaps, + ns_prboom, + ns_demos, + ns_hires //e6y + } li_namespace; // haleyjd 05/21/02: renamed from "namespace" + + wadfile_info_t *wadfile; + int position; + wad_source_t source; + int flags; //e6y +} lumpinfo_t; */ +public class lumpinfo_t implements Cloneable { + + public String name; + public InputStream handle; + public long position; + public long size; + // A 32-bit hash which should be enough for searching through hashtables. + public int hash; + // A 64-bit hash that just maps an 8-char string to a long num, good for hashing + // or for direct comparisons. + //public long stringhash; + // Intepreting the first 32 bits of their name as an int. Used in initsprites. + public int intname; + // public int next; + //public int index; + + // For BOOM compatibility + public li_namespace namespace; + public wadfile_info_t wadfile; + + public int hashCode() { + return hash; + } + + public String toString() { + return (name + " " + Integer.toHexString(hash)); + } + + public lumpinfo_t clone() { + lumpinfo_t tmp = new lumpinfo_t(); + tmp.name = name; // Well... a reference will do. + tmp.handle = handle; + tmp.position = position; + tmp.size = size; + tmp.hash = hash; + tmp.intname = intname; + tmp.namespace = namespace; + tmp.wadfile = wadfile; + + return tmp; + + } + +} \ No newline at end of file diff --git a/doom/src/w/name8.java b/doom/src/w/name8.java new file mode 100644 index 0000000..99746d0 --- /dev/null +++ b/doom/src/w/name8.java @@ -0,0 +1,77 @@ +package w; + +public class name8 { + + private byte[] s; + static byte[] ss = new byte[9]; + public int[] x; + public long hash; + + public name8(String name) { + s = new byte[9]; + x = new int[2]; + // in case the name was a full 8 chars + this.s[8] = 0; + + byte[] tmp = name.getBytes(); + System.arraycopy(tmp, 0, this.s, 0, Math.min(8, tmp.length)); + this.x[0] = byteArrayToInt(s, 0); + this.x[1] = byteArrayToInt(s, 4); + this.hash = byteArrayToLong(s, 0); + } + + /** Returns a 64-bit number that maps directly to the ASCII + * 8-bit representation of a fixed-length 8 char string. + * It's for all effects and purposes a unique 64-bit hash, and can be used to + * speed up comparisons. + * + * @param name + * @return + */ + public static long getLongHash(String name) { + // in case the name was a full 8 chars + for (int i = 0; i < ss.length; i++) { + ss[i] = 0; + } + + byte[] tmp = name.getBytes(); + // We must effectively limit hashes to 31 bits to be able to use them. + System.arraycopy(tmp, 0, ss, 0, Math.min(8, tmp.length)); + return byteArrayToLong(ss, 0); + } + + public static int getIntName(String name) { + // in case the name was a full 8 chars + for (int i = 0; i < ss.length; i++) { + ss[i] = 0; + } + + byte[] tmp = name.getBytes(); + System.arraycopy(tmp, 0, ss, 0, Math.min(4, tmp.length)); + return byteArrayToInt(ss, 0); + } + + public static int byteArrayToInt(byte[] src, int ofs) { + return (src[ofs] << 24) | (src[ofs + 1] << 16) | (src[ofs + 2] << 8) | src[ofs + 3]; + } + + public static long byteArrayToLong(byte[] src, int ofs) { + return (((long) byteArrayToInt(src, 0) << 32) | byteArrayToInt(src, 4)); + } + + /** Probably has horrible performance... + * + * @param src + * @param ofs + * @return + */ + public static int stringToInt(String src, int ofs) { + byte[] s = new byte[9]; + for (int i = 0; i < src.length(); i++) { + s[i] = (byte) src.charAt(i); + } + + return (s[ofs] << 24) | (s[ofs + 1] << 16) | (s[ofs + 2] << 8) | s[ofs + 3]; + } + +} \ No newline at end of file diff --git a/doom/src/w/statenum_t.java b/doom/src/w/statenum_t.java new file mode 100644 index 0000000..614fdd2 --- /dev/null +++ b/doom/src/w/statenum_t.java @@ -0,0 +1,19 @@ +package w; + +public enum statenum_t { + + NoState(-1), + StatCount(0), + ShowNextLoc(1); + + private int value; + + private statenum_t(int val) { + this.value = val; + } + + public int getValue() { + return value; + } + +} \ No newline at end of file diff --git a/doom/src/w/wad_source_t.java b/doom/src/w/wad_source_t.java new file mode 100644 index 0000000..c536403 --- /dev/null +++ b/doom/src/w/wad_source_t.java @@ -0,0 +1,17 @@ +package w; + +// CPhipps - defined enum in wider scope +// Ty 08/29/98 - add source field to identify where this lump came from +public enum wad_source_t { + // CPhipps - define elements in order of 'how new/unusual' + source_iwad, // iwad file load + source_pre, // predefined lump + source_auto_load, // lump auto-loaded by config file + source_pwad, // pwad file load + source_lmp, // lmp file load + source_net // CPhipps + //e6y + // ,source_deh_auto_load + , source_deh, source_err + +} \ No newline at end of file diff --git a/doom/src/w/wadfile_info_t.java b/doom/src/w/wadfile_info_t.java new file mode 100644 index 0000000..42dd22f --- /dev/null +++ b/doom/src/w/wadfile_info_t.java @@ -0,0 +1,18 @@ +package w; + +import java.io.InputStream; +import java.util.zip.ZipEntry; + +// CPhipps - changed wad init +// We _must_ have the wadfiles[] the same as those actually loaded, so there +// is no point having these separate entities. This belongs here. +public class wadfile_info_t { + + public String name; // Also used as a resource identifier, so save with full path and all. + public ZipEntry entry; // Secondary resource identifier e.g. files inside zip archives. + public int type; // as per InputStreamSugar + public wad_source_t src; + public InputStream handle; + public boolean cached; // Whether we use local caching e.g. for URL or zips + public long maxsize = -1; // Update when known for sure. Will speed up seeking. +} \ No newline at end of file diff --git a/doom/src/w/wadheader_t.java b/doom/src/w/wadheader_t.java new file mode 100644 index 0000000..7a7f6fa --- /dev/null +++ b/doom/src/w/wadheader_t.java @@ -0,0 +1,49 @@ +package w; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class wadheader_t implements IReadableDoomObject, IWritableDoomObject { + + public String type; + public int numentries; + public int tablepos; + + public boolean big_endian = false; + + public void read(DataInputStream f) throws IOException { + + type = DoomIO.readNullTerminatedString(f, 4); + + if (!big_endian) { + numentries = (int) DoomIO.readUnsignedLEInt(f); + tablepos = (int) DoomIO.readUnsignedLEInt(f); + + } else { + numentries = f.readInt(); + tablepos = f.readInt(); + } + + } + + public static int sizeof() { + return 16; + } + + @Override + public void write(DataOutputStream dos) + throws IOException { + DoomIO.writeString(dos, type, 4); + + if (!big_endian) { + DoomIO.writeLEInt(dos, (int) numentries); + DoomIO.writeLEInt(dos, (int) tablepos); + } else { + dos.writeInt((int) numentries); + dos.writeInt((int) tablepos); + } + + } + +} \ No newline at end of file diff --git a/doom/src/w/wadinfo_t.java b/doom/src/w/wadinfo_t.java new file mode 100644 index 0000000..99b1c41 --- /dev/null +++ b/doom/src/w/wadinfo_t.java @@ -0,0 +1,20 @@ +package w; + +import java.io.DataInputStream; +import java.io.IOException; + +public class wadinfo_t implements IReadableDoomObject { + // Should be "IWAD" or "PWAD". + + String identification; + long numlumps; + long infotableofs; + + /** Reads the wadinfo_t from the file.*/ + public void read(DataInputStream f) throws IOException { + identification = DoomIO.readString(f, 4); + numlumps = DoomIO.readUnsignedLEInt(f); + infotableofs = DoomIO.readUnsignedLEInt(f); + } + +} \ No newline at end of file diff --git a/doom/tranmap.dat b/doom/tranmap.dat new file mode 100644 index 0000000..94afabd Binary files /dev/null and b/doom/tranmap.dat differ diff --git a/doom/wads/doom-wad-shareware-license.txt b/doom/wads/doom-wad-shareware-license.txt new file mode 100644 index 0000000..97c9699 --- /dev/null +++ b/doom/wads/doom-wad-shareware-license.txt @@ -0,0 +1,126 @@ +The Doom 1 shareware WAD file is (C) Copyright id Software. + + LIMITED USE SOFTWARE LICENSE AGREEMENT + +This Limited Use Software License Agreement (the "Agreement") is a legal +agreement between you, the end-user, and Id Software, Inc. ("ID"). By +continuing the installation of this game program, by loading or running +the game, or by placing or copying the game program onto your computer +hard drive, you are agreeing to be bound by the terms of this Agreement. + +ID SOFTWARE LICENSE + + 1. Grant of License. ID grants to you the right to use the +Id Software game program (the "Software"), which is the shareware version +or episode one of the game program. For purposes of this section, "use" +means loading the Software into RAM, as well as installation on a hard disk +or other storage device. You may not: modify, translate, disassemble, +decompile, reverse engineer, or create derivative works based upon the +Software. You agree thatd the Software will not be shipped, transferred or +exported into any country in violation of the U.S. Export Administration Act +and that you will not utilize, in any other manner, the Software in violation +of any applicable law. + + 2. Copyright. The Software is owned by ID and is protected by United +States copyright laws and international treaty provisions. You must treat +the Software like any other copyrighted material, except that you may make +copies of the Software to give to other persons. You may not charge or +receive any consideration from any other person for the receipt or use of +the Software without receiving ID's prior written consent as specified in the +VENDOR.DOC file. You agree to use your best efforts to see that any user of +the Software licensed hereunder complies with this Agreement. + + 3. Limited Warranty. ID warrants that if properly installed and +operated on a computer for which it is designed, the Software will perform +substantially in accordance with its designed purpose for a period of ninety +(90) days from the date the Software is first obtained by an end-user. ID's +entire liability and your exclusive remedy shall be, at ID's option, either +(a) return of the retail price paid, if any, or (b) repair or replacement of +the Software that does not meet ID's Limited Warranty. To make a warranty +claim, return the Software to the point of purchase, accompanied by proof of +purchase, your name, your address, and a statement of defect, or return the +Software with the above information to ID. This Limited Warranty is void if +failure of the Software has resulted in whole or in part from accident, +abuse, misapplication or violation of this Agreement. Any replacement +Software will be warranted for the remainder of the original warranty period +or thirty (30) days, whichever is longer. This warranty allocates risks of +product failure between Licensee and ID. ID's product pricing reflects this +allocation of risk and the limitations of liability contained in this +warranty. + + 4. NO OTHER WARRANTIES. ID DISCLAIMS ALL OTHER WARRANTIES, EITHER +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, IMPLIED WARRANTIES OF +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE WITH RESPECT TO THE +SOFTWARE AND THE ACCOMPANYING WRITTEN MATERIALS, IF ANY. THIS LIMITED +WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS. YOU MAY HAVE OTHERS WHICH VARY +FROM JURISDICTION TO JURISDICTION. ID DOES NOT WARRANT THAT THE OPERATION +OF THE SOFTWARE WILL BE UNINTERRUPTED, ERROR FREE OR MEET LICENSEE'S +SPECIFIC REQUIREMENTS. THE WARRANTY SET FORTH ABOVE IS IN LIEU OF ALL OTHER +EXPRESS WARRANTIES WHETHER ORAL OR WRITTEN. THE AGENTS, EMPLOYEES, +DISTRIBUTORS, AND DEALERS OF ID ARE NOT AUTHORIZED TO MAKE MODIFICATIONS TO +THIS WARRANTY, OR ADDITIONAL WARRANTIES ON BEHALF OF ID. ADDITIONAL +STATEMENTS SUCH AS DEALER ADVERTISING OR PRESENTATIONS, WHETHER ORAL OR +WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID AND SHOULD NOT BE RELIED UPON. + + 5. Exclusive Remedies. You agree that your exclusive remedy against +ID, its affiliates, contractors, suppliers, and agents for loss or damage +caused by any defect or failure in the Software regardless of the form of +action, whether in contract, tort, including negligence, strict liability or +otherwise, shall be the return of the retail purchase price paid, if any, or +replacement of the Software. This Agreement shall be construed in +accordance with and governed by the laws of the State of Texas. Copyright +and other proprietary matters will be governed by United States laws and +international treaties. IN ANY CASE, ID SHALL NOT BE LIABLE FOR LOSS OF +DATA, LOSS OF PROFITS, LOST SAVINGS, SPECIAL, INCIDENTAL, CONSEQUENTIAL, +INDIRECT OR OTHER SIMILAR DAMAGES ARISING FROM BREACH OF WARRANTY, BREACH OF +CONTRACT, NEGLIGENCE, OR OTHER LEGAL THEORY EVEN IF ID OR ITS AGENT HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER +PARTY. Some jurisdictions do not allow the exclusion or limitation of +incidental or consequential damages, so the above limitation or exclusion +may not apply to you. + + 6. General Provisions. Neither this Agreement nor any part or portion +hereof shall be assigned or sublicensed, except as described herein. Should +any provision of this Agreement be held to be void, invalid, unenforceable or +illegal by a court, the validity and enforceability of the other provisions +shall not be affected thereby. If any provision is determined to be +unenforceable, you agree to a modification of such provision to provide for +enforcement of the provision's intent, to the extent permitted by applicable +law. Failure of a party to enforce any provision of this Agreement shall not +constitute or be construed as a waiver of such provision or of the right to +enforce such provision. If you fail to comply with any terms of this +Agreement, YOUR LICENSE IS AUTOMATICALLY TERMINATED. + + YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS +AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE +SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE +SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, YOU AGREE TO BE BOUND BY THIS +AGREEMENT'S TERMS AND CONDITIONS. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN +SEPARATE AGREEMENTS BETWEEN ID AND YOU, THIS AGREEMENT IS A COMPLETE AND +EXCLUSIVE STATEMENT OF THE RIGHTS AND LIABILITIES OF THE PARTIES. THIS +AGREEMENT SUPERSEDES ALL PRIOR ORAL AGREEMENTS, PROPOSALS OR UNDERSTANDINGS, +AND ANY OTHER COMMUNICATIONS BETWEEN ID AND YOU RELATING TO THE SUBJECT +MATTER OF THIS AGREEMENT. + +The above license does not appear to grant distribution permission. Email +from John Carmack of ID Software provided this clarification: + +X-Sender: johnc@mail.idsoftware.com +X-Mailer: Windows Eudora Pro Version 3.0 (32) +Date: Sat, 23 Oct 1999 20:01:30 -0500 +To: Joe Drew +From: johnc@idsoftware.com (John Carmack) +Subject: Re: Doom shareware WAD license + +At 08:02 PM 10/23/99 -0400, you wrote: +>Can you give me a definite license on the doom 1 shareware wad? I find certain +>things that say "freely distribute" and others that say "get vendor's license" +>... All I need to have is a license so I can package it up for Debian. +>Thanks. +>Joe + +The DOOM shareware wad is freely distributable. No Quake data is freely +distributable. + +John Carmack + diff --git a/doom/wads/doom1.md5 b/doom/wads/doom1.md5 new file mode 100644 index 0000000..28aca91 --- /dev/null +++ b/doom/wads/doom1.md5 @@ -0,0 +1 @@ +f0cefca49926d00903cf57551d901abe doom1.wad diff --git a/doom/wads/doom1.wad b/doom/wads/doom1.wad new file mode 100644 index 0000000..1a58f66 Binary files /dev/null and b/doom/wads/doom1.wad differ