Copyright (c) 2015 - 2022 Matt Tropiano
This library is an offshoot/rewrite of Black Rook Software's Doom Struct Project, of which I have been granted rights to rewrite (the rights were granted to myself BY myself).
All deprecated classes from the origin project will not be in this one. All end users are encouraged to switch to this one, as this project will be actively maintained.
NONE
The master
branch contains stable code (I hope). Until a release is cut, the master
branch will be shifting.
The purpose of the Doom Struct project is to provide a means to read/write data structures for the Doom Engine and similar derivatives.
There are several libraries out there for reading data from Doom in many different programming languages, but there isn't a decent one for Java. And by decent, I mean:
- Useful
- Documented
- Performant
- Efficient
The goal with this library is to get end-users up-and-running quickly with little boilerplate code, and to ensure clarity within documentation and with written code, and future-proofing to a reasonable extent.
- Reads/edits WAD files.
- Reads PK3 files (zips).
- Full UDMF parsing/writing support.
- Reads/edits all Doom data structures in Doom, Hexen/ZDoom, or Strife formats. This includes textures, patches, lines, vertices, things, sectors, nodes, palettes, colormaps, text, PNG data, music data, sound data, flats, blockmaps, reject, and even ENDOOM-type VGA lumps.
- Supports PNGs with offset data (grAb).
- Reads/edits Boom-engine data lumps like ANIMATED and SWITCHES.
- Contains a utility class for converting Doom graphics to standard Java graphics structures, and vice-versa.
- Contains a utility class for converting Doom DMX sound data to other formats via the Java SPI, and vice-versa.
- Contains a utility class for managing texture sets without needing to care too much about Doom's nightmarish texture data setup.
- Reading/converting PC Speaker effects.
- Supporting additional BSP node types.
- Stuff for drawing maps more easily (graphic-wise).
- Better ENDOOM rendering options.
- Turning this project into a Maven Repository Compatible one (currently it's Ant-compatible; it gets the job done).
- Better Unit tests (or automatable ones).
Contained in this release is a series of libraries that allow reading, writing, and extracting data in Doom Engine structures, found in the net.mtrop.doom packages.
Unless otherwise specified, assume that all methods are not thread-safe.
Open a WAD file (and close it).
WadFile wad = new WadFile("doom2.wad");
wad.close();
Open DOOM2.WAD
and read MAP01
into a Doom Map.
WadFile wad = new WadFile("doom2.wad");
DoomMap map = MapUtils.createDoomMap(wad, "map01");
wad.close();
Open DOOM2.WAD
and fetch all sectors in MAP29
with the BLOOD1
floor texture.
WadFile wad = new WadFile("doom2.wad");
Set<DoomSector> set = wad.getDataAsList("sectors", "map29", DoomSector.class, DoomSector.LENGTH).stream()
.filter((sector) -> sector.getFloorTexture().equals("BLOOD1"))
.collect(Collectors.toSet());
wad.close();
Open DOOM.WAD
and fetch all things in E1M1
that appear in multiplayer.
WadFile wad = new WadFile("doom.wad");
Set<DoomThing> set = wad.getDataAsList("things", "e1m1", DoomThing.class, DoomThing.LENGTH).stream()
.filter((thing) -> thing.isFlagSet(DoomThingFlags.NOT_SINGLEPLAYER))
.collect(Collectors.toSet());
wad.close();
Open HEXEN.WAD
and fetch all things in MAP01
that have a special.
WadFile wad = new WadFile("hexen.wad");
Set<HexenThing> set = wad.getDataAsList("things", "map01", HexenThing.class, HexenThing.LENGTH).stream()
.filter((thing) -> thing.getSpecial() > 0)
.collect(Collectors.toSet());
wad.close();
Open DOOM.WAD
and fetch all maps that have less than 1000 linedefs.
final WadFile wad = new WadFile("doom.wad");
Set<String> set = Arrays.asList(MapUtils.getAllMapHeaders(wad)).stream()
.filter((header) -> wad.getEntry("linedefs", header).getSize() / DoomLinedef.LENGTH < 1000)
.collect(Collectors.toSet());
wad.close();
Open SQUARE1.PK3
, fetch maps/E1A1.WAD
and read it into a UDMF Map.
DoomPK3 pk3 = new DoomPK3("square1.pk3");
WadBuffer wad = pk3.getDataAsWadBuffer("maps/e1a1.wad");
UDMFMap map = wad.getTextDataAs("textmap", Charset.forName("UTF-8"), UDMFMap.class);
pk3.close();
Open DOOM2.WAD
, read DEMO2
and figure out how many tics that player 1 was pushing "fire".
WadFile wad = new WadFile("doom2.wad");
Demo demo = wad.getDataAs("demo2", Demo.class);
int tics = 0;
for (int i = 0; i < demo.getTicCount(); i++) {
tics += (demo.getTic(i).getAction() & Demo.Tic.ACTION_FIRE) != 0 ? 1 : 0;
}
wad.close();
Open DOOM.WAD
and get MD5 hashes of each entry.
WadFile wad = new WadFile("doom.wad");
byte[][] hashes = new byte[wad.getEntryCount()][];
int i = 0;
for (WadEntry entry : wad)
hashes[i++] = MessageDigest.getInstance("MD5").digest(wad.getData(entry));
wad.close();
Open DOOM2.WAD
, fetch all TROO*
(graphic) entries and export them as PNGs.
WadFile wad = new WadFile("doom2.wad");
final Palette pal = wad.getDataAs("playpal", Palette.class);
for (WadEntry entry : wad) {
if (entry.getName().startsWith("TROO")) {
Picture p = wad.getDataAs(entry, Picture.class);
ImageIO.write(GraphicUtils.createImage(p, pal), "PNG", new File(entry.getName()+".png"));
}
}
wad.close();
Open DOOM.WAD
, fetch all DS*
(audio) entries, upsample them to 22kHz (cosine interpolation), and export them as WAVs.
WadFile wad = new WadFile("doom.wad");
for (WadEntry entry : wad) {
if (entry.getName().startsWith("DS")) {
DMXSound sound = wad.getDataAs(entry, DMXSound.class)
.resample(InterpolationType.COSINE, DMXSound.SAMPLERATE_22KHZ);
SoundUtils.writeSoundToFile(sound, AudioFileFormat.Type.WAVE, new File(entry.getName() + ".wav"));
}
}
wad.close();
Open DOOM.WAD
, fetch all map headers, and extract those maps' entries to separate WADs.
WadFile wad = new WadFile("doom.wad");
for (int headerIndex : MapUtils.getAllMapIndices(wad)) {
String headerName = wad.getEntry(headerIndex).getName();
WadBuffer.extract(wad, MapUtils.getMapEntries(wad, headerName))
.writeToFile(new File(headerName + ".wad"));
}
wad.close();
Open DOOM2.WAD
, extract MAP01
from it into a file called MAP01.WAD
, and replace any wall texture that starts with TEKGREN
to FIREBLU1
.
WadFile wad = new WadFile("doom2.wad");
WadFile temp = WadFile.extract(new File("MAP01.WAD"), wad, MapUtils.getMapEntries(wad, "MAP01"));
temp.transformData("sidedefs", DoomSidedef.class, DoomSidedef.LENGTH, (sidedef, n) -> {
if (sidedef.getTextureMiddle().startsWith("TEKGREN"))
sidedef.setTextureMiddle("FIREBLU1");
});
temp.close();
wad.close();
Open DOOM2.WAD
, assemble a TextureSet, remove every texture that begins with R
, and write it to a new WAD.
WadFile wad = new WadFile("doom2.wad");
TextureSet textureSet = new TextureSet(
wad.getDataAs("pnames", PatchNames.class),
wad.getDataAs("texture1", DoomTextureList.class)
);
wad.close();
Iterator<TextureSet.Texture> it = textureSet.iterator();
while (it.hasNext()) {
TextureSet.Texture t = it.next();
if (t.getName().startsWith("R"))
it.remove();
}
PatchNames pout = new PatchNames();
DoomTextureList tout = new DoomTextureList();
textureSet.export(pout, tout);
WadFile newwad = WadFile.createWadFile("new.wad");
newwad.addData("PNAMES", pout.toBytes());
newwad.addData("TEXTURE1", tout.toBytes());
newwad.close();
Open DOOM.WAD
, get the first map of each episode and write it to a new WAD called OUT.wad
and close both (with one statement!).
WadUtils.openWadAndExtractTo("DOOM.WAD", "OUT.wad", (wad)->
WadUtils.withEntries(MapUtils.getMapEntries(wad, "E1M1"))
.and(MapUtils.getMapEntries(wad, "E2M1"))
.and(MapUtils.getMapEntries(wad, "E3M1"))
.and(MapUtils.getMapEntries(wad, "E4M1"))
.get()
);
Open DOOM2.WAD
, create a new Wad called TEXTURES.wad
and copy over only a few textures and flats (and also-close them):
try (WadFile source = new WadFile("DOOM2.WAD")) {
try (WadFile target = WadFile.createWadFile("TEXTURES.wad")) {
try (TextureCopier copier = TextureUtils.createTextureCopier(source, target)) {
copier.copyFlat("FLOOR7_1");
copier.copyFlat("CEIL5_2");
copier.copyTexture("AASHITTY"); // first texture is "null" texture
copier.copyTexture("SUPPORT3");
copier.copyTexture("SUPPORT2");
copier.copyTexture("BIGDOOR1");
copier.copyTexture("BIGDOOR2");
copier.copyTexture("BIGDOOR3");
copier.copyTexture("BIGDOOR4");
copier.copyTexture("BIGDOOR5");
}
}
}
To compile this library with Apache Ant, type:
ant compile
To make Maven-compatible JARs of this library (placed in the build/jar directory), type:
ant jar
To make Javadocs (placed in the build/docs directory):
ant javadoc
To compile main and test code and run tests (if any):
ant test
To make Zip archives of everything (main src/resources, bin, javadocs, placed in the build/zip directory):
ant zip
To compile, JAR, test, and Zip up everything:
ant release
To clean up everything:
ant clean
Online Javadocs can be found at: https://mtrop.github.io/DoomStruct/javadoc/
This program/library and the accompanying materials are made available under the terms of the GNU Lesser Public License v2.1 which accompanies this distribution, and is available at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
A copy of the LGPL should have been included in this release (LICENSE.txt). If it was not, please contact me for a copy, or to notify me of a distribution that has not included it.
This contains code copied from Black Rook Base, under the terms of the MIT License (docs/LICENSE-BlackRookBase.txt).
Eat your heart out, OMGIFOL!