More work towards prototype

This commit is contained in:
Retera 2020-01-20 19:28:18 -06:00
parent d39976fa18
commit 015b04c371
141 changed files with 10344 additions and 1284 deletions

View File

@ -8,28 +8,38 @@ import java.util.Arrays;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.datasources.CompoundDataSource;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SolvedPath;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance;
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
private static final boolean SPIN = false;
private static final boolean ADVANCE_ANIMS = true;
private DataSource codebase;
private ModelViewer viewer;
private MdxModel model;
private CameraManager cameraManager;
private static int VAO;
private final Rectangle tempRect = new Rectangle();
private BitmapFont font;
private SpriteBatch batch;
@Override
public void create() {
@ -46,58 +56,320 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER);
System.err.println("Renderer: " + renderer);
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq");
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127");
final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\Test");
this.codebase = new CompoundDataSource(Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder));
final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor(".");
this.codebase = new CompoundDataSourceDescriptor(
Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder, currentFolder)).createDataSource();
this.viewer = new ModelViewer(this.codebase, this);
this.viewer.addHandler(new MdxHandler());
this.viewer.enableAudio();
final Scene scene = this.viewer.addScene();
scene.enableAudio();
this.cameraManager = new CameraManager();
this.cameraManager.setupCamera(scene);
this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() {
// this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() {
this.mainModel = (MdxModel) this.viewer.load("Buildings\\Undead\\Necropolis\\Necropolis.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxSimpleInstance instance = (MdxSimpleInstance) this.model.addInstance(1);
this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);
instance.setScene(scene);
this.mainInstance.setScene(scene);
// instance.setSequence(1);
//
// instance.setSequenceLoopMode(2);
int animIndex = 0;
for (final Sequence s : this.mainModel.getSequences()) {
if (s.getName().toLowerCase().startsWith("walk")) {
animIndex = this.mainModel.getSequences().indexOf(s);
}
}
this.mainInstance.setSequence(animIndex);
this.mainInstance.setSequenceLoopMode(0);
// acolytesHarvestingSceneJoke2(scene);
System.out.println("Loaded");
// Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background
Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background
this.font = new BitmapFont();
this.batch = new SpriteBatch();
}
private void makeDruidSquare(final Scene scene) {
final MdxModel model2 = (MdxModel) this.viewer.load("units\\nightelf\\druidoftheclaw\\druidoftheclaw.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
makePerfectSquare(scene, model2, 15);
}
private void singleAcolyteScene(final Scene scene) {
final MdxModel model2 = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0);
instance3.setScene(scene);
int animIndex = 0;
for (final Sequence s : model2.getSequences()) {
if (s.getName().toLowerCase().startsWith("stand work")) {
animIndex = model2.getSequences().indexOf(s);
}
}
instance3.setSequence(animIndex);
instance3.setSequenceLoopMode(2);
}
private void singleModelScene(final Scene scene, final String path, final String animName) {
final MdxModel model2 = (MdxModel) this.viewer.load(path, new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0);
instance3.setScene(scene);
int animIndex = 0;
for (final Sequence s : model2.getSequences()) {
if (s.getName().toLowerCase().startsWith(animName)) {
animIndex = model2.getSequences().indexOf(s);
}
}
instance3.setSequence(animIndex);
instance3.setSequenceLoopMode(2);
}
private void acolytesHarvestingScene(final Scene scene) {
final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxModel mineEffectModel = (MdxModel) this.viewer
.load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
for (int i = 0; i < 5; i++) {
final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0);
acolyteInstance.setScene(scene);
int animIndex = i % acolyteModel.getSequences().size();
for (final Sequence s : acolyteModel.getSequences()) {
if (s.getName().toLowerCase().startsWith("stand work")) {
animIndex = acolyteModel.getSequences().indexOf(s);
}
}
acolyteInstance.setSequence(animIndex);
acolyteInstance.setSequenceLoopMode(2);
final double angle = ((Math.PI * 2) / 5) * i;
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256;
acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI));
final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0);
effectInstance.setScene(scene);
effectInstance.setSequence(1);
effectInstance.setSequenceLoopMode(2);
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
}
final MdxModel mineModel = (MdxModel) this.viewer.load("buildings\\undead\\hauntedmine\\hauntedmine.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0);
mineInstance.setScene(scene);
mineInstance.setSequence(2);
mineInstance.setSequenceLoopMode(2);
}
private void acolytesHarvestingSceneJoke2(final Scene scene) {
final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxModel mineEffectModel = (MdxModel) this.viewer
.load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
for (int i = 0; i < 5; i++) {
final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0);
acolyteInstance.setScene(scene);
int animIndex = i % acolyteModel.getSequences().size();
for (final Sequence s : acolyteModel.getSequences()) {
if (s.getName().toLowerCase().startsWith("stand work")) {
animIndex = acolyteModel.getSequences().indexOf(s);
}
}
acolyteInstance.setSequence(animIndex);
acolyteInstance.setSequenceLoopMode(2);
final double angle = ((Math.PI * 2) / 5) * i;
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256;
acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI));
final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0);
effectInstance.setScene(scene);
effectInstance.setSequence(1);
effectInstance.setSequenceLoopMode(2);
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
}
final MdxModel mineModel = (MdxModel) this.viewer.load("units\\orc\\spiritwolf\\spiritwolf.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0);
mineInstance.setScene(scene);
mineInstance.setSequence(0);
mineInstance.localScale.x = 2;
mineInstance.localScale.y = 2;
mineInstance.localScale.z = 2;
mineInstance.setSequenceLoopMode(2);
final MdxModel mineModel2 = (MdxModel) this.viewer
.load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
final MdxComplexInstance mineInstance2 = (MdxComplexInstance) mineModel2.addInstance(0);
mineInstance2.setScene(scene);
mineInstance2.setSequence(0);
mineInstance2.setSequenceLoopMode(2);
}
private void makeFourHundred(final Scene scene, final MdxModel model2) {
for (int i = 0; i < 400; i++) {
final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0);
instance3.localLocation.x = (((i % 20) - 10) * 128);
instance3.localLocation.y = (((i / 20) - 10) * 128);
instance3.setScene(scene);
final int animIndex = i % model2.getSequences().size();
instance3.setSequence(animIndex);
instance3.setSequenceLoopMode(2);
}
}
private void makePerfectSquare(final Scene scene, final MdxModel model2, final int n) {
final int n2 = n * n;
for (int i = 0; i < n2; i++) {
final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0);
instance3.localLocation.x = (((i % n) - (n / 2)) * 128);
instance3.localLocation.y = (((i / n) - (n / 2)) * 128);
instance3.setScene(scene);
final int animIndex = i % model2.getSequences().size();
instance3.setSequence(animIndex);
instance3.setSequenceLoopMode(2);
}
}
public static void bindDefaultVertexArray() {
Gdx.gl30.glBindVertexArray(VAO);
}
private int frame = 0;
private MdxComplexInstance mainInstance;
private MdxModel mainModel;
@Override
public void render() {
// this.cameraManager.verticalAngle += 0.01;
// if (this.cameraManager.verticalAngle >= (Math.PI)) {
// this.cameraManager.verticalAngle = 0;
// }
this.cameraManager.horizontalAngle += 0.01;
if (this.cameraManager.horizontalAngle > (2 * Math.PI)) {
this.cameraManager.horizontalAngle = 0;
Gdx.gl30.glBindVertexArray(VAO);
if (SPIN) {
this.cameraManager.horizontalAngle += 0.01;
if (this.cameraManager.horizontalAngle > (2 * Math.PI)) {
this.cameraManager.horizontalAngle = 0;
}
}
this.cameraManager.updateCamera();
this.viewer.updateAndRender();
// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset);
// this.batch.begin();
// this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0);
// this.batch.end();
this.frame++;
if ((this.frame % 1000) == 0) {
System.out.println(Integer.toString(Gdx.graphics.getFramesPerSecond()));
}
if (ADVANCE_ANIMS && this.mainInstance.sequenceEnded) {
this.mainInstance.setSequence((this.mainInstance.sequence + 1) % this.mainModel.getSequences().size());
}
}
@Override
@ -114,6 +386,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
return Gdx.graphics.getHeight();
}
@Override
public void resize(final int width, final int height) {
this.tempRect.width = width;
this.tempRect.height = height;
this.cameraManager.camera.viewport(this.tempRect);
}
class CameraManager {
private CanvasProvider canvas;
private Camera camera;
@ -143,9 +422,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.zoomFactor = 0.1f;
this.horizontalAngle = (float) (Math.PI / 2);
this.verticalAngle = (float) (Math.PI / 4);
this.distance = 5;
this.distance = 1000;
this.position = new Vector3();
this.target = new Vector3();
this.target = new Vector3(0, 0, 50);
this.worldUp = new Vector3(0, 0, 1);
this.vecHeap = new Vector3();
this.quatHeap = new Quaternion();

View File

@ -0,0 +1,163 @@
package com.etheller.warsmash;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
public class WarsmashTestGameAttributes extends ApplicationAdapter {
private int arrayBuffer;
private int elementBuffer;
private int VAO;
@Override
public void create() {
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing
final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4);
tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final IntBuffer temp = tempByteBuffer.asIntBuffer();
Gdx.gl30.glGenVertexArrays(1, temp);
this.VAO = temp.get(0);
Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram = new ShaderProgram(vsSimple, fsSimple);
if (!this.shaderProgram.isCompiled()) {
throw new IllegalStateException(this.shaderProgram.getLog());
}
this.arrayBuffer = Gdx.gl.glGenBuffer();
this.elementBuffer = Gdx.gl.glGenBuffer();
System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.vertexBuffer = vertexByteBuffer.asFloatBuffer();
this.vertexBuffer.put(0, -1f);
this.vertexBuffer.put(1, -1f);
this.vertexBuffer.put(2, 0);
this.vertexBuffer.put(3, 1f);
this.vertexBuffer.put(4, -1f);
this.vertexBuffer.put(5, 0);
this.vertexBuffer.put(6, 0f);
this.vertexBuffer.put(7, 1f);
this.vertexBuffer.put(8, 0);
Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer);
final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer();
vertexBuffer2.put(0, -1f);
vertexBuffer2.put(1, -1f);
vertexBuffer2.put(2, 0);
vertexBuffer2.put(3, 1f);
vertexBuffer2.put(4, -1f);
vertexBuffer2.put(5, 0);
vertexBuffer2.put(6, 0f);
vertexBuffer2.put(7, 1f);
vertexBuffer2.put(8, 0);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2);
final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3);
skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
skinByteBuffer.put((byte) 34);
skinByteBuffer.put((byte) 35);
skinByteBuffer.put((byte) 36);
skinByteBuffer.clear();
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer);
// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
// this.shaderProgram.enableVertexAttribute("a_position");
// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
// this.shaderProgram.enableVertexAttribute("a_position2");
// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
// this.shaderProgram.enableVertexAttribute("a_boneNumber");
final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6);
faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.faceBuffer = faceByteBuffer.asShortBuffer();
this.faceBuffer.put(0, (short) 0);
this.faceBuffer.put(1, (short) 1);
this.faceBuffer.put(2, (short) 2);
Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW);
final int glGetError = Gdx.gl.glGetError();
System.out.println(glGetError);
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram.begin();
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
this.shaderProgram.enableVertexAttribute("a_position");
this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
this.shaderProgram.enableVertexAttribute("a_position2");
this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
this.shaderProgram.enableVertexAttribute("a_boneNumber");
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer);
Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0);
// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3);
this.shaderProgram.end();
}
@Override
public void dispose() {
}
@Override
public void resize(final int width, final int height) {
}
public static final String vsSimple = "\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_position2;\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying float fragNumber;\r\n" + //
" void main() {\r\n" + //
" gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + //
" fragNumber = a_boneNumber;\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
" varying float fragNumber;\r\n" + //
" void main() {\r\n" + //
" if( fragNumber > 35.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + //
" } else if( fragNumber > 34.5 ) {\r\n" + //
" gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + //
" } else if( fragNumber > 33.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + //
" } else {\r\n" + //
" gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + //
" }\r\n" + //
" }\r\n";
private ShaderProgram shaderProgram;
private FloatBuffer vertexBuffer;
private ShortBuffer faceBuffer;
}

View File

@ -0,0 +1,214 @@
package com.etheller.warsmash;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.parsers.mdlx.Geoset;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
public class WarsmashTestGameAttributes2 extends ApplicationAdapter {
private int arrayBuffer;
private int elementBuffer;
private int VAO;
private DataSource codebase;
@Override
public void create() {
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq");
final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\Test");
this.codebase = new CompoundDataSourceDescriptor(Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder))
.createDataSource();
final MdlxModel model;
try (InputStream modelStream = this.codebase.getResourceAsStream("Buildings\\Other\\TempArtB\\TempArtB.mdx")) {
model = new MdlxModel(modelStream);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing
final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4);
tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final IntBuffer temp = tempByteBuffer.asIntBuffer();
Gdx.gl30.glGenVertexArrays(1, temp);
this.VAO = temp.get(0);
Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram = new ShaderProgram(vsSimple, fsSimple);
if (!this.shaderProgram.isCompiled()) {
throw new IllegalStateException(this.shaderProgram.getLog());
}
this.arrayBuffer = Gdx.gl.glGenBuffer();
this.elementBuffer = Gdx.gl.glGenBuffer();
System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
final Geoset geoset0 = model.getGeosets().get(0);
final float[] vertices = geoset0.getVertices();
final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.vertexBuffer = vertexByteBuffer.asFloatBuffer();
this.vertexBuffer.put(0, -1f);
this.vertexBuffer.put(1, -1f);
this.vertexBuffer.put(2, 0);
this.vertexBuffer.put(3, 1f);
this.vertexBuffer.put(4, -1f);
this.vertexBuffer.put(5, 0);
this.vertexBuffer.put(6, 0f);
this.vertexBuffer.put(7, 1f);
this.vertexBuffer.put(8, 0);
Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer);
final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer();
vertexBuffer2.put(0, -1f);
vertexBuffer2.put(1, -1f);
vertexBuffer2.put(2, 0);
vertexBuffer2.put(3, 1f);
vertexBuffer2.put(4, -1f);
vertexBuffer2.put(5, 0);
vertexBuffer2.put(6, 0f);
vertexBuffer2.put(7, 1f);
vertexBuffer2.put(8, 0);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2);
final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3);
skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
skinByteBuffer.put((byte) 34);
skinByteBuffer.put((byte) 35);
skinByteBuffer.put((byte) 36);
skinByteBuffer.clear();
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer);
// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
// this.shaderProgram.enableVertexAttribute("a_position");
// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
// this.shaderProgram.enableVertexAttribute("a_position2");
// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
// this.shaderProgram.enableVertexAttribute("a_boneNumber");
final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6);
faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.faceBuffer = faceByteBuffer.asShortBuffer();
this.faceBuffer.put(0, (short) 0);
this.faceBuffer.put(1, (short) 1);
this.faceBuffer.put(2, (short) 2);
Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW);
final int glGetError = Gdx.gl.glGetError();
System.out.println(glGetError);
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram.begin();
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
this.shaderProgram.enableVertexAttribute("a_position");
this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
this.shaderProgram.enableVertexAttribute("a_position2");
this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
this.shaderProgram.enableVertexAttribute("a_boneNumber");
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer);
Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0);
// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3);
this.shaderProgram.end();
}
@Override
public void dispose() {
}
@Override
public void resize(final int width, final int height) {
}
public static final String vsSimple = "\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_position2;\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying float fragNumber;\r\n" + //
" void main() {\r\n" + //
" gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + //
" fragNumber = a_boneNumber;\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
" varying float fragNumber;\r\n" + //
" void main() {\r\n" + //
" if( fragNumber > 35.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + //
" } else if( fragNumber > 34.5 ) {\r\n" + //
" gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + //
" } else if( fragNumber > 33.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + //
" } else {\r\n" + //
" gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + //
" }\r\n" + //
" }\r\n";
private ShaderProgram shaderProgram;
private FloatBuffer vertexBuffer;
private ShortBuffer faceBuffer;
private static ShortBuffer wrapFaces(final int[] faces) {
final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder())
.asShortBuffer();
for (final int face : faces) {
wrapper.put((short) face);
}
wrapper.clear();
return wrapper;
}
private static ByteBuffer wrap(final byte[] skin) {
final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder());
wrapper.put(skin);
wrapper.clear();
return wrapper;
}
private static FloatBuffer wrap(final float[] positions) {
final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer();
wrapper.put(positions);
wrapper.clear();
return wrapper;
}
}

View File

@ -0,0 +1,244 @@
package com.etheller.warsmash;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.Shaders;
public class WarsmashTestGameTextureBuffer extends ApplicationAdapter {
private int arrayBuffer;
private int elementBuffer;
private int VAO;
private ShaderProgram shaderProgram;
private FloatBuffer vertexBuffer;
private ShortBuffer faceBuffer;
@Override
public void create() {
// ShaderProgram.pedantic = false;
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing
final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4);
tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final IntBuffer temp = tempByteBuffer.asIntBuffer();
Gdx.gl30.glGenVertexArrays(1, temp);
this.VAO = temp.get(0);
Gdx.gl30.glBindVertexArray(this.VAO);
System.out.println(vsSimple);
this.shaderProgram = new ShaderProgram(vsSimple, fsSimple);
if (!this.shaderProgram.isCompiled()) {
throw new IllegalStateException(this.shaderProgram.getLog());
}
this.arrayBuffer = Gdx.gl.glGenBuffer();
this.elementBuffer = Gdx.gl.glGenBuffer();
System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.vertexBuffer = vertexByteBuffer.asFloatBuffer();
this.vertexBuffer.put(0, -1f);
this.vertexBuffer.put(1, -1f);
this.vertexBuffer.put(2, 0);
this.vertexBuffer.put(3, 1f);
this.vertexBuffer.put(4, -1f);
this.vertexBuffer.put(5, 0);
this.vertexBuffer.put(6, 0f);
this.vertexBuffer.put(7, 1f);
this.vertexBuffer.put(8, 0);
Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer);
final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer();
vertexBuffer2.put(0, -1f);
vertexBuffer2.put(1, -1f);
vertexBuffer2.put(2, 0);
vertexBuffer2.put(3, 1f);
vertexBuffer2.put(4, -1f);
vertexBuffer2.put(5, 0);
vertexBuffer2.put(6, 0f);
vertexBuffer2.put(7, 1f);
vertexBuffer2.put(8, 0);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2);
final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3);
skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
skinByteBuffer.put((byte) 34);
skinByteBuffer.put((byte) 35);
skinByteBuffer.put((byte) 36);
skinByteBuffer.clear();
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer);
// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
// this.shaderProgram.enableVertexAttribute("a_position");
// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
// this.shaderProgram.enableVertexAttribute("a_position2");
// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
// this.shaderProgram.enableVertexAttribute("a_boneNumber");
// this.shaderProgram.setUniformi("u_boneMap", 15);
final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6);
faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.faceBuffer = faceByteBuffer.asShortBuffer();
this.faceBuffer.put(0, (short) 0);
this.faceBuffer.put(1, (short) 1);
this.faceBuffer.put(2, (short) 2);
Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW);
final int glGetError = Gdx.gl.glGetError();
System.out.println(glGetError);
final ByteBuffer vertex3ByteBuffer = ByteBuffer.allocateDirect(4 * 16);
vertex3ByteBuffer.order(ByteOrder.nativeOrder());
this.vertexTextureBuffer3 = vertex3ByteBuffer.asFloatBuffer();
this.vertexTextureBuffer3.put(0, 0.5f);
this.vertexTextureBuffer3.put(1, 0);
this.vertexTextureBuffer3.put(2, 0);
this.vertexTextureBuffer3.put(3, 0);
this.vertexTextureBuffer3.put(4, 0);
this.vertexTextureBuffer3.put(5, 1);
this.vertexTextureBuffer3.put(6, 0);
this.vertexTextureBuffer3.put(7, 0);
this.vertexTextureBuffer3.put(8, 0);
this.vertexTextureBuffer3.put(9, 0);
this.vertexTextureBuffer3.put(10, 1);
this.vertexTextureBuffer3.put(11, 0);
this.vertexTextureBuffer3.put(12, 0.0f);
this.vertexTextureBuffer3.put(13, 0.0f);
this.vertexTextureBuffer3.put(14, 0);
this.vertexTextureBuffer3.put(15, 1);
// this.vertexTextureBuffer = Gdx.gl.glGenBuffer();
this.vertexTexture = Gdx.gl.glGenTexture();
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE15);
// Gdx.gl.glBindBuffer(GL20.GL_TEXTURE_2D, this.vertexTextureBuffer);
// Gdx.gl.glBindBuffer(GL20.GL_TEXTURE_2D, 0);
Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, this.vertexTexture);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST);
Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_RGBA, 4, 1, 0, GL30.GL_RGBA, GL20.GL_FLOAT,
this.vertexTextureBuffer3);
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram.begin();
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
this.shaderProgram.enableVertexAttribute("a_position");
this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
this.shaderProgram.enableVertexAttribute("a_position2");
this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
this.shaderProgram.enableVertexAttribute("a_boneNumber");
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE15);
Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, this.vertexTexture);
Gdx.gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, 4, 1, GL30.GL_RGBA, GL20.GL_FLOAT,
this.vertexTextureBuffer3);
this.shaderProgram.setUniformi("u_boneMap", 15);
this.shaderProgram.setUniformf("u_vectorSize", 1f / 4);
this.shaderProgram.setUniformf("u_rowSize", 1);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer);
Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0);
// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3);
this.shaderProgram.end();
}
@Override
public void dispose() {
}
@Override
public void resize(final int width, final int height) {
}
public static final String boneTexture = ""//
+ " uniform samplerBuffer u_boneMap;\r\n" + //
// " uniform uint u_vectorSize;\r\n" + //
" uniform uint u_rowSize;\r\n" + //
" mat4 fetchMatrix(uint column, uint row) {\r\n" + //
// " column *= u_vectorSize * 4.0;\r\n" + //
// " row *= u_rowSize;\r\n" + //
" // Add in half texel to sample in the middle of the texel.\r\n" + //
" // Otherwise, since the sample is directly on the boundry, small floating point errors can cause the sample to get the wrong pixel.\r\n"
+ //
" // This is mostly noticable with NPOT textures, which the bone maps are.\r\n" + //
// " column += 0.5 * u_vectorSize;\r\n" + //
// " row += 0.5 * u_rowSize;\r\n" + //
" return mat4(texelFetch(u_boneMap, row * u_rowSize + column * 4),\r\n" + //
" texelFetch(u_boneMap, row * u_rowSize + column * 4 + 1),\r\n" + //
" texelFetch(u_boneMap, row * u_rowSize + column * 4 + 2),\r\n" + //
" texelFetch(u_boneMap, row * u_rowSize + column * 4 + 3);\r\n" + //
" }";
public static final String vsSimple = "\r\n" + //
"\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_position2;\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying float fragNumber;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void main() {\r\n" + //
" mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + //
" if( a_boneNumber <= 34.5 ) {\r\n" + //
" gl_Position = vec4(a_position2.x * bone[0][0], a_position2.y, a_position2.z, 1.0);\r\n" + //
" } else {\r\n" + //
" gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + //
" }\r\n" + //
" fragNumber = a_boneNumber;\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
" varying float fragNumber;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void main() {\r\n" + //
" mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + //
" if( fragNumber > 35.5 ) {\r\n" + //
" gl_FragColor = bone[0];//vec4(1.0, 0.0, 0.0, 1.0);\r\n" + //
" } else if( fragNumber > 34.5 ) {\r\n" + //
" gl_FragColor = bone[1];//vec4(0.0, 1.0, 1.0, 1.0);\r\n" + //
" } else if( fragNumber > 33.5 ) {\r\n" + //
" gl_FragColor = bone[2];//vec4(1.0, 0.0, 1.0, 1.0);\r\n" + //
" } else {\r\n" + //
" gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + //
" }\r\n" + //
" }\r\n";
private int vertexTexture;
private int vertexTextureBuffer;
private FloatBuffer vertexTextureBuffer3;
}

View File

@ -0,0 +1,203 @@
package com.etheller.warsmash;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.Shaders;
import com.etheller.warsmash.viewer5.gl.DataTexture;
public class WarsmashTestGameTextureBuffer2 extends ApplicationAdapter {
private int arrayBuffer;
private int elementBuffer;
private int VAO;
@Override
public void create() {
// ShaderProgram.pedantic = false;
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing
final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4);
tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final IntBuffer temp = tempByteBuffer.asIntBuffer();
Gdx.gl30.glGenVertexArrays(1, temp);
this.VAO = temp.get(0);
Gdx.gl30.glBindVertexArray(this.VAO);
System.out.println(vsSimple);
this.shaderProgram = new ShaderProgram(vsSimple, fsSimple);
if (!this.shaderProgram.isCompiled()) {
throw new IllegalStateException(this.shaderProgram.getLog());
}
this.arrayBuffer = Gdx.gl.glGenBuffer();
this.elementBuffer = Gdx.gl.glGenBuffer();
System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.vertexBuffer = vertexByteBuffer.asFloatBuffer();
this.vertexBuffer.put(0, -1f);
this.vertexBuffer.put(1, -1f);
this.vertexBuffer.put(2, 0);
this.vertexBuffer.put(3, 1f);
this.vertexBuffer.put(4, -1f);
this.vertexBuffer.put(5, 0);
this.vertexBuffer.put(6, 0f);
this.vertexBuffer.put(7, 1f);
this.vertexBuffer.put(8, 0);
Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer);
final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9);
vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer();
vertexBuffer2.put(0, -1f);
vertexBuffer2.put(1, -1f);
vertexBuffer2.put(2, 0);
vertexBuffer2.put(3, 1f);
vertexBuffer2.put(4, -1f);
vertexBuffer2.put(5, 0);
vertexBuffer2.put(6, 0f);
vertexBuffer2.put(7, 1f);
vertexBuffer2.put(8, 0);
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2);
final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3);
skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
skinByteBuffer.put((byte) 34);
skinByteBuffer.put((byte) 35);
skinByteBuffer.put((byte) 36);
skinByteBuffer.clear();
Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer);
// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
// this.shaderProgram.enableVertexAttribute("a_position");
// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
// this.shaderProgram.enableVertexAttribute("a_position2");
// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
// this.shaderProgram.enableVertexAttribute("a_boneNumber");
// this.shaderProgram.setUniformi("u_boneMap", 15);
final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6);
faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
this.faceBuffer = faceByteBuffer.asShortBuffer();
this.faceBuffer.put(0, (short) 0);
this.faceBuffer.put(1, (short) 1);
this.faceBuffer.put(2, (short) 2);
Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW);
final int glGetError = Gdx.gl.glGetError();
System.out.println(glGetError);
final ByteBuffer vertex3ByteBuffer = ByteBuffer.allocateDirect(4 * 16);
vertex3ByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final FloatBuffer vertexBuffer3 = vertex3ByteBuffer.asFloatBuffer();
vertexBuffer3.put(0, 1);
vertexBuffer3.put(1, 0);
vertexBuffer3.put(2, 0);
vertexBuffer3.put(3, 0);
vertexBuffer3.put(4, 0);
vertexBuffer3.put(5, 1);
vertexBuffer3.put(6, 0);
vertexBuffer3.put(7, 0);
vertexBuffer3.put(8, 0);
vertexBuffer3.put(9, 0);
vertexBuffer3.put(10, 1);
vertexBuffer3.put(11, 0);
vertexBuffer3.put(12, 0.0f);
vertexBuffer3.put(13, 0.0f);
vertexBuffer3.put(14, 0);
vertexBuffer3.put(15, 1);
this.dataTexture = new DataTexture(Gdx.gl, 4, 4, 1);
this.dataTexture.bindAndUpdate(vertexBuffer3);
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// Gdx.gl30.glBindVertexArray(this.VAO);
this.shaderProgram.begin();
this.dataTexture.bind(15);
Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer);
this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
this.shaderProgram.enableVertexAttribute("a_position");
this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9);
this.shaderProgram.enableVertexAttribute("a_position2");
this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2);
this.shaderProgram.enableVertexAttribute("a_boneNumber");
this.shaderProgram.setUniformi("u_boneMap", 15);
this.shaderProgram.setUniformf("u_vectorSize", 1f / this.dataTexture.getWidth());
this.shaderProgram.setUniformf("u_rowSize", 1);
Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer);
Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer);
Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0);
// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3);
this.shaderProgram.end();
}
@Override
public void dispose() {
}
@Override
public void resize(final int width, final int height) {
}
public static final String vsSimple = "\r\n" + //
"\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_position2;\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying float fragNumber;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void main() {\r\n" + //
" mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + //
" gl_Position = bone * vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + //
" fragNumber = a_boneNumber;\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
" varying float fragNumber;\r\n" + //
" void main() {\r\n" + //
" if( fragNumber > 35.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + //
" } else if( fragNumber > 34.5 ) {\r\n" + //
" gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + //
" } else if( fragNumber > 33.5 ) {\r\n" + //
" gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + //
" } else {\r\n" + //
" gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + //
" }\r\n" + //
" }\r\n";
private ShaderProgram shaderProgram;
private FloatBuffer vertexBuffer;
private ShortBuffer faceBuffer;
private DataTexture dataTexture;
}

View File

@ -0,0 +1,56 @@
package com.etheller.warsmash;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.util.ImageUtils;
public class WarsmashTestMyTextureGame extends ApplicationAdapter {
private DataSource codebase;
private Texture texture;
private SpriteBatch batch;
@Override
public void create() {
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127");
final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\Test");
final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor(".");
this.codebase = new CompoundDataSourceDescriptor(
Arrays.<DataSourceDescriptor>asList(war3mpq, testingFolder, currentFolder)).createDataSource();
try {
this.texture = ImageUtils
.getTexture(ImageIO.read(this.codebase.getResourceAsStream("Textures\\Dust3x.blp")));
}
catch (final IOException e) {
e.printStackTrace();
}
Gdx.gl.glClearColor(0, 0, 0, 1);
this.batch = new SpriteBatch();
this.batch.enableBlending();
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
this.batch.begin();
this.batch.draw(this.texture, 20, 20, 256, 256);
this.batch.end();
}
}

View File

@ -15,10 +15,10 @@ import java.util.Set;
public class CompoundDataSource implements DataSource {
private final List<DataSource> mpqList = new ArrayList<>();
public CompoundDataSource(final List<DataSourceDescriptor> dataSourceDescriptors) {
if (dataSourceDescriptors != null) {
for (final DataSourceDescriptor descriptor : dataSourceDescriptors) {
this.mpqList.add(descriptor.createDataSource());
public CompoundDataSource(final List<DataSource> dataSources) {
if (dataSources != null) {
for (final DataSource dataSource : dataSources) {
this.mpqList.add(dataSource);
}
}
}

View File

@ -0,0 +1,27 @@
package com.etheller.warsmash.datasources;
import java.util.ArrayList;
import java.util.List;
public class CompoundDataSourceDescriptor implements DataSourceDescriptor {
private final List<DataSourceDescriptor> dataSourceDescriptors;
public CompoundDataSourceDescriptor(final List<DataSourceDescriptor> dataSourceDescriptors) {
this.dataSourceDescriptors = dataSourceDescriptors;
}
@Override
public DataSource createDataSource() {
final List<DataSource> dataSources = new ArrayList<>();
for (final DataSourceDescriptor descriptor : this.dataSourceDescriptors) {
dataSources.add(descriptor.createDataSource());
}
return new CompoundDataSource(dataSources);
}
@Override
public String getDisplayName() {
return "CompoundDataSourceDescriptor";
}
}

View File

@ -0,0 +1,143 @@
package com.etheller.warsmash.datasources;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import mpq.ArchivedFile;
import mpq.ArchivedFileExtractor;
import mpq.ArchivedFileStream;
import mpq.HashLookup;
import mpq.MPQArchive;
import mpq.MPQException;
public class MpqDataSource implements DataSource {
private final MPQArchive archive;
private final SeekableByteChannel inputChannel;
private final ArchivedFileExtractor extractor = new ArchivedFileExtractor();
public MpqDataSource(final MPQArchive archive, final SeekableByteChannel inputChannel) {
this.archive = archive;
this.inputChannel = inputChannel;
}
public MPQArchive getArchive() {
return this.archive;
}
public SeekableByteChannel getInputChannel() {
return this.inputChannel;
}
@Override
public InputStream getResourceAsStream(final String filepath) throws IOException {
ArchivedFile file = null;
try {
file = this.archive.lookupHash2(new HashLookup(filepath));
}
catch (final MPQException exc) {
if (exc.getMessage().equals("lookup not found")) {
return null;
}
else {
throw new IOException(exc);
}
}
final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file);
final InputStream newInputStream = Channels.newInputStream(stream);
return newInputStream;
}
@Override
public File getFile(final String filepath) throws IOException {
// TODO Auto-generated method stub
// System.out.println("getting it from the outside: " +
// filepath);
ArchivedFile file = null;
try {
file = this.archive.lookupHash2(new HashLookup(filepath));
}
catch (final MPQException exc) {
if (exc.getMessage().equals("lookup not found")) {
return null;
}
else {
throw new IOException(exc);
}
}
final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file);
final InputStream newInputStream = Channels.newInputStream(stream);
String tmpdir = System.getProperty("java.io.tmpdir");
if (!tmpdir.endsWith(File.separator)) {
tmpdir += File.separator;
}
final String tempDir = tmpdir + "RMSExtract/";
final File tempProduct = new File(tempDir + filepath.replace('\\', File.separatorChar));
tempProduct.delete();
tempProduct.getParentFile().mkdirs();
Files.copy(newInputStream, tempProduct.toPath());
tempProduct.deleteOnExit();
return tempProduct;
}
@Override
public boolean has(final String filepath) {
try {
this.archive.lookupPath(filepath);
return true;
}
catch (final MPQException exc) {
if (exc.getMessage().equals("lookup not found")) {
return false;
}
else {
throw new RuntimeException(exc);
}
}
}
@Override
public Collection<String> getListfile() {
try {
final Set<String> listfile = new HashSet<>();
ArchivedFile listfileContents;
listfileContents = this.archive.lookupHash2(new HashLookup("(listfile)"));
final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor,
listfileContents);
final InputStream newInputStream = Channels.newInputStream(stream);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(newInputStream))) {
String line;
while ((line = reader.readLine()) != null) {
listfile.add(line);
}
}
catch (final IOException exc) {
throw new RuntimeException(exc);
}
return listfile;
}
catch (final MPQException exc) {
if (exc.getMessage().equals("lookup not found")) {
return null;
}
else {
throw new RuntimeException(exc);
}
}
}
@Override
public void close() throws IOException {
this.inputChannel.close();
}
}

View File

@ -0,0 +1,75 @@
package com.etheller.warsmash.datasources;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import mpq.MPQArchive;
import mpq.MPQException;
public class MpqDataSourceDescriptor implements DataSourceDescriptor {
/**
* Generated serial id
*/
private static final long serialVersionUID = 8424254987711783598L;
private final String mpqFilePath;
public MpqDataSourceDescriptor(final String mpqFilePath) {
this.mpqFilePath = mpqFilePath;
}
@Override
public DataSource createDataSource() {
try {
SeekableByteChannel sbc;
sbc = Files.newByteChannel(Paths.get(this.mpqFilePath), EnumSet.of(StandardOpenOption.READ));
return new MpqDataSource(new MPQArchive(sbc), sbc);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
catch (final MPQException e) {
throw new RuntimeException(e);
}
}
@Override
public String getDisplayName() {
return "MPQ Archive: " + this.mpqFilePath;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + ((this.mpqFilePath == null) ? 0 : this.mpqFilePath.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MpqDataSourceDescriptor other = (MpqDataSourceDescriptor) obj;
if (this.mpqFilePath == null) {
if (other.mpqFilePath != null) {
return false;
}
}
else if (!this.mpqFilePath.equals(other.mpqFilePath)) {
return false;
}
return true;
}
}

View File

@ -88,4 +88,8 @@ public class Bone extends GenericObject {
public int getGeosetAnimationId() {
return this.geosetAnimationId;
}
public int getGeosetId() {
return this.geosetId;
}
}

View File

@ -109,4 +109,24 @@ public class Sequence implements MdlxBlock {
public int getFlags() {
return this.flags;
}
public String getName() {
return this.name;
}
public float getRarity() {
return this.rarity;
}
public float getMoveSpeed() {
return this.moveSpeed;
}
public long getSyncPoint() {
return this.syncPoint;
}
public Extent getExtent() {
return this.extent;
}
}

View File

@ -0,0 +1,125 @@
package com.etheller.warsmash.parsers.w3x;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import com.etheller.warsmash.datasources.CompoundDataSource;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.MpqDataSource;
import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo;
import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData;
import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.google.common.io.LittleEndianDataInputStream;
import mpq.MPQArchive;
import mpq.MPQException;
/**
* Warcraft 3 map (W3X and W3M).
*/
public class War3Map implements DataSource {
private CompoundDataSource dataSource;
private MpqDataSource internalMpqContentsDataSource;
public War3Map(final DataSource dataSource, final String mapFileName) {
try {
// Slightly complex. Here's the theory:
// 1.) Copy map into RAM
// 2.) Setup a Data Source that will read assets
// from either the map or the game, giving the map priority.
SeekableByteChannel sbc;
try (InputStream mapStream = dataSource.getResourceAsStream(mapFileName)) {
final byte[] mapData = IOUtils.toByteArray(mapStream);
sbc = new SeekableInMemoryByteChannel(mapData);
this.internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc);
this.dataSource = new CompoundDataSource(Arrays.asList(dataSource, this.internalMpqContentsDataSource));
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
catch (final MPQException e) {
throw new RuntimeException(e);
}
}
public War3MapW3i readMapInformation() throws IOException {
War3MapW3i mapInfo;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(
this.dataSource.getResourceAsStream("war3map.w3i"))) {
mapInfo = new War3MapW3i(stream);
}
return mapInfo;
}
public War3MapW3e readEnvironment() throws IOException {
War3MapW3e environment;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(
this.dataSource.getResourceAsStream("war3map.w3e"))) {
environment = new War3MapW3e(stream);
}
return environment;
}
public War3MapDoo readDoodads() throws IOException {
War3MapDoo doodadsFile;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(
this.dataSource.getResourceAsStream("war3map.doo"))) {
doodadsFile = new War3MapDoo(stream);
}
return doodadsFile;
}
public War3MapUnitsDoo readUnits() throws IOException {
War3MapUnitsDoo unitsFile;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(
this.dataSource.getResourceAsStream("war3mapUnits.doo"))) {
unitsFile = new War3MapUnitsDoo(stream);
}
return unitsFile;
}
public Warcraft3MapObjectData readModifications() throws IOException {
final Warcraft3MapObjectData changes = Warcraft3MapObjectData.load(this.dataSource, true);
return changes;
}
@Override
public InputStream getResourceAsStream(final String filepath) throws IOException {
return this.dataSource.getResourceAsStream(filepath);
}
@Override
public File getFile(final String filepath) throws IOException {
return this.dataSource.getFile(filepath);
}
@Override
public boolean has(final String filepath) {
return this.dataSource.has(filepath);
}
@Override
public Collection<String> getListfile() {
return this.internalMpqContentsDataSource.getListfile();
}
@Override
public void close() throws IOException {
this.dataSource.close();
}
public CompoundDataSource getCompoundDataSource() {
return this.dataSource;
}
}

View File

@ -0,0 +1,157 @@
package com.etheller.warsmash.parsers.w3x.doo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Doodad {
private War3ID id;
private int variation;
private final float[] location = new float[3];
private float angle;
private final float[] scale = { 1f, 1f, 1f };
private short flags; // short to store unsigned byte, java problem
private short life; // short to store unsigned byte, java problem
private long itemTable = -1; // long to store unsigned int32, java problem
private final List<RandomItemSet> itemSets = new ArrayList<>();
private int editorId;
private final short[] u1 = new short[8]; // short to store unsigned byte, java problem
public void load(final LittleEndianDataInputStream stream, final int version) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.variation = stream.readInt();
ParseUtils.readFloatArray(stream, this.location);
this.angle = stream.readFloat();
ParseUtils.readFloatArray(stream, this.scale);
this.flags = ParseUtils.readUInt8(stream);
this.life = ParseUtils.readUInt8(stream);
if (version > 7) {
this.itemTable = ParseUtils.readUInt32(stream);
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final RandomItemSet itemSet = new RandomItemSet();
itemSet.load(stream);
this.itemSets.add(itemSet);
}
}
this.editorId = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
;
stream.writeInt(this.variation);
ParseUtils.writeFloatArray(stream, this.location);
stream.writeFloat(this.angle);
ParseUtils.writeFloatArray(stream, this.scale);
ParseUtils.writeUInt8(stream, this.flags);
ParseUtils.writeUInt8(stream, this.life);
if (version > 7) {
ParseUtils.writeUInt32(stream, this.itemTable);
ParseUtils.writeUInt32(stream, this.itemSets.size());
for (final RandomItemSet itemSet : this.itemSets) {
itemSet.save(stream);
}
}
stream.writeInt(this.editorId);
}
public int getByteLength(final int version) {
int size = 42;
if (version > 7) {
size += 8;
for (final RandomItemSet itemSet : this.itemSets) {
size += itemSet.getByteLength();
}
}
return size;
}
public War3ID getId() {
return this.id;
}
public void setId(final War3ID id) {
this.id = id;
}
public int getVariation() {
return this.variation;
}
public void setVariation(final int variation) {
this.variation = variation;
}
public float getAngle() {
return this.angle;
}
public void setAngle(final float angle) {
this.angle = angle;
}
public short getFlags() {
return this.flags;
}
public void setFlags(final short flags) {
this.flags = flags;
}
public short getLife() {
return this.life;
}
public void setLife(final short life) {
this.life = life;
}
public long getItemTable() {
return this.itemTable;
}
public void setItemTable(final long itemTable) {
this.itemTable = itemTable;
}
public int getEditorId() {
return this.editorId;
}
public void setEditorId(final int editorId) {
this.editorId = editorId;
}
public float[] getLocation() {
return this.location;
}
public float[] getScale() {
return this.scale;
}
public List<RandomItemSet> getItemSets() {
return this.itemSets;
}
public short[] getU1() {
return this.u1;
}
}

View File

@ -0,0 +1,23 @@
package com.etheller.warsmash.parsers.w3x.doo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomItem {
private War3ID id;
private int chance;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.chance = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.chance);
}
}

View File

@ -0,0 +1,34 @@
package com.etheller.warsmash.parsers.w3x.doo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomItemSet {
private final List<RandomItem> items = new ArrayList<>();
public void load(final LittleEndianDataInputStream stream) throws IOException {
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final RandomItem item = new RandomItem();
item.load(stream);
this.items.add(item);
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.items.size());
for (final RandomItem item : this.items) {
item.save(stream);
}
}
public int getByteLength() {
return 4 + (this.items.size() * 8);
}
}

View File

@ -0,0 +1,54 @@
package com.etheller.warsmash.parsers.w3x.doo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A terrain doodad.
*
* This type of doodad works much like cliffs. It uses the height of the
* terrain, and gets affected by the ground heightmap. It cannot be manipulated
* in any way in the World Editor once placed. Indeed, the only way to change it
* is to remove it by changing cliffs around it.
*/
public class TerrainDoodad {
private War3ID id;
private long u1;
private final long[] location = new long[2];
public void load(final LittleEndianDataInputStream stream, final int version) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.u1 = ParseUtils.readUInt32(stream);
ParseUtils.readUInt32Array(stream, this.location);
}
public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
ParseUtils.writeUInt32(stream, this.u1);
ParseUtils.writeUInt32Array(stream, this.location);
}
public War3ID getId() {
return this.id;
}
public long getU1() {
return this.u1;
}
public long[] getLocation() {
return this.location;
}
public void setId(final War3ID id) {
this.id = id;
}
public void setU1(final long u1) {
this.u1 = u1;
}
}

View File

@ -0,0 +1,108 @@
package com.etheller.warsmash.parsers.w3x.doo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* war3map.doo - the doodad and destructible file.
*/
public class War3MapDoo {
private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3do");
private int version = 0;
private final short[] u1 = new short[4];
private final List<Doodad> doodads = new ArrayList<>();
private final short[] u2 = new short[4];
private final List<TerrainDoodad> terrainDoodads = new ArrayList<>();
public War3MapDoo(final LittleEndianDataInputStream stream) throws IOException {
if (stream != null) {
this.load(stream);
}
}
private boolean load(final LittleEndianDataInputStream stream) throws IOException {
final War3ID firstId = ParseUtils.readWar3ID(stream);
if (!MAGIC_NUMBER.equals(firstId)) {
return false;
}
this.version = stream.readInt();
ParseUtils.readUInt8Array(stream, this.u1);
for (int i = 0, l = stream.readInt(); i < l; i++) {
final Doodad doodad = new Doodad();
doodad.load(stream, this.version);
this.doodads.add(doodad);
}
ParseUtils.readUInt8Array(stream, this.u2);
for (int i = 0, l = stream.readInt(); i < l; i++) {
final TerrainDoodad terrainDoodad = new TerrainDoodad();
terrainDoodad.load(stream, this.version);
this.terrainDoodads.add(terrainDoodad);
}
return true;
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, MAGIC_NUMBER);
stream.writeInt(this.version);
ParseUtils.writeUInt8Array(stream, this.u1);
ParseUtils.writeUInt32(stream, this.doodads.size());
for (final Doodad doodad : this.doodads) {
doodad.save(stream, this.version);
}
ParseUtils.writeUInt8Array(stream, this.u2);
ParseUtils.writeUInt32(stream, this.terrainDoodads.size());
for (final TerrainDoodad terrainDoodad : this.terrainDoodads) {
terrainDoodad.save(stream, this.version);
}
}
public int getByteLength() {
int size = 24 + (this.terrainDoodads.size() * 16);
for (final Doodad doodad : this.doodads) {
size += doodad.getByteLength(this.version);
}
return size;
}
public int getVersion() {
return this.version;
}
public short[] getU1() {
return this.u1;
}
public List<Doodad> getDoodads() {
return this.doodads;
}
public short[] getU2() {
return this.u2;
}
public List<TerrainDoodad> getTerrainDoodads() {
return this.terrainDoodads;
}
}

View File

@ -0,0 +1,166 @@
package com.etheller.warsmash.parsers.w3x.objectdata;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.StandardObjectData;
import com.etheller.warsmash.units.StandardObjectData.WarcraftData;
import com.etheller.warsmash.units.custom.WTSFile;
import com.etheller.warsmash.units.custom.War3ObjectDataChangeset;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.WorldEditStrings;
import com.google.common.io.LittleEndianDataInputStream;
public final class Warcraft3MapObjectData {
private final MutableObjectData units;
private final MutableObjectData items;
private final MutableObjectData destructibles;
private final MutableObjectData doodads;
private final MutableObjectData abilities;
private final MutableObjectData buffs;
private final MutableObjectData upgrades;
private final List<MutableObjectData> datas;
private transient Map<WorldEditorDataType, MutableObjectData> typeToData = new HashMap<>();
public Warcraft3MapObjectData(final MutableObjectData units, final MutableObjectData items,
final MutableObjectData destructibles, final MutableObjectData doodads, final MutableObjectData abilities,
final MutableObjectData buffs, final MutableObjectData upgrades) {
this.units = units;
this.items = items;
this.destructibles = destructibles;
this.doodads = doodads;
this.abilities = abilities;
this.buffs = buffs;
this.upgrades = upgrades;
this.datas = new ArrayList<>();
this.datas.add(units);
this.datas.add(items);
this.datas.add(destructibles);
this.datas.add(doodads);
this.datas.add(abilities);
this.datas.add(buffs);
this.datas.add(upgrades);
for (final MutableObjectData data : this.datas) {
this.typeToData.put(data.getWorldEditorDataType(), data);
}
}
public MutableObjectData getDataByType(final WorldEditorDataType type) {
return this.typeToData.get(type);
}
public MutableObjectData getUnits() {
return this.units;
}
public MutableObjectData getItems() {
return this.items;
}
public MutableObjectData getDestructibles() {
return this.destructibles;
}
public MutableObjectData getDoodads() {
return this.doodads;
}
public MutableObjectData getAbilities() {
return this.abilities;
}
public MutableObjectData getBuffs() {
return this.buffs;
}
public MutableObjectData getUpgrades() {
return this.upgrades;
}
public List<MutableObjectData> getDatas() {
return this.datas;
}
public static Warcraft3MapObjectData load(final DataSource dataSource, final boolean inlineWTS) throws IOException {
final StandardObjectData standardObjectData = new StandardObjectData(dataSource);
final WarcraftData standardUnits = standardObjectData.getStandardUnits();
final WarcraftData standardItems = standardObjectData.getStandardItems();
final WarcraftData standardDoodads = standardObjectData.getStandardDoodads();
final WarcraftData standardDestructables = standardObjectData.getStandardDestructables();
final WarcraftData abilities = standardObjectData.getStandardAbilities();
final WarcraftData standardAbilityBuffs = standardObjectData.getStandardAbilityBuffs();
final WarcraftData standardUpgrades = standardObjectData.getStandardUpgrades();
final DataTable standardUnitMeta = standardObjectData.getStandardUnitMeta();
final DataTable standardDoodadMeta = standardObjectData.getStandardDoodadMeta();
final DataTable standardDestructableMeta = standardObjectData.getStandardDestructableMeta();
final DataTable abilityMeta = standardObjectData.getStandardAbilityMeta();
final DataTable standardAbilityBuffMeta = standardObjectData.getStandardAbilityBuffMeta();
final DataTable standardUpgradeMeta = standardObjectData.getStandardUpgradeMeta();
final War3ObjectDataChangeset unitChangeset = new War3ObjectDataChangeset('u');
final War3ObjectDataChangeset itemChangeset = new War3ObjectDataChangeset('t');
final War3ObjectDataChangeset doodadChangeset = new War3ObjectDataChangeset('d');
final War3ObjectDataChangeset destructableChangeset = new War3ObjectDataChangeset('b');
final War3ObjectDataChangeset abilityChangeset = new War3ObjectDataChangeset('a');
final War3ObjectDataChangeset buffChangeset = new War3ObjectDataChangeset('h');
final War3ObjectDataChangeset upgradeChangeset = new War3ObjectDataChangeset('q');
final WTSFile wts = new WTSFile(dataSource.getResourceAsStream("war3map.wts"));
if (dataSource.has("war3map.w3u")) {
unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts,
inlineWTS);
}
if (dataSource.has("war3map.w3t")) {
itemChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3t")), wts,
inlineWTS);
}
if (dataSource.has("war3map.w3d")) {
doodadChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3d")), wts,
inlineWTS);
}
if (dataSource.has("war3map.w3b")) {
destructableChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3b")),
wts, inlineWTS);
}
if (dataSource.has("war3map.w3a")) {
abilityChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3a")), wts,
inlineWTS);
}
if (dataSource.has("war3map.w3h")) {
buffChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3h")), wts,
inlineWTS);
}
if (dataSource.has("war3map.w3q")) {
upgradeChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3q")), wts,
inlineWTS);
}
final WorldEditStrings worldEditStrings = standardObjectData.getWorldEditStrings();
final MutableObjectData unitData = new MutableObjectData(worldEditStrings, WorldEditorDataType.UNITS,
standardUnits, standardUnitMeta, unitChangeset);
final MutableObjectData itemData = new MutableObjectData(worldEditStrings, WorldEditorDataType.ITEM,
standardItems, standardUnitMeta, itemChangeset);
final MutableObjectData doodadData = new MutableObjectData(worldEditStrings, WorldEditorDataType.DOODADS,
standardDoodads, standardDoodadMeta, doodadChangeset);
final MutableObjectData destructableData = new MutableObjectData(worldEditStrings,
WorldEditorDataType.DESTRUCTIBLES, standardDestructables, standardDestructableMeta,
destructableChangeset);
final MutableObjectData abilityData = new MutableObjectData(worldEditStrings, WorldEditorDataType.ABILITIES,
abilities, abilityMeta, abilityChangeset);
final MutableObjectData buffData = new MutableObjectData(worldEditStrings, WorldEditorDataType.BUFFS_EFFECTS,
standardAbilityBuffs, standardAbilityBuffMeta, buffChangeset);
final MutableObjectData upgradeData = new MutableObjectData(worldEditStrings, WorldEditorDataType.UPGRADES,
standardUpgrades, standardUpgradeMeta, upgradeChangeset);
return new Warcraft3MapObjectData(unitData, itemData, destructableData, doodadData, abilityData, buffData,
upgradeData);
}
}

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A dropped item.
*/
public class DroppedItem {
private War3ID id;
private int chance;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.chance = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.chance);
}
}

View File

@ -0,0 +1,38 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A dropped item set.
*/
public class DroppedItemSet {
private final List<DroppedItem> items = new ArrayList<>();
public void load(final LittleEndianDataInputStream stream) throws IOException {
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final DroppedItem item = new DroppedItem();
item.load(stream);
this.items.add(item);
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.items.size());
for (final DroppedItem item : this.items) {
item.save(stream);
}
}
public int getByteLength() {
return 4 + (this.items.size() * 8);
}
}

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* An inventory item.
*/
public class InventoryItem {
private int slot;
private War3ID id;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.slot = stream.readInt();
this.id = ParseUtils.readWar3ID(stream);
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.slot);
ParseUtils.writeWar3ID(stream, this.id);
}
}

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class ModifiedAbility {
private War3ID id;
private int activeForAutocast = 0;
private int heroLevel = 1;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.activeForAutocast = stream.readInt();
this.heroLevel = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.activeForAutocast);
stream.writeInt(this.heroLevel);
}
}

View File

@ -0,0 +1,23 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomUnit {
private War3ID id;
private int chance;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.chance = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.chance);
}
}

View File

@ -0,0 +1,428 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Unit {
private War3ID id;
private int variation;
private final float[] location = new float[3];
private float angle;
private final float[] scale = new float[3];
private short flags;
private int player;
private int unknown;
private int hitpoints = -1;
private int mana = -1;
/**
* @since 8
*/
private int droppedItemTable = 0;
private final List<DroppedItemSet> droppedItemSets = new ArrayList<>();
private int goldAmount;
private float targetAcquisition;
private int heroLevel;
/**
* @since 8
*/
private int heroStrength;
/**
* @since 8
*/
private int heroAgility;
/**
* @since 8
*/
private int heroIntelligence;
private final List<InventoryItem> itemsInInventory = new ArrayList<>();
private final List<ModifiedAbility> modifiedAbilities = new ArrayList<>();
private int randomFlag;
private final short[] level = new short[3];
private short itemClass;
private long unitGroup;
private long positionInGroup;
private final List<RandomUnit> randomUnitTables = new ArrayList<>();
private int customTeamColor;
private int waygate;
private int creationNumber;
public void load(final LittleEndianDataInputStream stream, final int version) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.variation = stream.readInt();
ParseUtils.readFloatArray(stream, this.location);
this.angle = stream.readFloat();
ParseUtils.readFloatArray(stream, this.scale);
this.flags = ParseUtils.readUInt8(stream);
this.player = stream.readInt();
this.unknown = ParseUtils.readUInt16(stream);
this.hitpoints = stream.readInt();
this.mana = stream.readInt();
if (version > 7) {
this.droppedItemTable = stream.readInt();
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final DroppedItemSet set = new DroppedItemSet();
set.load(stream);
this.droppedItemSets.add(set);
}
this.goldAmount = stream.readInt();
this.targetAcquisition = stream.readFloat();
this.heroLevel = stream.readInt();
if (version > 7) {
this.heroStrength = stream.readInt();
this.heroAgility = stream.readInt();
this.heroIntelligence = stream.readInt();
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final InventoryItem item = new InventoryItem();
item.load(stream);
this.itemsInInventory.add(item);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final ModifiedAbility modifiedAbility = new ModifiedAbility();
modifiedAbility.load(stream);
this.modifiedAbilities.add(modifiedAbility);
}
this.randomFlag = stream.readInt();
if (this.randomFlag == 0) {
ParseUtils.readUInt8Array(stream, this.level);
this.itemClass = ParseUtils.readUInt8(stream);
}
else if (this.randomFlag == 1) {
this.unitGroup = ParseUtils.readUInt32(stream);
this.positionInGroup = ParseUtils.readUInt32(stream);
}
else if (this.randomFlag == 2) {
for (int i = 0, l = stream.readInt(); i < l; i++) {
final RandomUnit randomUnit = new RandomUnit();
randomUnit.load(stream);
this.randomUnitTables.add(randomUnit);
}
}
this.customTeamColor = stream.readInt();
this.waygate = stream.readInt();
this.creationNumber = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.variation);
ParseUtils.writeFloatArray(stream, this.location);
stream.writeFloat(this.angle);
ParseUtils.writeFloatArray(stream, this.scale);
ParseUtils.writeUInt8(stream, this.flags);
stream.writeInt(this.player);
ParseUtils.writeUInt16(stream, this.unknown);
stream.writeInt(this.hitpoints);
stream.writeInt(this.mana);
if (version > 7) {
stream.writeInt(this.droppedItemTable);
}
stream.writeInt(this.droppedItemSets.size());
for (final DroppedItemSet droppedItemSet : this.droppedItemSets) {
droppedItemSet.save(stream);
}
stream.writeInt(this.goldAmount);
stream.writeFloat(this.targetAcquisition);
stream.writeInt(this.heroLevel);
if (version > 7) {
stream.writeInt(this.heroStrength);
stream.writeInt(this.heroAgility);
stream.writeInt(this.heroIntelligence);
}
stream.writeInt(this.itemsInInventory.size());
for (final InventoryItem itemInInventory : this.itemsInInventory) {
itemInInventory.save(stream);
}
stream.writeInt(this.modifiedAbilities.size());
for (final ModifiedAbility modifiedAbility : this.modifiedAbilities) {
modifiedAbility.save(stream);
}
stream.writeInt(this.randomFlag);
if (this.randomFlag == 0) {
ParseUtils.writeUInt8Array(stream, this.level);
ParseUtils.writeUInt8(stream, this.itemClass);
}
else if (this.randomFlag == 1) {
ParseUtils.writeUInt32(stream, this.unitGroup);
ParseUtils.writeUInt32(stream, this.positionInGroup);
}
else if (this.randomFlag == 2) {
stream.writeInt(this.randomUnitTables.size());
for (final RandomUnit randomUnitTable : this.randomUnitTables) {
randomUnitTable.save(stream);
}
}
stream.writeInt(this.customTeamColor);
stream.writeInt(this.waygate);
stream.writeInt(this.creationNumber);
}
public int getByteLength(final int version) {
int size = 91;
if (version > 7) {
size += 16;
}
for (final DroppedItemSet droppedItemSet : this.droppedItemSets) {
size += droppedItemSet.getByteLength();
}
size += this.itemsInInventory.size() * 8;
size += this.modifiedAbilities.size() * 12;
if (this.randomFlag == 0) {
size += 4;
}
else if (this.randomFlag == 1) {
size += 8;
}
else if (this.randomFlag == 2) {
size += 4 + (this.randomUnitTables.size() * 8);
}
return size;
}
public War3ID getId() {
return this.id;
}
public int getVariation() {
return this.variation;
}
public float[] getLocation() {
return this.location;
}
public float getAngle() {
return this.angle;
}
public float[] getScale() {
return this.scale;
}
public short getFlags() {
return this.flags;
}
public int getPlayer() {
return this.player;
}
public int getUnknown() {
return this.unknown;
}
public int getHitpoints() {
return this.hitpoints;
}
public int getMana() {
return this.mana;
}
public int getDroppedItemTable() {
return this.droppedItemTable;
}
public List<DroppedItemSet> getDroppedItemSets() {
return this.droppedItemSets;
}
public int getGoldAmount() {
return this.goldAmount;
}
public float getTargetAcquisition() {
return this.targetAcquisition;
}
public int getHeroLevel() {
return this.heroLevel;
}
public int getHeroStrength() {
return this.heroStrength;
}
public int getHeroAgility() {
return this.heroAgility;
}
public int getHeroIntelligence() {
return this.heroIntelligence;
}
public List<InventoryItem> getItemsInInventory() {
return this.itemsInInventory;
}
public List<ModifiedAbility> getModifiedAbilities() {
return this.modifiedAbilities;
}
public int getRandomFlag() {
return this.randomFlag;
}
public short[] getLevel() {
return this.level;
}
public short getItemClass() {
return this.itemClass;
}
public long getUnitGroup() {
return this.unitGroup;
}
public long getPositionInGroup() {
return this.positionInGroup;
}
public List<RandomUnit> getRandomUnitTables() {
return this.randomUnitTables;
}
public int getCustomTeamColor() {
return this.customTeamColor;
}
public int getWaygate() {
return this.waygate;
}
public int getCreationNumber() {
return this.creationNumber;
}
public void setId(final War3ID id) {
this.id = id;
}
public void setVariation(final int variation) {
this.variation = variation;
}
public void setAngle(final float angle) {
this.angle = angle;
}
public void setFlags(final short flags) {
this.flags = flags;
}
public void setPlayer(final int player) {
this.player = player;
}
public void setUnknown(final int unknown) {
this.unknown = unknown;
}
public void setHitpoints(final int hitpoints) {
this.hitpoints = hitpoints;
}
public void setMana(final int mana) {
this.mana = mana;
}
public void setDroppedItemTable(final int droppedItemTable) {
this.droppedItemTable = droppedItemTable;
}
public void setGoldAmount(final int goldAmount) {
this.goldAmount = goldAmount;
}
public void setTargetAcquisition(final float targetAcquisition) {
this.targetAcquisition = targetAcquisition;
}
public void setHeroLevel(final int heroLevel) {
this.heroLevel = heroLevel;
}
public void setHeroStrength(final int heroStrength) {
this.heroStrength = heroStrength;
}
public void setHeroAgility(final int heroAgility) {
this.heroAgility = heroAgility;
}
public void setHeroIntelligence(final int heroIntelligence) {
this.heroIntelligence = heroIntelligence;
}
public void setRandomFlag(final int randomFlag) {
this.randomFlag = randomFlag;
}
public void setItemClass(final short itemClass) {
this.itemClass = itemClass;
}
public void setUnitGroup(final long unitGroup) {
this.unitGroup = unitGroup;
}
public void setPositionInGroup(final long positionInGroup) {
this.positionInGroup = positionInGroup;
}
public void setCustomTeamColor(final int customTeamColor) {
this.customTeamColor = customTeamColor;
}
public void setWaygate(final int waygate) {
this.waygate = waygate;
}
public void setCreationNumber(final int creationNumber) {
this.creationNumber = creationNumber;
}
}

View File

@ -0,0 +1,76 @@
package com.etheller.warsmash.parsers.w3x.unitsdoo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class War3MapUnitsDoo {
private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3do");
private int version = 8;
private long unknown = 11;
private final List<Unit> units = new ArrayList<>();
public War3MapUnitsDoo(final LittleEndianDataInputStream stream) throws IOException {
if (stream != null) {
this.load(stream);
}
}
private boolean load(final LittleEndianDataInputStream stream) throws IOException {
final War3ID firstId = ParseUtils.readWar3ID(stream);
if (!MAGIC_NUMBER.equals(firstId)) {
return false;
}
this.version = stream.readInt();
this.unknown = ParseUtils.readUInt32(stream);
for (int i = 0, l = stream.readInt(); i < l; i++) {
final Unit unit = new Unit();
unit.load(stream, this.version);
this.units.add(unit);
}
return true;
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, MAGIC_NUMBER);
stream.writeInt(this.version);
ParseUtils.writeUInt32(stream, this.unknown);
stream.writeInt(this.units.size());
for (final Unit unit : this.units) {
unit.save(stream, this.version);
}
}
public int getByteLength() {
int size = 16;
for (final Unit unit : this.units) {
size += unit.getByteLength(this.version);
}
return size;
}
public List<Unit> getUnits() {
return this.units;
}
public int getVersion() {
return this.version;
}
public void setVersion(final int version) {
this.version = version;
}
}

View File

@ -0,0 +1,110 @@
package com.etheller.warsmash.parsers.w3x.w3e;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A tile corner.
*/
public class Corner {
private int groundHeight;
private int waterHeight;
private int mapEdge;
private int ramp;
private int blight;
private int water;
private int boundary;
private int groundTexture;
private int cliffVariation;
private int groundVariation;
private int cliffTexture;
private int layerHeight;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.groundHeight = (stream.readShort() - 8192) / 512;
final short waterAndEdge = stream.readShort();
this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / 512;
this.mapEdge = waterAndEdge & 0x4000;
final short textureAndFlags = ParseUtils.readUInt8(stream);
this.ramp = textureAndFlags & 0b00010000;
this.blight = textureAndFlags & 0b00100000;
this.water = textureAndFlags & 0b01000000;
this.boundary = textureAndFlags & 0b10000000;
this.groundTexture = textureAndFlags & 0b00001111;
final short variation = ParseUtils.readUInt8(stream);
this.cliffVariation = (variation & 0b11100000) >>> 5;
this.groundVariation = variation & 0b00011111;
final short cliffTextureAndLayer = ParseUtils.readUInt8(stream);
this.cliffTexture = (cliffTextureAndLayer & 0b11110000) >>> 4;
this.layerHeight = cliffTextureAndLayer & 0b00001111;
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeShort((this.groundHeight * 512) + 8192);
stream.writeShort((this.waterHeight + 8192 + this.mapEdge) << 14);
ParseUtils.writeUInt8(stream, (short) ((this.ramp << 4) | (this.blight << 5) | (this.water << 6)
| (this.boundary << 7) | this.groundTexture));
ParseUtils.writeUInt8(stream, (short) ((this.cliffVariation << 5) | this.groundVariation));
ParseUtils.writeUInt8(stream, (short) ((this.cliffTexture << 4) + this.layerHeight));
}
public int getGroundHeight() {
return this.groundHeight;
}
public int getWaterHeight() {
return this.waterHeight;
}
public int getMapEdge() {
return this.mapEdge;
}
public int getRamp() {
return this.ramp;
}
public int getBlight() {
return this.blight;
}
public int getWater() {
return this.water;
}
public int getBoundary() {
return this.boundary;
}
public int getGroundTexture() {
return this.groundTexture;
}
public int getCliffVariation() {
return this.cliffVariation;
}
public int getGroundVariation() {
return this.groundVariation;
}
public int getCliffTexture() {
return this.cliffTexture;
}
public int getLayerHeight() {
return this.layerHeight;
}
}

View File

@ -0,0 +1,148 @@
package com.etheller.warsmash.parsers.w3x.w3e;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* war3map.w3e - the environment file.
*/
public class War3MapW3e {
private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3E!");
private int version;
private char tileset = 'A';
private int hasCustomTileset;
private final List<War3ID> groundTiles = new ArrayList<>();
private final List<War3ID> cliffTiles = new ArrayList<>();
private final int[] mapSize = new int[2];
private final float[] centerOffset = new float[2];
private Corner[][] corners;
public War3MapW3e(final LittleEndianDataInputStream stream) throws IOException {
if (stream != null) {
this.load(stream);
}
}
private boolean load(final LittleEndianDataInputStream stream) throws IOException {
final War3ID firstId = ParseUtils.readWar3ID(stream);
if (!MAGIC_NUMBER.equals(firstId)) {
return false;
}
this.version = stream.readInt();
this.tileset = (char) stream.read();
this.hasCustomTileset = stream.readInt();
for (int i = 0, l = stream.readInt(); i < l; i++) {
this.groundTiles.add(ParseUtils.readWar3ID(stream));
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
this.cliffTiles.add(ParseUtils.readWar3ID(stream));
}
ParseUtils.readInt32Array(stream, this.mapSize);
ParseUtils.readFloatArray(stream, this.centerOffset);
this.corners = new Corner[this.mapSize[1]][];
for (int row = 0, rows = this.mapSize[1]; row < rows; row++) {
this.corners[row] = new Corner[this.mapSize[0]];
for (int column = 0, columns = this.mapSize[0]; column < columns; column++) {
final Corner corner = new Corner();
corner.load(stream);
this.corners[row][column] = corner;
}
}
return true;
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, MAGIC_NUMBER);
stream.writeInt(this.version);
stream.write(this.tileset);
stream.writeInt(this.hasCustomTileset);
ParseUtils.writeUInt32(stream, this.groundTiles.size());
for (final War3ID groundTile : this.groundTiles) {
ParseUtils.writeWar3ID(stream, groundTile);
}
ParseUtils.writeUInt32(stream, this.cliffTiles.size());
for (final War3ID cliffTile : this.cliffTiles) {
ParseUtils.writeWar3ID(stream, cliffTile);
}
ParseUtils.writeInt32Array(stream, this.mapSize);
ParseUtils.writeFloatArray(stream, this.centerOffset);
for (final Corner[] row : this.corners) {
for (final Corner corner : row) {
corner.save(stream);
}
}
}
public int getByteLength() {
return 37 + (this.groundTiles.size() * 4) + (this.cliffTiles.size() * 4)
+ (this.mapSize[0] * this.mapSize[1] * 7);
}
public int getVersion() {
return this.version;
}
public char getTileset() {
return this.tileset;
}
public int getHasCustomTileset() {
return this.hasCustomTileset;
}
public List<War3ID> getGroundTiles() {
return this.groundTiles;
}
public List<War3ID> getCliffTiles() {
return this.cliffTiles;
}
public int[] getMapSize() {
return this.mapSize;
}
public float[] getCenterOffset() {
return this.centerOffset;
}
public Corner[][] getCorners() {
return this.corners;
}
public void setVersion(final int version) {
this.version = version;
}
public void setTileset(final char tileset) {
this.tileset = tileset;
}
public void setHasCustomTileset(final int hasCustomTileset) {
this.hasCustomTileset = hasCustomTileset;
}
public void setCorners(final Corner[][] corners) {
this.corners = corners;
}
}

View File

@ -0,0 +1,29 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class Force {
private long flags;
private long playerMasks;
private String name;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.flags = ParseUtils.readUInt32(stream);
this.playerMasks = ParseUtils.readUInt32(stream);
this.name = ParseUtils.readUntilNull(stream);
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.flags);
ParseUtils.writeUInt32(stream, this.playerMasks);
ParseUtils.writeWithNullTerminator(stream, this.name);
}
public int getByteLength() {
return 9 + this.name.length();
}
}

View File

@ -0,0 +1,48 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* A player.
*/
public class Player {
private War3ID id;
private int type;
private int race;
private int isFixedStartPosition;
private String name;
private final float[] startLocation = new float[2];
private long allyLowPriorities;
private long allyHighPriorities;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.type = stream.readInt();
this.race = stream.readInt();
this.isFixedStartPosition = stream.readInt();
this.name = ParseUtils.readUntilNull(stream);
ParseUtils.readFloatArray(stream, this.startLocation);
this.allyLowPriorities = ParseUtils.readUInt32(stream);
this.allyHighPriorities = ParseUtils.readUInt32(stream);
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.type);
stream.writeInt(this.race);
stream.writeInt(this.isFixedStartPosition);
ParseUtils.writeWithNullTerminator(stream, this.name);
ParseUtils.writeFloatArray(stream, this.startLocation);
ParseUtils.writeUInt32(stream, this.allyLowPriorities);
ParseUtils.writeUInt32(stream, this.allyHighPriorities);
}
public int getByteLength() {
return 33 + this.name.length();
}
}

View File

@ -0,0 +1,39 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomItem {
private int chance;
private War3ID id;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.chance = stream.readInt();
this.id = ParseUtils.readWar3ID(stream);
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.chance);
ParseUtils.writeWar3ID(stream, this.id);
}
public int getChance() {
return this.chance;
}
public War3ID getId() {
return this.id;
}
public void setChance(final int chance) {
this.chance = chance;
}
public void setId(final War3ID id) {
this.id = id;
}
}

View File

@ -0,0 +1,39 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomItemSet {
private final List<RandomItem> items = new ArrayList<>();
public void load(final LittleEndianDataInputStream stream) throws IOException {
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final RandomItem item = new RandomItem();
item.load(stream);
this.items.add(item);
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.items.size());
for (final RandomItem item : this.items) {
item.save(stream);
}
}
public int getByteLength() {
return 4 + (this.items.size() * 8);
}
public List<RandomItem> getItems() {
return this.items;
}
}

View File

@ -0,0 +1,69 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomItemTable {
private War3ID id;
private String name;
private final List<RandomItemSet> sets = new ArrayList<>();
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.name = ParseUtils.readUntilNull(stream);
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final RandomItemSet set = new RandomItemSet();
set.load(stream);
this.sets.add(set);
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
ParseUtils.writeWithNullTerminator(stream, this.name);
ParseUtils.writeUInt32(stream, this.sets.size());
for (final RandomItemSet set : this.sets) {
set.save(stream);
}
}
public int getByteLength() {
int size = 9 + this.name.length();
for (final RandomItemSet set : this.sets) {
size += set.getByteLength();
}
return size;
}
public War3ID getId() {
return this.id;
}
public String getName() {
return this.name;
}
public List<RandomItemSet> getSets() {
return this.sets;
}
public void setId(final War3ID id) {
this.id = id;
}
public void setName(final String name) {
this.name = name;
}
}

View File

@ -0,0 +1,31 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomUnit {
private int chance;
private final List<War3ID> ids = new ArrayList<>();
public void load(final LittleEndianDataInputStream stream, final int positions) throws IOException {
this.chance = stream.readInt();
for (int i = 0; i < positions; i++) {
this.ids.add(ParseUtils.readWar3ID(stream));
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.chance);
for (final War3ID id : this.ids) {
ParseUtils.writeWar3ID(stream, id);
}
}
}

View File

@ -0,0 +1,48 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class RandomUnitTable {
private int id;
private String name;
private int positions;
private int[] columnTypes;
private List<RandomUnit> units;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.id = stream.readInt(); // TODO is this a War3ID?
this.name = ParseUtils.readUntilNull(stream);
this.positions = stream.readInt();
this.columnTypes = ParseUtils.readInt32Array(stream, this.positions);
for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) {
final RandomUnit unit = new RandomUnit();
unit.load(stream, this.positions);
this.units.add(unit);
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.id);
ParseUtils.writeWithNullTerminator(stream, this.name);
stream.writeInt(this.positions);
ParseUtils.writeInt32Array(stream, this.columnTypes);
ParseUtils.writeUInt32(stream, this.units.size());
for (final RandomUnit unit : this.units) {
unit.save(stream);
}
}
public int getByteLength() {
return 13 + this.name.length() + (this.columnTypes.length * 4)
+ (this.units.size() * (4 + (4 * this.positions)));
}
}

View File

@ -0,0 +1,24 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class TechAvailabilityChange {
private long playerFlags;
private War3ID id;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.playerFlags = ParseUtils.readUInt32(stream);
this.id = ParseUtils.readWar3ID(stream);
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.playerFlags);
ParseUtils.writeWar3ID(stream, this.id);
}
}

View File

@ -0,0 +1,30 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
public class UpgradeAvailabilityChange {
private long playerFlags;
private War3ID id;
private int levelAffected;
private int availability;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.playerFlags = ParseUtils.readUInt32(stream);
this.id = ParseUtils.readWar3ID(stream);
this.levelAffected = stream.readInt();
this.availability = stream.readInt();
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeUInt32(stream, this.playerFlags);
ParseUtils.writeWar3ID(stream, this.id);
stream.writeInt(this.levelAffected);
stream.writeInt(this.availability);
}
}

View File

@ -0,0 +1,440 @@
package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.util.ParseUtils;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* war3map.w3i - the general map information file.
*/
public class War3MapW3i {
private int version;
private int saves;
private int editorVersion;
private final short[] unknown1 = new short[16];
private String name;
private String author;
private String description;
private String recommendedPlayers;
private final float[] cameraBounds = new float[8];
private final int[] cameraBoundsComplements = new int[4];
private final int[] playableSize = new int[2];
private long flags;
private char tileset = 'A';
private int campaignBackground;
private String loadingScreenModel;
private String loadingScreenText;
private String loadingScreenTitle;
private String loadingScreenSubtitle;
private int loadingScreen;
private String prologueScreenModel;
private String prologueScreenText;
private String prologueScreenTitle;
private String prologueScreenSubtitle;
private int useTerrainFog;
private final float[] fogHeight = new float[2];
private float fogDensity;
private final short[] fogColor = new short[4];
private int globalWeather;
private String soundEnvironment;
private char lightEnvironmentTileset;
private final short[] waterVertexColor = new short[4];
private final short[] unknown2 = new short[4];
private final List<Player> players = new ArrayList<>();
private final List<Force> forces = new ArrayList<>();
private final List<UpgradeAvailabilityChange> upgradeAvailabilityChanges = new ArrayList<>();
private final List<TechAvailabilityChange> techAvailabilityChanges = new ArrayList<>();
private final List<RandomUnitTable> randomUnitTables = new ArrayList<>();
private final List<RandomItemTable> randomItemTables = new ArrayList<>();
public War3MapW3i(final LittleEndianDataInputStream stream) throws IOException {
if (stream != null) {
load(stream);
}
}
private void load(final LittleEndianDataInputStream stream) throws IOException {
this.version = stream.readInt();
this.saves = stream.readInt();
this.editorVersion = stream.readInt();
if (this.version > 27) {
ParseUtils.readUInt8Array(stream, this.unknown1);
}
this.name = ParseUtils.readUntilNull(stream);
this.author = ParseUtils.readUntilNull(stream);
this.description = ParseUtils.readUntilNull(stream);
this.recommendedPlayers = ParseUtils.readUntilNull(stream);
ParseUtils.readFloatArray(stream, this.cameraBounds);
ParseUtils.readInt32Array(stream, this.cameraBoundsComplements);
ParseUtils.readInt32Array(stream, this.playableSize);
this.flags = ParseUtils.readUInt32(stream);
this.tileset = (char) stream.read();
this.campaignBackground = stream.readInt();
if (this.version > 24) {
this.loadingScreenModel = ParseUtils.readUntilNull(stream);
}
this.loadingScreenText = ParseUtils.readUntilNull(stream);
this.loadingScreenTitle = ParseUtils.readUntilNull(stream);
this.loadingScreenSubtitle = ParseUtils.readUntilNull(stream);
this.loadingScreen = stream.readInt();
if (this.version > 24) {
this.prologueScreenModel = ParseUtils.readUntilNull(stream);
}
this.prologueScreenText = ParseUtils.readUntilNull(stream);
this.prologueScreenTitle = ParseUtils.readUntilNull(stream);
this.prologueScreenSubtitle = ParseUtils.readUntilNull(stream);
if (this.version > 24) {
this.useTerrainFog = stream.readInt();
ParseUtils.readFloatArray(stream, this.fogHeight);
this.fogDensity = stream.readFloat();
ParseUtils.readUInt8Array(stream, this.fogColor);
this.globalWeather = stream.readInt(); // TODO probably war3id, right?
this.soundEnvironment = ParseUtils.readUntilNull(stream);
this.lightEnvironmentTileset = (char) stream.read();
ParseUtils.readUInt8Array(stream, this.waterVertexColor);
}
if (this.version > 27) {
ParseUtils.readUInt8Array(stream, this.unknown2);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final Player player = new Player();
player.load(stream);
this.players.add(player);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final Force force = new Force();
force.load(stream);
this.forces.add(force);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final UpgradeAvailabilityChange upgradeAvailabilityChange = new UpgradeAvailabilityChange();
upgradeAvailabilityChange.load(stream);
this.upgradeAvailabilityChanges.add(upgradeAvailabilityChange);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final TechAvailabilityChange techAvailabilityChange = new TechAvailabilityChange();
techAvailabilityChange.load(stream);
this.techAvailabilityChanges.add(techAvailabilityChange);
}
for (int i = 0, l = stream.readInt(); i < l; i++) {
final RandomUnitTable randomUnitTable = new RandomUnitTable();
randomUnitTable.load(stream);
this.randomUnitTables.add(randomUnitTable);
}
if (this.version > 24) {
for (int i = 0, l = stream.readInt(); i < l; i++) {
final RandomItemTable randomItemTable = new RandomItemTable();
randomItemTable.load(stream);
this.randomItemTables.add(randomItemTable);
}
}
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeInt(this.version);
stream.writeInt(this.saves);
stream.writeInt(this.editorVersion);
if (this.version > 27) {
ParseUtils.writeUInt8Array(stream, this.unknown1);
}
ParseUtils.writeWithNullTerminator(stream, this.name);
ParseUtils.writeWithNullTerminator(stream, this.author);
ParseUtils.writeWithNullTerminator(stream, this.description);
ParseUtils.writeWithNullTerminator(stream, this.recommendedPlayers);
ParseUtils.writeFloatArray(stream, this.cameraBounds);
ParseUtils.writeInt32Array(stream, this.cameraBoundsComplements);
ParseUtils.writeInt32Array(stream, this.playableSize);
ParseUtils.writeUInt32(stream, this.flags);
stream.write((byte) this.tileset);
stream.writeInt(this.campaignBackground);
if (this.version > 24) {
ParseUtils.writeWithNullTerminator(stream, this.loadingScreenModel);
}
ParseUtils.writeWithNullTerminator(stream, this.loadingScreenText);
ParseUtils.writeWithNullTerminator(stream, this.loadingScreenTitle);
ParseUtils.writeWithNullTerminator(stream, this.loadingScreenSubtitle);
stream.writeInt(this.loadingScreen);
if (this.version > 24) {
ParseUtils.writeWithNullTerminator(stream, this.prologueScreenModel);
}
ParseUtils.writeWithNullTerminator(stream, this.prologueScreenText);
ParseUtils.writeWithNullTerminator(stream, this.prologueScreenTitle);
ParseUtils.writeWithNullTerminator(stream, this.prologueScreenSubtitle);
if (this.version > 24) {
stream.writeInt(this.useTerrainFog);
ParseUtils.writeFloatArray(stream, this.fogHeight);
stream.writeFloat(this.fogDensity);
ParseUtils.writeUInt8Array(stream, this.fogColor);
stream.writeInt(this.globalWeather); // TODO War3ID???
ParseUtils.writeWithNullTerminator(stream, this.soundEnvironment);
stream.write((byte) this.lightEnvironmentTileset);
ParseUtils.writeUInt8Array(stream, this.waterVertexColor);
}
if (this.version > 27) {
ParseUtils.writeUInt8Array(stream, this.unknown2);
}
ParseUtils.writeUInt32(stream, this.players.size());
for (final Player player : this.players) {
player.save(stream);
}
ParseUtils.writeUInt32(stream, this.forces.size());
for (final Force force : this.forces) {
force.save(stream);
}
ParseUtils.writeUInt32(stream, this.upgradeAvailabilityChanges.size());
for (final UpgradeAvailabilityChange change : this.upgradeAvailabilityChanges) {
change.save(stream);
}
ParseUtils.writeUInt32(stream, this.techAvailabilityChanges.size());
for (final TechAvailabilityChange change : this.techAvailabilityChanges) {
change.save(stream);
}
ParseUtils.writeUInt32(stream, this.randomUnitTables.size());
for (final RandomUnitTable table : this.randomUnitTables) {
table.save(stream);
}
if (this.version > 24) {
ParseUtils.writeUInt32(stream, this.randomItemTables.size());
for (final RandomItemTable table : this.randomItemTables) {
table.save(stream);
}
}
}
public int getByteLength() {
int size = 111 + this.name.length() + this.author.length() + this.description.length()
+ this.recommendedPlayers.length() + this.loadingScreenText.length() + this.loadingScreenTitle.length()
+ this.loadingScreenSubtitle.length() + this.prologueScreenText.length()
+ this.prologueScreenTitle.length() + this.prologueScreenSubtitle.length();
for (final Player player : this.players) {
size += player.getByteLength();
}
for (final Force force : this.forces) {
size += force.getByteLength();
}
size += this.upgradeAvailabilityChanges.size() * 16;
size += this.techAvailabilityChanges.size() * 8;
for (final RandomUnitTable table : this.randomUnitTables) {
size += table.getByteLength();
}
if (this.version > 24) {
size += 36 + this.loadingScreenModel.length() + this.prologueScreenModel.length()
+ this.soundEnvironment.length();
for (final RandomItemTable table : this.randomItemTables) {
size += table.getByteLength();
}
}
return size;
}
public int getVersion() {
return this.version;
}
public int getSaves() {
return this.saves;
}
public int getEditorVersion() {
return this.editorVersion;
}
public short[] getUnknown1() {
return this.unknown1;
}
public String getName() {
return this.name;
}
public String getAuthor() {
return this.author;
}
public String getDescription() {
return this.description;
}
public String getRecommendedPlayers() {
return this.recommendedPlayers;
}
public float[] getCameraBounds() {
return this.cameraBounds;
}
public int[] getCameraBoundsComplements() {
return this.cameraBoundsComplements;
}
public int[] getPlayableSize() {
return this.playableSize;
}
public long getFlags() {
return this.flags;
}
public char getTileset() {
return this.tileset;
}
public int getCampaignBackground() {
return this.campaignBackground;
}
public String getLoadingScreenModel() {
return this.loadingScreenModel;
}
public String getLoadingScreenText() {
return this.loadingScreenText;
}
public String getLoadingScreenTitle() {
return this.loadingScreenTitle;
}
public String getLoadingScreenSubtitle() {
return this.loadingScreenSubtitle;
}
public int getLoadingScreen() {
return this.loadingScreen;
}
public String getPrologueScreenModel() {
return this.prologueScreenModel;
}
public String getPrologueScreenText() {
return this.prologueScreenText;
}
public String getPrologueScreenTitle() {
return this.prologueScreenTitle;
}
public String getPrologueScreenSubtitle() {
return this.prologueScreenSubtitle;
}
public int getUseTerrainFog() {
return this.useTerrainFog;
}
public float[] getFogHeight() {
return this.fogHeight;
}
public float getFogDensity() {
return this.fogDensity;
}
public short[] getFogColor() {
return this.fogColor;
}
public int getGlobalWeather() {
return this.globalWeather;
}
public String getSoundEnvironment() {
return this.soundEnvironment;
}
public char getLightEnvironmentTileset() {
return this.lightEnvironmentTileset;
}
public short[] getWaterVertexColor() {
return this.waterVertexColor;
}
public short[] getUnknown2() {
return this.unknown2;
}
public List<Player> getPlayers() {
return this.players;
}
public List<Force> getForces() {
return this.forces;
}
public List<UpgradeAvailabilityChange> getUpgradeAvailabilityChanges() {
return this.upgradeAvailabilityChanges;
}
public List<TechAvailabilityChange> getTechAvailabilityChanges() {
return this.techAvailabilityChanges;
}
public List<RandomUnitTable> getRandomUnitTables() {
return this.randomUnitTables;
}
public List<RandomItemTable> getRandomItemTables() {
return this.randomItemTables;
}
}

View File

@ -0,0 +1,54 @@
package com.etheller.warsmash.parsers.w3x.wpm;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
public class War3MapWpm {
private static final War3ID MAGIC_NUMBER = War3ID.fromString("MP3W");
private int version;
private final int[] size = new int[2];
private short[] pathing;
public War3MapWpm(final LittleEndianDataInputStream stream) throws IOException {
if (stream != null) {
this.load(stream);
}
}
private boolean load(final LittleEndianDataInputStream stream) throws IOException {
final War3ID firstId = ParseUtils.readWar3ID(stream);
if (!MAGIC_NUMBER.equals(firstId)) {
return false;
}
this.version = stream.readInt();
ParseUtils.readInt32Array(stream, this.size);
this.pathing = ParseUtils.readUInt8Array(stream, this.size[0] * this.size[1]);
return true;
}
public int getVersion() {
return this.version;
}
public int[] getSize() {
return this.size;
}
public short[] getPathing() {
return this.pathing;
}
public void setVersion(final int version) {
this.version = version;
}
public void setPathing(final short[] pathing) {
this.pathing = pathing;
}
}

View File

@ -339,6 +339,10 @@ public class StandardObjectData {
return unitMetaData;
}
public WorldEditStrings getWorldEditStrings() {
return this.worldEditStrings;
}
public static class WarcraftData implements ObjectData {
WorldEditStrings worldEditStrings;
List<DataTable> tables = new ArrayList<>();

View File

@ -0,0 +1,105 @@
package com.etheller.warsmash.units.custom;
import com.etheller.warsmash.util.War3ID;
public final class Change {
private War3ID id;
private int vartype, level, dataptr;
private int longval;
private float realval;
private String strval;
private boolean boolval;
private War3ID junkDNA;
public War3ID getId() {
return this.id;
}
public void setId(final War3ID id) {
this.id = id;
}
public int getVartype() {
return this.vartype;
}
public void setVartype(final int vartype) {
this.vartype = vartype;
}
public int getLevel() {
return this.level;
}
public void setLevel(final int level) {
this.level = level;
}
public int getDataptr() {
return this.dataptr;
}
public void setDataptr(final int dataptr) {
this.dataptr = dataptr;
}
public int getLongval() {
return this.longval;
}
public void setLongval(final int longval) {
this.longval = longval;
}
public float getRealval() {
return this.realval;
}
public void setRealval(final float realval) {
this.realval = realval;
}
public String getStrval() {
return this.strval;
}
public void setStrval(final String strval) {
this.strval = strval;
}
public boolean isBoolval() {
return this.boolval;
}
public void setBoolval(final boolean boolval) {
this.boolval = boolval;
}
public void setJunkDNA(final War3ID junkDNA) {
this.junkDNA = junkDNA;
}
public War3ID getJunkDNA() {
return this.junkDNA;
}
public void copyFrom(final Change other) {
this.id = other.id;
this.level = other.level;
this.dataptr = other.dataptr;
this.vartype = other.vartype;
this.longval = other.longval;
this.realval = other.realval;
this.strval = other.strval;
this.boolval = other.boolval;
this.junkDNA = other.junkDNA;
}
@Override
public Change clone() {
final Change copy = new Change();
copy.copyFrom(this);
return copy;
}
}

View File

@ -0,0 +1,55 @@
package com.etheller.warsmash.units.custom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.etheller.warsmash.util.War3ID;
public final class ChangeMap implements Iterable<Map.Entry<War3ID, List<Change>>> {
private final Map<War3ID, List<Change>> idToChanges = new LinkedHashMap<>();
public void add(final War3ID war3Id, final Change change) {
List<Change> list = this.idToChanges.get(war3Id);
if (list == null) {
list = new ArrayList<>();
this.idToChanges.put(war3Id, list);
}
list.add(change);
}
public void add(final War3ID war3Id, final List<Change> changes) {
for (final Change change : changes) {
add(war3Id, change);
}
}
public List<Change> get(final War3ID war3ID) {
return this.idToChanges.get(war3ID);
}
public void delete(final War3ID war3ID, final Change change) {
if (this.idToChanges.containsKey(war3ID)) {
final List<Change> changeList = this.idToChanges.get(war3ID);
changeList.remove(change);
if (changeList.isEmpty()) {
this.idToChanges.remove(war3ID);
}
}
}
@Override
public Iterator<Map.Entry<War3ID, List<Change>>> iterator() {
return this.idToChanges.entrySet().iterator();
}
public int size() {
return this.idToChanges.size();
}
public void clear() {
this.idToChanges.clear();
}
}

View File

@ -0,0 +1,47 @@
package com.etheller.warsmash.units.custom;
import java.util.List;
import java.util.Map;
import com.etheller.warsmash.util.War3ID;
public final class ObjectDataChangeEntry {
private War3ID oldId;
private War3ID newId;
private final ChangeMap changes;
public ObjectDataChangeEntry(final War3ID oldId, final War3ID newId) {
this.oldId = oldId;
this.newId = newId;
this.changes = new ChangeMap();
}
@Override
public ObjectDataChangeEntry clone() {
final ObjectDataChangeEntry objectDataChangeEntry = new ObjectDataChangeEntry(this.oldId, this.newId);
for (final Map.Entry<War3ID, List<Change>> entry : this.changes) {
objectDataChangeEntry.getChanges().add(entry.getKey(), entry.getValue());
}
return objectDataChangeEntry;
}
public ChangeMap getChanges() {
return this.changes;
}
public War3ID getOldId() {
return this.oldId;
}
public void setOldId(final War3ID oldId) {
this.oldId = oldId;
}
public War3ID getNewId() {
return this.newId;
}
public void setNewId(final War3ID newId) {
this.newId = newId;
}
}

View File

@ -0,0 +1,89 @@
package com.etheller.warsmash.units.custom;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import com.etheller.warsmash.util.War3ID;
public final class ObjectMap implements Iterable<Map.Entry<War3ID, ObjectDataChangeEntry>> {
private final Map<War3ID, ObjectDataChangeEntry> idToDataChangeEntry;
private final Set<War3ID> lowerCaseKeySet;
public ObjectMap() {
this.idToDataChangeEntry = new LinkedHashMap<>();
this.lowerCaseKeySet = new HashSet<>();
}
public void clear() {
this.idToDataChangeEntry.clear();
this.lowerCaseKeySet.clear();
}
public ObjectDataChangeEntry remove(final War3ID key) {
this.lowerCaseKeySet.remove(War3ID.fromString(key.toString().toLowerCase()));
return this.idToDataChangeEntry.remove(key);
}
public Set<War3ID> keySet() {
return this.idToDataChangeEntry.keySet();
}
public ObjectDataChangeEntry put(final War3ID key, final ObjectDataChangeEntry value) {
this.lowerCaseKeySet.add(War3ID.fromString(key.toString().toLowerCase()));
return this.idToDataChangeEntry.put(key, value);
}
public Set<Map.Entry<War3ID, ObjectDataChangeEntry>> entrySet() {
return this.idToDataChangeEntry.entrySet();
}
public ObjectDataChangeEntry get(final War3ID key) {
return this.idToDataChangeEntry.get(key);
}
public boolean containsKey(final War3ID key) {
return this.idToDataChangeEntry.containsKey(key);
}
public boolean containsKeyCaseInsensitive(final War3ID key) {
return this.lowerCaseKeySet.contains(War3ID.fromString(key.toString().toLowerCase()));
}
public boolean containsValue(final ObjectDataChangeEntry value) {
return this.idToDataChangeEntry.containsValue(value);
}
public Collection<ObjectDataChangeEntry> values() {
return this.idToDataChangeEntry.values();
}
public int size() {
return this.idToDataChangeEntry.size();
}
public void forEach(final BiConsumer<? super War3ID, ? super ObjectDataChangeEntry> forEach) {
this.idToDataChangeEntry.forEach(forEach);
}
@Override
public Iterator<Map.Entry<War3ID, ObjectDataChangeEntry>> iterator() {
return this.idToDataChangeEntry.entrySet().iterator();
}
@Override
public ObjectMap clone() {
final ObjectMap clone = new ObjectMap();
forEach(new BiConsumer<War3ID, ObjectDataChangeEntry>() {
@Override
public void accept(final War3ID key, final ObjectDataChangeEntry value) {
clone.put(key, value);
}
});
return clone;
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.units.custom;
public interface WTS {
String get(int key);
}

View File

@ -0,0 +1,94 @@
package com.etheller.warsmash.units.custom;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Hashtable;
import java.util.Map;
/**
*
* @author Deaod
*
*/
public class WTSFile implements WTS {
private final InputStream source;
private final Map<Integer, String> trigStrings = new Hashtable<>();
private static enum ParseState {
NEXT_TRIGSTR,
START_OF_DATA,
END_OF_DATA;
}
private void parse() throws IOException {
final BufferedReader sourceReader = new BufferedReader(
new InputStreamReader(this.source, Charset.forName("utf-8")));
ParseState state = ParseState.NEXT_TRIGSTR;
// WTS files may start with a Byte Order Mark, which we will have to skip.
sourceReader.mark(4);
if (sourceReader.read() != 0xFEFF) {
// first character not a BOM, unread the character.
sourceReader.reset();
}
String currentLine = sourceReader.readLine();
int id = 0;
StringBuffer data = new StringBuffer();
while (currentLine != null) {
switch (state) {
case NEXT_TRIGSTR:
if (currentLine.startsWith("STRING ")) {
id = Integer.parseInt(currentLine.substring(7));
state = ParseState.START_OF_DATA;
}
break;
case START_OF_DATA:
if (currentLine.startsWith("{")) {
state = ParseState.END_OF_DATA;
}
break;
case END_OF_DATA:
if (currentLine.startsWith("}")) {
this.trigStrings.put(id, data.toString());
data = new StringBuffer();
state = ParseState.NEXT_TRIGSTR;
}
else {
data.append(currentLine);
}
break;
}
currentLine = sourceReader.readLine();
}
sourceReader.close();
}
public WTSFile(final InputStream inputStream) throws IOException {
this.source = inputStream;
parse();
}
public WTSFile(final Path source) throws IOException {
this(Files.newInputStream(source));
}
public WTSFile(final String sourcePath) throws IOException {
this(Paths.get(sourcePath));
}
@Override
public String get(final int index) {
return this.trigStrings.get(index);
}
}

View File

@ -0,0 +1,802 @@
package com.etheller.warsmash.units.custom;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
/**
* Inspired by PitzerMike's obj.h, without a lot of immediate focus on Java
* conventions. I will probably get it converted over to Java conventions once I
* have a working replica of his obj.h code.
*
* @author Eric
*
*/
public final class War3ObjectDataChangeset {
public static final int VAR_TYPE_INT = 0;
public static final int VAR_TYPE_REAL = 1;
public static final int VAR_TYPE_UNREAL = 2;
public static final int VAR_TYPE_STRING = 3;
public static final int VAR_TYPE_BOOLEAN = 4;
public static final int MAX_STR_LEN = 1024;
private static final Set<War3ID> UNIT_ID_SET;
private static final Set<War3ID> ABILITY_ID_SET;
static {
final HashSet<War3ID> unitHashSet = new HashSet<>();
unitHashSet.add(War3ID.fromString("ubpx"));
unitHashSet.add(War3ID.fromString("ubpy"));
unitHashSet.add(War3ID.fromString("ides"));
unitHashSet.add(War3ID.fromString("uhot"));
unitHashSet.add(War3ID.fromString("unam"));
unitHashSet.add(War3ID.fromString("ureq"));
unitHashSet.add(War3ID.fromString("urqa"));
unitHashSet.add(War3ID.fromString("utip"));
unitHashSet.add(War3ID.fromString("utub"));
UNIT_ID_SET = unitHashSet;
final HashSet<War3ID> abilHashSet = new HashSet<>();
abilHashSet.add(War3ID.fromString("irc2"));
abilHashSet.add(War3ID.fromString("irc3"));
abilHashSet.add(War3ID.fromString("bsk1"));
abilHashSet.add(War3ID.fromString("bsk2"));
abilHashSet.add(War3ID.fromString("bsk3"));
abilHashSet.add(War3ID.fromString("coau"));
abilHashSet.add(War3ID.fromString("coa1"));
abilHashSet.add(War3ID.fromString("coa2"));
abilHashSet.add(War3ID.fromString("cyc1"));
abilHashSet.add(War3ID.fromString("dcp1"));
abilHashSet.add(War3ID.fromString("dcp2"));
abilHashSet.add(War3ID.fromString("dvm1"));
abilHashSet.add(War3ID.fromString("dvm2"));
abilHashSet.add(War3ID.fromString("dvm3"));
abilHashSet.add(War3ID.fromString("dvm4"));
abilHashSet.add(War3ID.fromString("dvm5"));
abilHashSet.add(War3ID.fromString("exh1"));
abilHashSet.add(War3ID.fromString("exhu"));
abilHashSet.add(War3ID.fromString("fak1"));
abilHashSet.add(War3ID.fromString("fak2"));
abilHashSet.add(War3ID.fromString("fak3"));
abilHashSet.add(War3ID.fromString("hwdu"));
abilHashSet.add(War3ID.fromString("inv1"));
abilHashSet.add(War3ID.fromString("inv2"));
abilHashSet.add(War3ID.fromString("inv3"));
abilHashSet.add(War3ID.fromString("inv4"));
abilHashSet.add(War3ID.fromString("inv5"));
abilHashSet.add(War3ID.fromString("liq1"));
abilHashSet.add(War3ID.fromString("liq2"));
abilHashSet.add(War3ID.fromString("liq3"));
abilHashSet.add(War3ID.fromString("liq4"));
abilHashSet.add(War3ID.fromString("mim1"));
abilHashSet.add(War3ID.fromString("mfl1"));
abilHashSet.add(War3ID.fromString("mfl2"));
abilHashSet.add(War3ID.fromString("mfl3"));
abilHashSet.add(War3ID.fromString("mfl4"));
abilHashSet.add(War3ID.fromString("mfl5"));
abilHashSet.add(War3ID.fromString("tpi1"));
abilHashSet.add(War3ID.fromString("tpi2"));
abilHashSet.add(War3ID.fromString("spl1"));
abilHashSet.add(War3ID.fromString("spl2"));
abilHashSet.add(War3ID.fromString("irl1"));
abilHashSet.add(War3ID.fromString("irl2"));
abilHashSet.add(War3ID.fromString("irl3"));
abilHashSet.add(War3ID.fromString("irl4"));
abilHashSet.add(War3ID.fromString("irl5"));
abilHashSet.add(War3ID.fromString("idc1"));
abilHashSet.add(War3ID.fromString("idc2"));
abilHashSet.add(War3ID.fromString("idc3"));
abilHashSet.add(War3ID.fromString("imo1"));
abilHashSet.add(War3ID.fromString("imo2"));
abilHashSet.add(War3ID.fromString("imo3"));
abilHashSet.add(War3ID.fromString("imou"));
abilHashSet.add(War3ID.fromString("ict1"));
abilHashSet.add(War3ID.fromString("ict2"));
abilHashSet.add(War3ID.fromString("isr1"));
abilHashSet.add(War3ID.fromString("isr2"));
abilHashSet.add(War3ID.fromString("ipv1"));
abilHashSet.add(War3ID.fromString("ipv2"));
abilHashSet.add(War3ID.fromString("ipv3"));
abilHashSet.add(War3ID.fromString("mec1"));
abilHashSet.add(War3ID.fromString("spb1"));
abilHashSet.add(War3ID.fromString("spb2"));
abilHashSet.add(War3ID.fromString("spb3"));
abilHashSet.add(War3ID.fromString("spb4"));
abilHashSet.add(War3ID.fromString("spb5"));
abilHashSet.add(War3ID.fromString("gra1"));
abilHashSet.add(War3ID.fromString("gra2"));
abilHashSet.add(War3ID.fromString("gra3"));
abilHashSet.add(War3ID.fromString("gra4"));
abilHashSet.add(War3ID.fromString("gra5"));
abilHashSet.add(War3ID.fromString("ipmu"));
abilHashSet.add(War3ID.fromString("flk1"));
abilHashSet.add(War3ID.fromString("flk2"));
abilHashSet.add(War3ID.fromString("flk3"));
abilHashSet.add(War3ID.fromString("flk4"));
abilHashSet.add(War3ID.fromString("flk5"));
abilHashSet.add(War3ID.fromString("fbk1"));
abilHashSet.add(War3ID.fromString("fbk2"));
abilHashSet.add(War3ID.fromString("fbk3"));
abilHashSet.add(War3ID.fromString("fbk4"));
abilHashSet.add(War3ID.fromString("nca1"));
abilHashSet.add(War3ID.fromString("pxf1"));
abilHashSet.add(War3ID.fromString("pxf2"));
abilHashSet.add(War3ID.fromString("mls1"));
abilHashSet.add(War3ID.fromString("sla1"));
abilHashSet.add(War3ID.fromString("sla2"));
ABILITY_ID_SET = abilHashSet;
}
private int version;
private ObjectMap original = new ObjectMap();
private final ObjectMap custom = new ObjectMap();
private char expected;
private War3ID lastused;
public char kind;
public boolean detected;
public War3ID nameField;
public War3ObjectDataChangeset() {
this.version = 2;
this.kind = 'u';
this.expected = 'u';
this.detected = false;
this.lastused = War3ID.fromString("u~~~");
}
public War3ObjectDataChangeset(final char expectedkind) {
this.version = 2;
this.kind = 'u';
this.expected = expectedkind;
this.detected = false;
this.lastused = War3ID.fromString("u~~~");
}
public boolean detectKind(final War3ID chid) {
if (UNIT_ID_SET.contains(chid)) {
this.kind = 'u';
return false;
}
else if (ABILITY_ID_SET.contains(chid)) {
this.kind = 'a';
}
else {
switch (chid.asStringValue().charAt(0)) {
case 'f':
this.kind = 'h';
break;
case 'i':
this.kind = 't';
break;
case 'g':
this.kind = 'q';
break;
case 'a':
case 'u':
case 'b':
case 'd':
this.kind = chid.asStringValue().charAt(0);
break;
default:
this.kind = 'a';
}
}
return true;
}
public char getExpectedKind() {
return this.expected;
}
public War3ID getNameField() {
final War3ID field = War3ID.fromString("unam");
char cmp = this.kind;
if (!this.detected) {
cmp = this.expected;
}
switch (cmp) {
case 'h':
this.nameField = field.set(0, 'f');
break;
case 't':
this.nameField = field.set(0, 'u');
break;
case 'q':
this.nameField = field.set(0, 'g');
break;
default:
this.nameField = field.set(0, cmp);
break;
}
return this.nameField;
}
public boolean extended() {
char cmp = this.kind;
if (!this.detected) {
cmp = this.expected;
}
switch (cmp) {
case 'u':
case 'h':
case 'b':
case 't':
return false;
}
return true;
}
public void renameids(final ObjectMap map, final boolean isOriginal) {
final War3ID nameId = getNameField();
final List<War3ID> idsToRemoveFromMap = new ArrayList<>();
final Map<War3ID, ObjectDataChangeEntry> idsToObjectsForAddingToMap = new HashMap<>();
for (final Iterator<Map.Entry<War3ID, ObjectDataChangeEntry>> iterator = map.iterator(); iterator.hasNext();) {
final Map.Entry<War3ID, ObjectDataChangeEntry> entry = iterator.next();
final ObjectDataChangeEntry current = entry.getValue();
final List<Change> nameEntry = current.getChanges().get(nameId);
if ((nameEntry != null) && !nameEntry.isEmpty()) {
final Change firstNameChange = nameEntry.get(0);
int pos = firstNameChange.getStrval().lastIndexOf("::");
if ((pos != -1) && (firstNameChange.getStrval().length() > (pos + 2))) {
String rest = firstNameChange.getStrval().substring(pos + 2);
if (rest.length() == 4) {
final War3ID newId = War3ID.fromString(rest);
final ObjectDataChangeEntry existingObjectWithMatchingId = map.get(newId);
if (isOriginal) {// obj.cpp: update id and name
current.setOldId(newId);
}
else {
current.setNewId(newId);
}
firstNameChange.setStrval(firstNameChange.getStrval().substring(0, pos));
if (existingObjectWithMatchingId != null) {
// obj.cpp: carry over all changes
final Iterator<Map.Entry<War3ID, List<Change>>> changeIterator = current.getChanges()
.iterator();
while (changeIterator.hasNext()) {
final Map.Entry<War3ID, List<Change>> changeIteratorNext = changeIterator.next();
final War3ID copiedChangeId = changeIteratorNext.getKey();
List<Change> changeListForFieldToOverwrite = existingObjectWithMatchingId.getChanges()
.get(copiedChangeId);
if (changeListForFieldToOverwrite == null) {
changeListForFieldToOverwrite = new ArrayList<>();
}
for (final Change changeToCopy : changeIteratorNext.getValue()) {
final Iterator<Change> replaceIterator = changeListForFieldToOverwrite.iterator();
boolean didOverwrite = false;
while (replaceIterator.hasNext()) {
final Change changeToOverwrite = replaceIterator.next();
if (changeToOverwrite.getLevel() != changeToCopy.getLevel()) {
// obj.cpp: we can only replace
// changes with the same
// level/variation
continue;
}
if (copiedChangeId.equals(nameId)) {
// obj.cpp: carry over further
// references
pos = changeToOverwrite.getStrval().lastIndexOf("::");
if ((pos != -1) && (changeToOverwrite.getStrval().length() > (pos + 2))) {
rest = changeToOverwrite.getStrval().substring(pos + 2);
if ((rest.length() == 4) || "REMOVE".equals(rest)) {
changeToCopy.setStrval(changeToCopy.getStrval() + "::" + rest);
// so if this is a peasant, whose name was "Peasant::hfoo"
// and when we copied his data onto the footman, we found
// that the footman was named "Footman::hkni", then at that
// point we set the peasant's name to be "Peasant::hkni"
// because we are about to copy it onto the footman.
// And, we already set it to just "Peasant", so
// appending the "::" and the 'rest' variable is enough.
// Then, on a further loop iteration, in theory
// we will copoy the footman who is named Peasant
// onto the knight.
//
// TODO but what if we already copied the footman onto the knight?
// did PitzerMike consider this in obj.cpp?
}
}
}
changeToOverwrite.copyFrom(changeToCopy);
didOverwrite = true;
break;
}
if (!didOverwrite) {
changeListForFieldToOverwrite.add(changeToCopy);
if (changeListForFieldToOverwrite.size() == 1) {
existingObjectWithMatchingId.getChanges().add(copiedChangeId,
changeListForFieldToOverwrite);
}
}
}
}
}
else { // obj.cpp: an object with that id didn't exist
idsToRemoveFromMap.add(entry.getKey());
idsToObjectsForAddingToMap.put(newId, current.clone());
}
}
else if ("REMOVE".equals(rest)) { // obj.cpp: want to remove the object
idsToRemoveFromMap.add(entry.getKey());
} // obj.cpp: in all other cases keep it untouched
}
}
}
for (final War3ID id : idsToRemoveFromMap) {
map.remove(id);
}
for (final Map.Entry<War3ID, ObjectDataChangeEntry> entry : idsToObjectsForAddingToMap.entrySet()) {
map.put(entry.getKey(), entry.getValue());
}
}
public void renameIds() {
renameids(this.original, true);
renameids(this.custom, false);
}
// ' ' - '/'
// ':' - '@'
// '[' - '`'
// '{' - '~'
public char nextchar(final char cur) {
switch (cur) {
case '&': // skip ' because often jass parsers don't handle escaped rawcodes like '\''
return '(';
case '/': // skip digits
return ':';
case '@': // skip capital letters
return '['; // skip \ for the sam reason like ' ('\\')
case '[':
return ']';
case '_': // skip <EFBFBD> and lower case letters (<EFBFBD> can't be seen very well)
return '{';
case '~': // close circle and restart at !
return '!';
default:
return (char) ((short) cur + 1);
}
}
// we use only special characters to avoid collisions with existing objects
// the first character must remain unchanged though because it can have a
// special meaning
public War3ID getunusedid(final War3ID substitutefor) {
this.lastused = this.lastused.set(0, substitutefor.charAt(0));
this.lastused = this.lastused.set(3, nextchar(substitutefor.charAt(3)));
if (this.lastused.charAt(3) == '!') {
this.lastused = this.lastused.set(2, nextchar(substitutefor.charAt(2)));
if (this.lastused.charAt(2) == '!') {
this.lastused = this.lastused.set(1, nextchar(substitutefor.charAt(1)));
}
}
return this.lastused;
}
public void mergetable(final ObjectMap target, final ObjectMap targetCustom, final ObjectMap source,
final CollisionHandling collisionHandling) {
final Iterator<Map.Entry<War3ID, ObjectDataChangeEntry>> sourceObjectIterator = source.iterator();
while (sourceObjectIterator.hasNext()) {
final Map.Entry<War3ID, ObjectDataChangeEntry> sourceObject = sourceObjectIterator.next();
if (target.containsKey(sourceObject.getKey())) {
// obj.cpp: we have a collision
War3ID oldId;
War3ID replacementId;
switch (collisionHandling) {
case CREATE_NEW_ID:
oldId = sourceObject.getKey();
// obj.cpp: get new id until we finally have one that isn't used yet, or we're
// out of ids
replacementId = getunusedid(oldId);
while (!((oldId.charAt(1) == '~') && (oldId.charAt(2) == '~') && (oldId.charAt(3) == '~'))
&& targetCustom.containsKey(replacementId)) {
oldId = replacementId;
replacementId = getunusedid(oldId);
}
if (!((oldId.charAt(1) == '~') && (oldId.charAt(2) == '~') && (oldId.charAt(3) == '~'))) {
sourceObject.getValue().setNewId(replacementId);
targetCustom.put(replacementId, sourceObject.getValue().clone());
}
break;
case REPLACE:
// final ObjectDataChangeEntry deleteObject = target.get(sourceObject.getKey());
target.put(sourceObject.getKey(), sourceObject.getValue().clone());
break;
default:// merge
final ObjectDataChangeEntry targetObject = target.get(sourceObject.getKey());
for (final Map.Entry<War3ID, List<Change>> sourceUnitField : sourceObject.getValue().getChanges()) {
for (final Change sourceChange : sourceUnitField.getValue()) {
List<Change> targetChanges = targetObject.getChanges().get(sourceUnitField.getKey());
if (targetChanges == null) {
targetChanges = new ArrayList<>();
}
Change bestTargetChange = null;
for (final Change targetChange : targetChanges) {
if (targetChange.getLevel() == sourceChange.getLevel()) {
bestTargetChange = targetChange;
break;
}
}
if (bestTargetChange != null) {
bestTargetChange.copyFrom(sourceChange);
}
else {
targetChanges.add(sourceChange.clone());
if (targetChanges.size() == 1) {
targetObject.getChanges().add(sourceUnitField.getKey(), targetChanges);
}
}
}
}
break;
}
}
else {
targetCustom.put(sourceObject.getKey(), sourceObject.getValue().clone());
}
}
}
public static enum CollisionHandling {
CREATE_NEW_ID,
REPLACE,
MERGE;
}
public void merge(final War3ObjectDataChangeset obj, final CollisionHandling collisionHandling) {
mergetable(this.original, this.custom, obj.original, collisionHandling);
mergetable(this.original, this.custom, obj.custom, collisionHandling);
}
public int getvartype(final String name) {
if ("int".equals(name) || "bool".equals(name)) {
return 0;
}
else if ("real".equals(name)) {
return 1;
}
else if ("unreal".equals(name)) {
return 2;
}
return 3; // string
}
public boolean loadtable(final LittleEndianDataInputStream stream, final ObjectMap map, final boolean isOriginal,
final WTS wts, final boolean inlineWTS) throws IOException {
final War3ID noid = new War3ID(0);
final ByteBuffer stringByteBuffer = ByteBuffer.allocate(1024); // TODO check max len?
final CharsetDecoder decoder = Charset.forName("utf-8").newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
int ptr;
final int count = stream.readInt();
for (int i = 0; i < count; i++) {
final long nanoTime = System.nanoTime();
War3ID origid;
War3ID newid = null;
origid = readWar3ID(stream);
ObjectDataChangeEntry existingObject;
if (isOriginal) {
if (noid.equals(origid)) {
throw new IOException("the input stream might be screwed");
}
existingObject = map.get(origid);
if (existingObject == null) {
existingObject = new ObjectDataChangeEntry(origid, noid);
}
existingObject.setNewId(readWar3ID(stream));
}
else {
newid = readWar3ID(stream);
if (noid.equals(origid) || noid.equals(newid)) {
throw new IOException("the input stream might be screwed");
}
existingObject = map.get(newid);
if (existingObject == null) {
existingObject = new ObjectDataChangeEntry(origid, newid);
}
}
final int ccount = stream.readInt();// Retera: I assume this is change count?
if ((ccount == 0) && isOriginal) {
// throw new IOException("we seem to have reached the end of the stream and get
// zeroes");
System.err.println("we seem to have reached the end of the stream and get zeroes");
}
if (isOriginal) {
debugprint("StandardUnit \"" + origid + "\" " + ccount + " {");
}
else {
debugprint("CustomUnit \"" + origid + ":" + newid + "\" " + ccount + " {");
}
for (int j = 0; j < ccount; j++) {
final War3ID chid = readWar3ID(stream);
if (noid.equals(chid)) {
throw new IOException("the input stream might be screwed");
}
if (!this.detected) {
this.detected = detectKind(chid);
}
final Change newlyReadChange = new Change();
newlyReadChange.setId(chid);
newlyReadChange.setVartype(stream.readInt());
debugprint("\t\"" + chid + "\" {");
debugprint("\t\tType " + newlyReadChange.getVartype() + ",");
if (extended()) {
newlyReadChange.setLevel(stream.readInt());
newlyReadChange.setDataptr(stream.readInt());
debugprint("\t\tLevel " + newlyReadChange.getLevel() + ",");
debugprint("\t\tData " + newlyReadChange.getDataptr() + ",");
}
switch (newlyReadChange.getVartype()) {
case 0:
newlyReadChange.setLongval(stream.readInt());
debugprint("\t\tValue " + newlyReadChange.getLongval() + ",");
break;
case 3:
ptr = 0;
stringByteBuffer.clear();
byte charRead;
while ((charRead = (byte) stream.read()) != 0) {
stringByteBuffer.put(charRead);
}
stringByteBuffer.flip();
newlyReadChange.setStrval(decoder.decode(stringByteBuffer).toString());
if (inlineWTS && (newlyReadChange.getStrval().length() > 8)
&& "TRIGSTR_".equals(newlyReadChange.getStrval().substring(0, 8))) {
final int key = getWTSValue(newlyReadChange);
newlyReadChange.setStrval(wts.get(key));
if ((newlyReadChange.getStrval() != null)
&& (newlyReadChange.getStrval().length() > MAX_STR_LEN)) {
newlyReadChange.setStrval(newlyReadChange.getStrval().substring(0, MAX_STR_LEN - 1));
}
}
debugprint("\t\tValue \"" + newlyReadChange.getStrval() + "\",");
break;
case 4:
newlyReadChange.setBoolval(stream.readInt() == 1);
debugprint("\t\tValue " + newlyReadChange.isBoolval() + ",");
break;
default:
newlyReadChange.setRealval(stream.readFloat());
debugprint("\t\tValue " + newlyReadChange.getRealval() + ",");
break;
}
final War3ID crap = readWar3ID(stream);
debugprint("\t\tExtra \"" + crap + "\",");
newlyReadChange.setJunkDNA(crap);
List<Change> existingChanges = existingObject.getChanges().get(chid);
if (existingChanges == null) {
existingChanges = new ArrayList<>();
}
Change bestTargetChange = null;
for (final Change targetChange : existingChanges) {
if (targetChange.getLevel() == newlyReadChange.getLevel()) {
bestTargetChange = targetChange;
break;
}
}
if (bestTargetChange != null) {
bestTargetChange.copyFrom(newlyReadChange);
}
else {
existingChanges.add(newlyReadChange.clone());
if (existingChanges.size() == 1) {
existingObject.getChanges().add(chid, existingChanges);
}
}
if (!crap.equals(existingObject.getOldId()) && !crap.equals(existingObject.getNewId())
&& !crap.equals(noid)) {
for (int charIndex = 0; charIndex < 4; charIndex++) {
if ((crap.charAt(charIndex) < 32) || (crap.charAt(charIndex) > 126)) {
return false;
}
}
}
debugprint("\t}");
}
debugprint("}");
if ((newid == null) && !isOriginal) {
throw new IllegalStateException("custom unit has no ID!");
}
map.put(isOriginal ? origid : newid, existingObject);
final long endNanoTime = System.nanoTime();
final long deltaNanoTime = endNanoTime - nanoTime;
}
return true;
}
private War3ID readWar3ID(final LittleEndianDataInputStream stream) throws IOException {
return new War3ID(Integer.reverseBytes(stream.readInt()));
}
private static int getWTSValue(final Change change) {
String numberAsText = change.getStrval().substring(8);
while ((numberAsText.length() > 0) && (numberAsText.charAt(0) == '0')) {
numberAsText = numberAsText.substring(1);
}
if (numberAsText.length() == 0) {
return 0;
}
while (!Character.isDigit(numberAsText.charAt(numberAsText.length() - 1))) {
numberAsText = numberAsText.substring(0, numberAsText.length() - 1);
}
return Integer.parseInt(numberAsText);
}
public boolean load(final LittleEndianDataInputStream stream, final WTS wts, final boolean inlineWTS)
throws IOException {
this.detected = false;
this.version = stream.readInt();
if ((this.version != 1) && (this.version != 2)) {
return false;
}
ObjectMap backup = this.original.clone();
if (!loadtable(stream, this.original, true, wts, inlineWTS)) {
this.original = backup;
return false;
}
backup = this.custom.clone();
if (!loadtable(stream, this.custom, false, wts, inlineWTS)) {
this.original = backup;
return false;
}
return true;
}
public boolean load(final File file, final WTS wts, final boolean inlineWTS) throws IOException {
try (LittleEndianDataInputStream inputStream = new LittleEndianDataInputStream(new FileInputStream(file))) {
final boolean result = load(inputStream, wts, inlineWTS);
return result;
}
}
public static void inlineWTSTable(final ObjectMap map, final WTS wts) {
for (final Map.Entry<War3ID, ObjectDataChangeEntry> entry : map.entrySet()) {
for (final Map.Entry<War3ID, List<Change>> changes : entry.getValue().getChanges()) {
for (final Change change : changes.getValue()) {
if ((change.getStrval().length() > 8) && "TRIGSTR_".equals(change.getStrval().substring(0, 8))) {
final int key = getWTSValue(change);
change.setStrval(wts.get(key));
if (change.getStrval().length() > MAX_STR_LEN) {
change.setStrval(change.getStrval().substring(0, MAX_STR_LEN - 1));
}
}
}
}
}
}
public void inlineWTS(final WTS wts) {
inlineWTSTable(this.original, wts);
inlineWTSTable(this.custom, wts);
}
public void reset() {
reset('u');
}
public void reset(final char expectedkind) {
this.detected = false;
this.kind = 'u';
this.lastused = War3ID.fromString("u~~~");
this.expected = expectedkind;
this.original.clear();
this.custom.clear();
}
public boolean saveTable(final LittleEndianDataOutputStream outputStream, final ObjectMap map,
final boolean isOriginal) throws IOException {
final CharsetEncoder encoder = Charset.forName("utf-8").newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
final CharBuffer charBuffer = CharBuffer.allocate(1024);
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
final War3ID noid = new War3ID(0);
int count;
count = map.size();
outputStream.writeInt(count);
for (final Map.Entry<War3ID, ObjectDataChangeEntry> entry : map) {
final ObjectDataChangeEntry cl = entry.getValue();
int totalSize = 0;
for (final Map.Entry<War3ID, List<Change>> changeEntry : cl.getChanges()) {
totalSize += changeEntry.getValue().size();
}
if ((totalSize > 0) || !isOriginal) {
ParseUtils.writeWar3ID(outputStream, cl.getOldId());
ParseUtils.writeWar3ID(outputStream, cl.getNewId());
count = totalSize;// cl.getChanges().size();
outputStream.writeInt(count);
for (final Map.Entry<War3ID, List<Change>> changes : entry.getValue().getChanges()) {
for (final Change change : changes.getValue()) {
ParseUtils.writeWar3ID(outputStream, change.getId());
outputStream.writeInt(change.getVartype());
if (extended()) {
outputStream.writeInt(change.getLevel());
outputStream.writeInt(change.getDataptr());
}
switch (change.getVartype()) {
case 0:
outputStream.writeInt(change.getLongval());
break;
case 3:
charBuffer.clear();
byteBuffer.clear();
charBuffer.put(change.getStrval());
charBuffer.flip();
encoder.encode(charBuffer, byteBuffer, false);
byteBuffer.flip();
final byte[] stringBytes = new byte[byteBuffer.remaining() + 1];
int i = 0;
while (byteBuffer.hasRemaining()) {
stringBytes[i++] = byteBuffer.get();
}
stringBytes[i] = 0;
outputStream.write(stringBytes);
break;
case 4:
outputStream.writeInt(change.isBoolval() ? 1 : 0);
break;
default:
outputStream.writeFloat(change.getRealval());
break;
}
// if (change.getJunkDNA() == null) {
// saveWriteChars(outputStream, cl.getNewId().asStringValue().toCharArray());
// } else {
// saveWriteChars(outputStream,
// change.getJunkDNA().asStringValue().toCharArray());
// }
// saveWriteChars(outputStream, cl.getNewId().asStringValue().toCharArray());
ParseUtils.writeWar3ID(outputStream, noid);
}
}
}
}
return true;
}
public boolean save(final LittleEndianDataOutputStream outputStream, final boolean generateWTS) throws IOException {
if (generateWTS) {
throw new UnsupportedOperationException("FAIL cannot generate WTS, needs more code");
}
this.version = 2;
outputStream.writeInt(this.version);
if (!saveTable(outputStream, this.original, true)) {
throw new RuntimeException("Failed to save standard unit custom data");
}
if (!saveTable(outputStream, this.custom, false)) {
throw new RuntimeException("Failed to save custom unit custom data");
}
return true;
}
public ObjectMap getOriginal() {
return this.original;
}
public ObjectMap getCustom() {
return this.custom;
}
private static void debugprint(final String s) {
}
}

View File

@ -0,0 +1,922 @@
package com.etheller.warsmash.units.manager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.etheller.warsmash.units.GameObject;
import com.etheller.warsmash.units.ObjectData;
import com.etheller.warsmash.units.custom.Change;
import com.etheller.warsmash.units.custom.ChangeMap;
import com.etheller.warsmash.units.custom.ObjectDataChangeEntry;
import com.etheller.warsmash.units.custom.War3ObjectDataChangeset;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WorldEditStrings;
public final class MutableObjectData {
private static final War3ID ROC_SUPPORT_URAC = War3ID.fromString("urac");
private static final War3ID ROC_SUPPORT_UCAM = War3ID.fromString("ucam");
private static final War3ID ROC_SUPPORT_USPE = War3ID.fromString("uspe");
private static final War3ID ROC_SUPPORT_UBDG = War3ID.fromString("ubdg");
private final WorldEditorDataType worldEditorDataType;
private final ObjectData sourceSLKData;
private final ObjectData sourceSLKMetaData;
private final War3ObjectDataChangeset editorData;
private Set<War3ID> cachedKeySet;
private final Map<String, War3ID> metaNameToMetaId;
private final Map<War3ID, MutableGameObject> cachedKeyToGameObject;
private final MutableObjectDataChangeNotifier changeNotifier;
private final WorldEditStrings worldEditStrings;
public MutableObjectData(final WorldEditStrings worldEditStrings, final WorldEditorDataType worldEditorDataType,
final ObjectData sourceSLKData, final ObjectData sourceSLKMetaData,
final War3ObjectDataChangeset editorData) {
this.worldEditStrings = worldEditStrings;
this.worldEditorDataType = worldEditorDataType;
resolveStringReferencesInNames(sourceSLKData);
this.sourceSLKData = sourceSLKData;
this.sourceSLKMetaData = sourceSLKMetaData;
this.editorData = editorData;
this.metaNameToMetaId = new HashMap<>();
for (final String metaKeyString : sourceSLKMetaData.keySet()) {
final War3ID metaKey = War3ID.fromString(metaKeyString);
this.metaNameToMetaId.put(sourceSLKMetaData.get(metaKeyString).getField("field"), metaKey);
}
this.cachedKeyToGameObject = new HashMap<>();
this.changeNotifier = new MutableObjectDataChangeNotifier();
}
// TODO remove this hack
public War3ObjectDataChangeset getEditorData() {
return this.editorData;
}
private void resolveStringReferencesInNames(final ObjectData sourceSLKData) {
for (final String key : sourceSLKData.keySet()) {
final GameObject gameObject = sourceSLKData.get(key);
String name = gameObject.getField("Name");
final String suffix = gameObject.getField("EditorSuffix");
if (name.startsWith("WESTRING")) {
if (!name.contains(" ")) {
name = this.worldEditStrings.getString(name);
}
else {
final String[] names = name.split(" ");
name = "";
for (final String subName : names) {
if (name.length() > 0) {
name += " ";
}
if (subName.startsWith("WESTRING")) {
name += this.worldEditStrings.getString(subName);
}
else {
name += subName;
}
}
}
if (name.startsWith("\"") && name.endsWith("\"")) {
name = name.substring(1, name.length() - 1);
}
gameObject.setField("Name", name);
}
if (suffix.startsWith("WESTRING")) {
gameObject.setField("EditorSuffix", this.worldEditStrings.getString(suffix));
}
}
}
public void mergeChangset(final War3ObjectDataChangeset changeset) {
final List<War3ID> newObjects = new ArrayList<>();
final Map<War3ID, War3ID> previousAliasToNewAlias = new HashMap<>();
for (final Map.Entry<War3ID, ObjectDataChangeEntry> entry : changeset.getCustom()) {
// final String newId = JOptionPane.showInputDialog("Choose UNIT ID");
final War3ID nextDefaultEditorId = /* War3ID.fromString(newId); */getNextDefaultEditorId(
War3ID.fromString(entry.getKey().charAt(0) + "000"));
;
System.out.println("Merging " + nextDefaultEditorId + " for " + entry.getKey());
// createNew API will notifier the changeNotifier
final MutableGameObject newObject = createNew(nextDefaultEditorId, entry.getValue().getOldId(), false);
for (final Map.Entry<War3ID, List<Change>> changeList : entry.getValue().getChanges()) {
newObject.customUnitData.getChanges().add(changeList.getKey(), changeList.getValue());
}
newObjects.add(nextDefaultEditorId);
previousAliasToNewAlias.put(entry.getKey(), nextDefaultEditorId);
}
final War3ID[] fieldsToCheck = this.worldEditorDataType == WorldEditorDataType.UNITS
? new War3ID[] { War3ID.fromString("utra"), War3ID.fromString("uupt"), War3ID.fromString("ubui") }
: new War3ID[] {};
for (final War3ID unitId : newObjects) {
final MutableGameObject unit = get(unitId);
for (final War3ID field : fieldsToCheck) {
final String techtreeString = unit.getFieldAsString(field, 0);
final java.util.List<String> techList = Arrays.asList(techtreeString.split(","));
final ArrayList<String> resultingTechList = new ArrayList<>();
for (final String tech : techList) {
if (tech.length() == 4) {
final War3ID newTechId = previousAliasToNewAlias.get(War3ID.fromString(tech));
if (newTechId != null) {
resultingTechList.add(newTechId.toString());
}
else {
resultingTechList.add(tech);
}
}
else {
resultingTechList.add(tech);
}
}
final StringBuilder sb = new StringBuilder();
for (final String tech : resultingTechList) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(tech);
}
unit.setField(field, 0, sb.toString());
}
}
this.changeNotifier.objectsCreated(newObjects.toArray(new War3ID[newObjects.size()]));
}
public War3ObjectDataChangeset copySelectedObjects(final List<MutableGameObject> objectsToCopy) {
final War3ObjectDataChangeset changeset = new War3ObjectDataChangeset(this.editorData.getExpectedKind());
final War3ID[] fieldsToCheck = this.worldEditorDataType == WorldEditorDataType.UNITS
? new War3ID[] { War3ID.fromString("utra"), War3ID.fromString("uupt"), War3ID.fromString("ubui") }
: new War3ID[] {};
final Map<War3ID, War3ID> previousAliasToNewAlias = new HashMap<>();
for (final MutableGameObject gameObject : objectsToCopy) {
final ObjectDataChangeEntry gameObjectUserDataToCopy;
final ObjectDataChangeEntry gameObjectUserData;
final War3ID alias = gameObject.getAlias();
if (this.editorData.getOriginal().containsKey(alias)) {
gameObjectUserDataToCopy = this.editorData.getOriginal().get(alias);
final War3ID newAlias = getNextDefaultEditorId(
War3ID.fromString(gameObject.getCode().charAt(0) + "000"), changeset, this.sourceSLKData);
gameObjectUserData = new ObjectDataChangeEntry(gameObjectUserDataToCopy.getOldId(), newAlias);
}
else if (this.editorData.getCustom().containsKey(alias)) {
gameObjectUserDataToCopy = this.editorData.getCustom().get(alias);
gameObjectUserData = new ObjectDataChangeEntry(gameObjectUserDataToCopy.getOldId(),
gameObjectUserDataToCopy.getNewId());
}
else {
gameObjectUserDataToCopy = null;
final War3ID newAlias = getNextDefaultEditorId(
War3ID.fromString(gameObject.getCode().charAt(0) + "000"), changeset, this.sourceSLKData);
gameObjectUserData = new ObjectDataChangeEntry(
gameObject.isCustom() ? gameObject.getCode() : gameObject.getAlias(), newAlias);
}
if (gameObjectUserDataToCopy != null) {
for (final Map.Entry<War3ID, List<Change>> changeEntry : gameObjectUserDataToCopy.getChanges()) {
for (final Change change : changeEntry.getValue()) {
final Change newChange = new Change();
newChange.copyFrom(change);
gameObjectUserData.getChanges().add(change.getId(), newChange);
}
}
}
previousAliasToNewAlias.put(gameObject.getAlias(), gameObjectUserData.getNewId());
changeset.getCustom().put(gameObjectUserData.getNewId(), gameObjectUserData);
}
final MutableObjectData changeEditManager = new MutableObjectData(this.worldEditStrings,
this.worldEditorDataType, this.sourceSLKData, this.sourceSLKMetaData, changeset);
for (final War3ID unitId : changeEditManager.keySet()) {
final MutableGameObject unit = changeEditManager.get(unitId);
for (final War3ID field : fieldsToCheck) {
final String techtreeString = unit.getFieldAsString(field, 0);
final java.util.List<String> techList = Arrays.asList(techtreeString.split(","));
final ArrayList<String> resultingTechList = new ArrayList<>();
for (final String tech : techList) {
if (tech.length() == 4) {
final War3ID newTechId = previousAliasToNewAlias.get(War3ID.fromString(tech));
if (newTechId != null) {
resultingTechList.add(newTechId.toString());
}
else {
resultingTechList.add(tech);
}
}
else {
resultingTechList.add(tech);
}
}
final StringBuilder sb = new StringBuilder();
for (final String tech : resultingTechList) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(tech);
}
unit.setField(field, 0, sb.toString());
}
}
return changeset;
}
public WorldEditorDataType getWorldEditorDataType() {
return this.worldEditorDataType;
}
public ObjectData getSourceSLKMetaData() {
return this.sourceSLKMetaData;
}
public void addChangeListener(final MutableObjectDataChangeListener listener) {
this.changeNotifier.subscribe(listener);
}
public void removeChangeListener(final MutableObjectDataChangeListener listener) {
this.changeNotifier.unsubscribe(listener);
}
/**
* Returns the set of all Unit IDs in the map, at the cost of a lot of time to
* go find them all.
*
* @return
*/
public Set<War3ID> keySet() {
if (this.cachedKeySet == null) {
final Set<War3ID> customUnitKeys = this.editorData.getCustom().keySet();
final Set<War3ID> customKeys = new HashSet<>(customUnitKeys);
for (final String standardUnitKey : this.sourceSLKData.keySet()) {
customKeys.add(War3ID.fromString(standardUnitKey));
}
this.cachedKeySet = customKeys;
}
return this.cachedKeySet;
}
public void dropCachesHack() {
this.cachedKeySet = null;
this.cachedKeyToGameObject.clear();
}
public MutableGameObject get(final War3ID id) {
MutableGameObject mutableGameObject = this.cachedKeyToGameObject.get(id);
if (mutableGameObject == null) {
if (this.editorData.getCustom().containsKey(id)) {
final ObjectDataChangeEntry customUnitData = this.editorData.getCustom().get(id);
mutableGameObject = new MutableGameObject(
this.sourceSLKData.get(customUnitData.getOldId().asStringValue()), customUnitData);
this.cachedKeyToGameObject.put(id, mutableGameObject);
}
else if (this.editorData.getOriginal().containsKey(id)) {
final ObjectDataChangeEntry customUnitData = this.editorData.getOriginal().get(id);
mutableGameObject = new MutableGameObject(
this.sourceSLKData.get(customUnitData.getOldId().asStringValue()),
this.editorData.getOriginal().get(id));
this.cachedKeyToGameObject.put(id, mutableGameObject);
}
else if (this.sourceSLKData.get(id.asStringValue()) != null) {
mutableGameObject = new MutableGameObject(this.sourceSLKData.get(id.asStringValue()), null);
this.cachedKeyToGameObject.put(id, mutableGameObject);
}
}
return mutableGameObject;
}
public MutableGameObject createNew(final War3ID id, final War3ID parent) {
return createNew(id, parent, true);
}
private MutableGameObject createNew(final War3ID id, final War3ID parent, final boolean fireListeners) {
this.editorData.getCustom().put(id, new ObjectDataChangeEntry(parent, id));
if (this.cachedKeySet != null) {
this.cachedKeySet.add(id);
}
if (fireListeners) {
this.changeNotifier.objectCreated(id);
}
return get(id);
}
public void remove(final War3ID id) {
remove(id, true);
}
public void remove(final List<MutableGameObject> objects) {
final List<War3ID> removedIds = new ArrayList<>();
for (final MutableGameObject object : objects) {
if (object.isCustom()) {
remove(object.getAlias(), false);
removedIds.add(object.getAlias());
}
}
this.changeNotifier.objectsRemoved(removedIds.toArray(new War3ID[removedIds.size()]));
}
private MutableGameObject remove(final War3ID id, final boolean fireListeners) {
final ObjectDataChangeEntry removedObject = this.editorData.getCustom().remove(id);
final MutableGameObject removedMutableObj = this.cachedKeyToGameObject.remove(id);
if (this.cachedKeySet != null) {
this.cachedKeySet.remove(id);
}
if (fireListeners) {
this.changeNotifier.objectRemoved(id);
}
return removedMutableObj /* might be null based on cache, don't use */;
}
private static boolean goodForId(final char c) {
return Character.isDigit(c) || ((c >= 'A') && (c <= 'Z'));
}
public War3ID getNextDefaultEditorId(final War3ID startingId) {
War3ID newId = startingId;
while (this.editorData.getCustom().containsKeyCaseInsensitive(newId)
|| (this.sourceSLKData.get(newId.toString()) != null) || !goodForId(newId.charAt(1))
|| !goodForId(newId.charAt(2)) || !goodForId(newId.charAt(3))) {
// TODO good code general solution
if (newId.charAt(3) == 'Z') {
if (newId.charAt(2) == 'Z') {
if (newId.charAt(1) == 'Z') {
newId = new War3ID(((newId.getValue() / (256 * 256 * 256)) * 256 * 256 * 256)
+ (256 * 256 * 256) + '0' + ('0' * 256) + ('0' * 256 * 256));
}
else {
newId = new War3ID(
((newId.getValue() / (256 * 256)) * 256 * 256) + (256 * 256) + '0' + ('0' * 256));
}
}
else {
newId = new War3ID(((newId.getValue() / 256) * 256) + 256 + '0');
}
}
else {
newId = new War3ID(newId.getValue() + 1);
}
}
return newId;
}
public static War3ID getNextDefaultEditorId(final War3ID startingId, final War3ObjectDataChangeset editorData,
final ObjectData sourceSLKData) {
War3ID newId = startingId;
while (editorData.getCustom().containsKeyCaseInsensitive(newId) || (sourceSLKData.get(newId.toString()) != null)
|| !goodForId(newId.charAt(1)) || !goodForId(newId.charAt(2)) || !goodForId(newId.charAt(3))) {
newId = new War3ID(newId.getValue() + 1);
}
return newId;
}
private static final War3ID BUFF_EDITOR_NAME = War3ID.fromString("fnam");
private static final War3ID BUFF_BUFFTIP = War3ID.fromString("ftip");
private static final War3ID UNIT_CAMPAIGN = War3ID.fromString("ucam");
private static final War3ID UNIT_EDITOR_SUFFIX = War3ID.fromString("unsf");
private static final War3ID ABIL_EDITOR_SUFFIX = War3ID.fromString("ansf");
private static final War3ID DESTRUCTABLE_EDITOR_SUFFIX = War3ID.fromString("bsuf");
private static final War3ID BUFF_EDITOR_SUFFIX = War3ID.fromString("fnsf");
private static final War3ID UPGRADE_EDITOR_SUFFIX = War3ID.fromString("gnsf");
private static final War3ID HERO_PROPER_NAMES = War3ID.fromString("upro");
private static final Set<War3ID> CATEGORY_FIELDS = new HashSet<>();
private static final Set<War3ID> TEXT_FIELDS = new HashSet<>();
private static final Set<War3ID> ICON_FIELDS = new HashSet<>();
private static final Set<War3ID> FIELD_SETTINGS_FIELDS = new HashSet<>();
static {
// categorizing - I thought these would be changeFlags value "c", but no luck
CATEGORY_FIELDS.add(War3ID.fromString("ubdg")); // is a building
CATEGORY_FIELDS.add(War3ID.fromString("uspe")); // categorize special
CATEGORY_FIELDS.add(War3ID.fromString("ucam")); // categorize campaign
CATEGORY_FIELDS.add(War3ID.fromString("urac")); // race
CATEGORY_FIELDS.add(War3ID.fromString("uine")); // in editor
CATEGORY_FIELDS.add(War3ID.fromString("ucls")); // sort string (not a real field, fanmade)
CATEGORY_FIELDS.add(War3ID.fromString("icla")); // item class
CATEGORY_FIELDS.add(War3ID.fromString("bcat")); // destructible category
CATEGORY_FIELDS.add(War3ID.fromString("dcat")); // doodad category
CATEGORY_FIELDS.add(War3ID.fromString("aher")); // hero ability
CATEGORY_FIELDS.add(War3ID.fromString("aite")); // item ability
CATEGORY_FIELDS.add(War3ID.fromString("arac")); // ability race
CATEGORY_FIELDS.add(War3ID.fromString("frac")); // buff race
CATEGORY_FIELDS.add(War3ID.fromString("feff")); // is effect
CATEGORY_FIELDS.add(War3ID.fromString("grac")); // upgrade race
// field structure fields - doesn't seem to be changeFlags 's' like you might
// hope
FIELD_SETTINGS_FIELDS.add(War3ID.fromString("ubdg")); // unit is a builder
FIELD_SETTINGS_FIELDS.add(War3ID.fromString("dvar")); // doodad variations
FIELD_SETTINGS_FIELDS.add(War3ID.fromString("alev")); // ability level
FIELD_SETTINGS_FIELDS.add(War3ID.fromString("glvl")); // upgrade max level
}
public final class MutableGameObject {
private final GameObject parentWC3Object;
private ObjectDataChangeEntry customUnitData;
private void fireChangedEvent(final War3ID field, final int level) {
final String changeFlags = MutableObjectData.this.sourceSLKMetaData.get(field.toString())
.getField("changeFlags");
if (CATEGORY_FIELDS.contains(field)) {
MutableObjectData.this.changeNotifier.categoriesChanged(getAlias());
}
else if (changeFlags.contains("t")) {
MutableObjectData.this.changeNotifier.textChanged(getAlias());
}
else if (changeFlags.contains("m")) {
MutableObjectData.this.changeNotifier.modelChanged(getAlias());
}
else if (changeFlags.contains("i")) {
MutableObjectData.this.changeNotifier.iconsChanged(getAlias());
}
else if (FIELD_SETTINGS_FIELDS.contains(field)) {
MutableObjectData.this.changeNotifier.fieldsChanged(getAlias());
}
}
public MutableGameObject(final GameObject parentWC3Object, final ObjectDataChangeEntry customUnitData) {
this.parentWC3Object = parentWC3Object;
if (parentWC3Object == null) {
System.err.println(
"Parent object is null for " + customUnitData.getNewId() + ":" + customUnitData.getOldId());
throw new AssertionError("parentWC3Object cannot be null");
// this.parentWC3Object = new Element("", new DataTable());
}
this.customUnitData = customUnitData;
}
public boolean hasCustomField(final War3ID field, final int level) {
return getMatchingChange(field, level) != null;
}
public boolean hasEditorData() {
return (this.customUnitData != null) && (this.customUnitData.getChanges().size() > 0);
}
public boolean isCustom() {
return MutableObjectData.this.editorData.getCustom().containsKey(getAlias());
}
public void setField(final War3ID field, final int level, final String value) {
if (value.equals(getFieldStringFromSLKs(field, level))) {
if (!value.equals(getFieldAsString(field, level))) {
fireChangedEvent(field, level);
System.out.println("field was reset");
}
else {
System.out.println("field was unmodified");
}
resetFieldToDefaults(field, level);
return;
}
final Change matchingChange = getOrCreateMatchingChange(field, level);
matchingChange.setStrval(value);
matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_STRING);
System.out.println("field created change");
fireChangedEvent(field, level);
}
public void setField(final War3ID field, final int level, final boolean value) {
if (value == (asInt(getFieldStringFromSLKs(field, level).trim()) == 1)) {
if (value != getFieldAsBoolean(field, level)) {
fireChangedEvent(field, level);
}
resetFieldToDefaults(field, level);
return;
}
final Change matchingChange = getOrCreateMatchingChange(field, level);
matchingChange.setBoolval(value);
matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_BOOLEAN);
fireChangedEvent(field, level);
}
public void setField(final War3ID field, final int level, final int value) {
if (value == asInt(getFieldStringFromSLKs(field, level).trim())) {
if (value != getFieldAsInteger(field, level)) {
fireChangedEvent(field, level);
}
resetFieldToDefaults(field, level);
return;
}
final Change matchingChange = getOrCreateMatchingChange(field, level);
matchingChange.setLongval(value);
matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_INT);
fireChangedEvent(field, level);
}
public void resetFieldToDefaults(final War3ID field, final int level) {
final Change existingChange = getMatchingChange(field, level);
if ((existingChange != null) && (this.customUnitData != null)) {
this.customUnitData.getChanges().delete(field, existingChange);
fireChangedEvent(field, level);
}
return;
}
public void setField(final War3ID field, final int level, final float value) {
if (Math.abs(value - asFloat(getFieldStringFromSLKs(field, level).trim())) < 0.00001f) {
if (Math.abs(value - getFieldAsFloat(field, level)) > 0.00001f) {
fireChangedEvent(field, level);
}
resetFieldToDefaults(field, level);
return;
}
final Change matchingChange = getOrCreateMatchingChange(field, level);
matchingChange.setRealval(value);
final boolean unsigned = MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue())
.getField("type").equals("unreal");
matchingChange.setVartype(
unsigned ? War3ObjectDataChangeset.VAR_TYPE_UNREAL : War3ObjectDataChangeset.VAR_TYPE_REAL);
fireChangedEvent(field, level);
}
private Change getOrCreateMatchingChange(final War3ID field, final int level) {
if (this.customUnitData == null) {
final War3ID war3Id = War3ID.fromString(this.parentWC3Object.getId());
final ObjectDataChangeEntry newCustomUnitData = new ObjectDataChangeEntry(war3Id, War3ID.NONE);
MutableObjectData.this.editorData.getOriginal().put(war3Id, newCustomUnitData);
this.customUnitData = newCustomUnitData;
}
Change matchingChange = getMatchingChange(field, level);
if (matchingChange == null) {
final ChangeMap changeMap = this.customUnitData.getChanges();
final List<Change> changeList = changeMap.get(field);
matchingChange = new Change();
matchingChange.setId(field);
matchingChange.setLevel(level);
if (MutableObjectData.this.editorData.extended()) {
// dunno why, but Blizzard sure likes those dataptrs in the ability data
// my code should grab 0 when the metadata lacks this field
matchingChange.setDataptr(
MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue()).getFieldValue("data"));
}
if (changeList == null) {
changeMap.add(field, matchingChange);
}
else {
boolean insertedChange = false;
for (int i = 0; i < changeList.size(); i++) {
if (changeList.get(i).getLevel() > level) {
insertedChange = true;
changeList.add(i, matchingChange);
break;
}
}
if (!insertedChange) {
changeList.add(changeList.size(), matchingChange);
}
}
}
return matchingChange;
}
public String getFieldAsString(final War3ID field, final int level) {
final Change matchingChange = getMatchingChange(field, level);
if (matchingChange != null) {
if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_STRING) {
throw new IllegalStateException(
"Requested string value of '" + field + "' from '" + this.parentWC3Object.getId()
+ "', but this field was not a string! vartype=" + matchingChange.getVartype());
}
return matchingChange.getStrval();
}
// no luck with custom data, look at the standard data
int slkLevel = level;
if (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES) {
slkLevel -= 1;
}
return getFieldStringFromSLKs(field, slkLevel);
}
private Change getMatchingChange(final War3ID field, final int level) {
Change matchingChange = null;
if (this.customUnitData == null) {
return null;
}
final List<Change> changeList = this.customUnitData.getChanges().get(field);
if (changeList != null) {
for (final Change change : changeList) {
if (change.getLevel() == level) {
matchingChange = change;
break;
}
}
}
return matchingChange;
}
public String readSLKTag(final String key) {
if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) {
return getFieldAsString(MutableObjectData.this.metaNameToMetaId.get(key), 0);
}
return this.parentWC3Object.getField(key);
}
public boolean readSLKTagBoolean(final String key) {
if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) {
return getFieldAsBoolean(MutableObjectData.this.metaNameToMetaId.get(key), 0);
}
return this.parentWC3Object.getFieldValue(key) == 1;
}
public String getName() {
String name = getFieldAsString(MutableObjectData.this.editorData.getNameField(),
MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES ? 1 : 0);
boolean nameKnown = name.length() >= 1;
if (!nameKnown && !readSLKTag("code").equals(getAlias().toString()) && (readSLKTag("code").length() >= 4)
&& !isCustom()) {
final MutableGameObject codeObject = get(War3ID.fromString(readSLKTag("code").substring(0, 4)));
if (codeObject != null) {
name = codeObject.getName();
nameKnown = true;
}
}
String suf = "";
switch (MutableObjectData.this.worldEditorDataType) {
case ABILITIES:
suf = getFieldAsString(ABIL_EDITOR_SUFFIX, 0);
break;
case BUFFS_EFFECTS:
final String editorName = getFieldAsString(BUFF_EDITOR_NAME, 0);
if (!nameKnown && (editorName.length() > 1)) {
name = editorName;
nameKnown = true;
}
final String buffTip = getFieldAsString(BUFF_BUFFTIP, 0);
if (!nameKnown && (buffTip.length() > 1)) {
name = buffTip;
nameKnown = true;
}
suf = getFieldAsString(BUFF_EDITOR_SUFFIX, 0);
break;
case DESTRUCTIBLES:
suf = getFieldAsString(DESTRUCTABLE_EDITOR_SUFFIX, 0);
break;
case DOODADS:
break;
case ITEM:
break;
case UNITS:
if (getFieldAsBoolean(UNIT_CAMPAIGN, 0) && Character.isUpperCase(getAlias().charAt(0))) {
name = getFieldAsString(HERO_PROPER_NAMES, 0);
if (name.contains(",")) {
name = name.split(",")[0];
}
}
suf = getFieldAsString(UNIT_EDITOR_SUFFIX, 0);
break;
case UPGRADES:
suf = getFieldAsString(UPGRADE_EDITOR_SUFFIX, 1);
break;
}
if (nameKnown/* && name.startsWith("WESTRING") */) {
if (!name.contains(" ")) {
// name = WEString.getString(name);
}
else {
final String[] names = name.split(" ");
name = "";
for (final String subName : names) {
if (name.length() > 0) {
name += " ";
}
// if (subName.startsWith("WESTRING")) {
// name += WEString.getString(subName);
// } else {
name += subName;
// }
}
}
if (name.startsWith("\"") && name.endsWith("\"")) {
name = name.substring(1, name.length() - 1);
}
}
if (!nameKnown) {
name = MutableObjectData.this.worldEditStrings.getString("WESTRING_UNKNOWN") + " '"
+ getAlias().toString() + "'";
}
if ((suf.length() > 0) && !suf.equals("_")) {
// if (suf.startsWith("WESTRING")) {
// suf = WEString.getString(suf);
// }
if (!suf.startsWith(" ")) {
name += " ";
}
name += suf;
}
return name;
}
private String getFieldStringFromSLKs(final War3ID field, final int level) {
final GameObject metaData = MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue());
if (metaData == null) {
if (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UNITS) {
if (ROC_SUPPORT_URAC.equals(field)) {
return this.parentWC3Object.getField("race");
}
else if (ROC_SUPPORT_UCAM.equals(field)) {
return "0";
}
else if (ROC_SUPPORT_USPE.equals(field)) {
return this.parentWC3Object.getField("special");
}
else if (ROC_SUPPORT_UBDG.equals(field)) {
return this.parentWC3Object.getField("isbldg");
}
}
throw new IllegalStateException("Program requested " + field.toString() + " from "
+ MutableObjectData.this.worldEditorDataType);
}
if (this.parentWC3Object == null) {
throw new IllegalStateException("corrupted unit, no parent unit id");
}
int index = metaData.getFieldValue("index");
final String upgradeHack = metaData.getField("appendIndex");
if ("0".equals(upgradeHack)) {
// Engage magic upgrade hack to replace index with level
if (!field.toString().equals("gbpx") && !field.toString().equals("gbpy")) {
index = level;
}
}
else if ((index != -1) && (level > 0)) {
index = level - 1;
}
if (index != -1) {
final String fieldStringValue = this.parentWC3Object
.getField(getEditorMetaDataDisplayKey(level, metaData), index);
return fieldStringValue;
}
final String fieldStringValue = this.parentWC3Object.getField(getEditorMetaDataDisplayKey(level, metaData));
return fieldStringValue;
}
public int getFieldAsInteger(final War3ID field, final int level) {
final Change matchingChange = getMatchingChange(field, level);
if (matchingChange != null) {
if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_INT) {
throw new IllegalStateException(
"Requested integer value of '" + field + "' from '" + this.parentWC3Object.getId()
+ "', but this field was not an int! vartype=" + matchingChange.getVartype());
}
return matchingChange.getLongval();
}
// no luck with custom data, look at the standard data
try {
return Integer.parseInt(getFieldStringFromSLKs(field, level));
}
catch (final NumberFormatException e) {
return 0;
}
}
public boolean getFieldAsBoolean(final War3ID field, final int level) {
final Change matchingChange = getMatchingChange(field, level);
if (matchingChange != null) {
if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_BOOLEAN) {
if (matchingChange.getVartype() == War3ObjectDataChangeset.VAR_TYPE_INT) {
return matchingChange.getLongval() == 1;
}
else {
throw new IllegalStateException(
"Requested boolean value of '" + field + "' from '" + this.parentWC3Object.getId()
+ "', but this field was not a bool! vartype=" + matchingChange.getVartype());
}
}
return matchingChange.isBoolval();
}
// no luck with custom data, look at the standard data
try {
return Integer.parseInt(getFieldStringFromSLKs(field, level)) == 1;
}
catch (final NumberFormatException e) {
return false;
}
}
public float getFieldAsFloat(final War3ID field, final int level) {
final Change matchingChange = getMatchingChange(field, level);
if (matchingChange != null) {
if ((matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_REAL)
&& (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_UNREAL)) {
throw new IllegalStateException(
"Requested float value of '" + field + "' from '" + this.parentWC3Object.getId()
+ "', but this field was not a float! vartype=" + matchingChange.getVartype());
}
return matchingChange.getRealval();
}
// no luck with custom data, look at the standard data
try {
return Float.parseFloat(getFieldStringFromSLKs(field, level));
}
catch (final NumberFormatException e) {
return 0;
}
}
public War3ID getAlias() {
if (this.customUnitData == null) {
return War3ID.fromString(this.parentWC3Object.getId());
}
if (War3ID.NONE.equals(this.customUnitData.getNewId())) {
return this.customUnitData.getOldId();
}
return this.customUnitData.getNewId();
}
public War3ID getCode() {
if (this.customUnitData == null) {
if ((MutableObjectData.this.worldEditorDataType == WorldEditorDataType.ABILITIES)
|| (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.BUFFS_EFFECTS)) {
return War3ID.fromString(this.parentWC3Object.getField("code"));
}
else {
return War3ID.fromString(this.parentWC3Object.getId());
}
}
if (War3ID.NONE.equals(this.customUnitData.getNewId())) {
if ((MutableObjectData.this.worldEditorDataType == WorldEditorDataType.ABILITIES)
|| (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.BUFFS_EFFECTS)) {
return War3ID.fromString(this.parentWC3Object.getField("code"));
}
else {
return this.customUnitData.getOldId();
}
}
return this.customUnitData.getOldId();
}
}
private static int asInt(final String text) {
return text == null ? 0
: "".equals(text) ? 0 : "-".equals(text) ? 0 : "_".equals(text) ? 0 : Integer.parseInt(text);
}
private static float asFloat(final String text) {
return text == null ? 0
: "".equals(text) ? 0 : "-".equals(text) ? 0 : "_".equals(text) ? 0 : Float.parseFloat(text);
}
public enum WorldEditorDataType {
UNITS("w3u"),
ITEM("w3t"),
DESTRUCTIBLES("w3b"),
DOODADS("w3d"),
ABILITIES("w3a"),
BUFFS_EFFECTS("w3h"),
UPGRADES("w3q");
private String extension;
private WorldEditorDataType(final String extension) {
this.extension = extension;
}
public String getExtension() {
return this.extension;
}
}
public static String getEditorMetaDataDisplayKey(int level, final GameObject metaData) {
final int index = metaData.getFieldValue("index");
String metaDataName = metaData.getField("field");
final int repeatCount = metaData.getFieldValue("repeat");
final String upgradeHack = metaData.getField("appendIndex");
final boolean repeats = (repeatCount > 0) && !"0".equals(upgradeHack);
final int data = metaData.getFieldValue("data");
if (data > 0) {
metaDataName += (char) ('A' + (data - 1));
}
if ("1".equals(upgradeHack)) {
final int upgradeExtensionLevel = level - 1;
if (upgradeExtensionLevel > 0) {
metaDataName += Integer.toString(upgradeExtensionLevel);
}
}
else if (repeats && (index == -1)) {
if (level == 0) {
level = 1;
}
if (repeatCount >= 10) {
metaDataName += String.format("%2d", level).replace(' ', '0');
}
else {
metaDataName += Integer.toString(level);
}
}
return metaDataName;
}
public static String getDisplayAsRawDataName(final MutableGameObject gameObject) {
String aliasString = gameObject.getAlias().toString();
if (!gameObject.getAlias().equals(gameObject.getCode())) {
aliasString += ":" + gameObject.getCode().toString();
}
return aliasString + " (" + gameObject.getName() + ")";
}
}

View File

@ -0,0 +1,23 @@
package com.etheller.warsmash.units.manager;
import com.etheller.warsmash.util.War3ID;
public interface MutableObjectDataChangeListener {
void textChanged(War3ID changedObject);
void iconsChanged(War3ID changedObject);
void categoriesChanged(War3ID changedObject);
void fieldsChanged(War3ID changedObject);
void modelChanged(War3ID changedObject);
void objectCreated(War3ID newObject);
void objectsCreated(War3ID[] newObject);
void objectRemoved(War3ID removedObject);
void objectsRemoved(War3ID[] removedObject);
}

View File

@ -0,0 +1,72 @@
package com.etheller.warsmash.units.manager;
import com.etheller.warsmash.util.SubscriberSetNotifier;
import com.etheller.warsmash.util.War3ID;
public final class MutableObjectDataChangeNotifier extends SubscriberSetNotifier<MutableObjectDataChangeListener>
implements MutableObjectDataChangeListener {
@Override
public void textChanged(final War3ID changedObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.textChanged(changedObject);
}
}
@Override
public void categoriesChanged(final War3ID changedObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.categoriesChanged(changedObject);
}
}
@Override
public void iconsChanged(final War3ID changedObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.iconsChanged(changedObject);
}
}
@Override
public void fieldsChanged(final War3ID changedObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.fieldsChanged(changedObject);
}
}
@Override
public void modelChanged(final War3ID changedObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.modelChanged(changedObject);
}
}
@Override
public void objectCreated(final War3ID newObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.objectCreated(newObject);
}
}
@Override
public void objectsCreated(final War3ID[] newObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.objectsCreated(newObject);
}
}
@Override
public void objectRemoved(final War3ID newObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.objectRemoved(newObject);
}
}
@Override
public void objectsRemoved(final War3ID[] newObject) {
for (final MutableObjectDataChangeListener listener : this.set) {
listener.objectsRemoved(newObject);
}
}
}

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.util;
import java.awt.Graphics2D;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
@ -8,6 +7,7 @@ import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
@ -30,14 +30,20 @@ public final class ImageUtils {
// for
// RGB
final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888);
final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) {
@Override
public int getGLInternalFormat() {
return GL30.GL_SRGB8_ALPHA8;
}
};
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
final int pixel = pixels[(y * image.getWidth()) + x];
pixmap.drawPixel(x, y, (pixel << 8) | (pixel >>> 24));
}
}
return new Texture(pixmap);
final Texture texture = new Texture(pixmap);
return texture;
}
/**
@ -66,12 +72,10 @@ public final class ImageUtils {
DataBuffer.TYPE_BYTE);
final BufferedImage lRGB = new BufferedImage(lRGBModel,
lRGBModel.createCompatibleWritableRaster(in.getWidth(), in.getHeight()), false, null);
final Graphics2D graphic = lRGB.createGraphics();
try {
graphic.drawImage(in, 0, 0, null);
}
finally {
graphic.dispose();
for (int i = 0; i < in.getWidth(); i++) {
for (int j = 0; j < in.getHeight(); j++) {
lRGB.setRGB(i, j, in.getRGB(i, j));
}
}
// Convert to sRGB.

View File

@ -13,6 +13,10 @@ import java.util.Map;
public class MappedData {
private final Map<String, MappedDataRow> map = new HashMap<>();
public MappedData() {
this(null);
}
public MappedData(final String buffer) {
if (buffer != null) {
this.load(buffer);

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
@ -49,6 +50,18 @@ public class ParseUtils {
return array;
}
public static void readInt32Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException {
for (int i = 0; i < array.length; i++) {
array[i] = stream.readInt();
}
}
public static int[] readInt32Array(final LittleEndianDataInputStream stream, final int length) throws IOException {
final int[] array = new int[length];
readInt32Array(stream, array);
return array;
}
public static void readUInt16Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException {
for (int i = 0; i < array.length; i++) {
array[i] = readUInt16(stream);
@ -115,6 +128,13 @@ public class ParseUtils {
}
}
public static void writeInt32Array(final LittleEndianDataOutputStream stream, final int[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
stream.writeInt(array[i]);
}
}
public static void writeUInt16Array(final LittleEndianDataOutputStream stream, final int[] array)
throws IOException {
for (int i = 0; i < array.length; i++) {
@ -138,4 +158,21 @@ public class ParseUtils {
final String name = new String(recycleByteArray, 0, i, ParseUtils.UTF8);
return name;
}
public static String readUntilNull(final LittleEndianDataInputStream stream) throws IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c;
while (((c = stream.read()) != 0) && (c != -1)) {
baos.write(c);
}
return new String(baos.toByteArray(), ParseUtils.UTF8);
}
public static void writeWithNullTerminator(final LittleEndianDataOutputStream stream, final String name)
throws IOException {
final byte[] nameBytes = name.getBytes(ParseUtils.UTF8);
stream.write(nameBytes);
stream.write(0);
}
}

View File

@ -1,5 +1,10 @@
package com.etheller.warsmash.util;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
@ -9,6 +14,8 @@ import com.badlogic.gdx.math.Vector3;
public enum RenderMathUtils {
;
public static final float[] EMPTY_FLOAT_ARRAY = new float[0];
public static final long[] EMPTY_LONG_ARRAY = new long[0];
public static final Quaternion QUAT_DEFAULT = new Quaternion(0, 0, 0, 1);
public static final Vector3 VEC3_ONE = new Vector3(1, 1, 1);
public static final Vector3 VEC3_UNIT_X = new Vector3(1, 0, 0);
@ -42,22 +49,22 @@ public enum RenderMathUtils {
final float sy = s.y;
final float sz = s.z;
out.val[Matrix4.M00] = (1 - (yy + zz)) * sx;
out.val[Matrix4.M01] = (xy + wz) * sx;
out.val[Matrix4.M02] = (xz - wy) * sx;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = (xy - wz) * sy;
out.val[Matrix4.M10] = (xy + wz) * sx;
out.val[Matrix4.M20] = (xz - wy) * sx;
out.val[Matrix4.M30] = 0;
out.val[Matrix4.M01] = (xy - wz) * sy;
out.val[Matrix4.M11] = (1 - (xx + zz)) * sy;
out.val[Matrix4.M12] = (yz + wx) * sy;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = (xz + wy) * sz;
out.val[Matrix4.M21] = (yz - wx) * sz;
out.val[Matrix4.M21] = (yz + wx) * sy;
out.val[Matrix4.M31] = 0;
out.val[Matrix4.M02] = (xz + wy) * sz;
out.val[Matrix4.M12] = (yz - wx) * sz;
out.val[Matrix4.M22] = (1 - (xx + yy)) * sz;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M10] * pivot.y)
+ (out.val[Matrix4.M20] * pivot.z));
out.val[Matrix4.M31] = (v.y + pivot.y) - ((out.val[Matrix4.M01] * pivot.x) + (out.val[Matrix4.M11] * pivot.y)
+ (out.val[Matrix4.M21] * pivot.z));
out.val[Matrix4.M32] = (v.z + pivot.z) - ((out.val[Matrix4.M02] * pivot.x) + (out.val[Matrix4.M12] * pivot.y)
out.val[Matrix4.M32] = 0;
out.val[Matrix4.M03] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M01] * pivot.y)
+ (out.val[Matrix4.M02] * pivot.z));
out.val[Matrix4.M13] = (v.y + pivot.y) - ((out.val[Matrix4.M10] * pivot.x) + (out.val[Matrix4.M11] * pivot.y)
+ (out.val[Matrix4.M12] * pivot.z));
out.val[Matrix4.M23] = (v.z + pivot.z) - ((out.val[Matrix4.M20] * pivot.x) + (out.val[Matrix4.M21] * pivot.y)
+ (out.val[Matrix4.M22] * pivot.z));
out.val[Matrix4.M33] = 1;
}
@ -86,20 +93,20 @@ public enum RenderMathUtils {
final float sy = s.y;
final float sz = s.z;
out.val[Matrix4.M00] = (1 - (yy + zz)) * sx;
out.val[Matrix4.M01] = (xy + wz) * sx;
out.val[Matrix4.M02] = (xz - wy) * sx;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = (xy - wz) * sy;
out.val[Matrix4.M10] = (xy + wz) * sx;
out.val[Matrix4.M20] = (xz - wy) * sx;
out.val[Matrix4.M30] = 0;
out.val[Matrix4.M01] = (xy - wz) * sy;
out.val[Matrix4.M11] = (1 - (xx + zz)) * sy;
out.val[Matrix4.M12] = (yz + wx) * sy;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = (xz + wy) * sz;
out.val[Matrix4.M21] = (yz - wx) * sz;
out.val[Matrix4.M21] = (yz + wx) * sy;
out.val[Matrix4.M31] = 0;
out.val[Matrix4.M02] = (xz + wy) * sz;
out.val[Matrix4.M12] = (yz - wx) * sz;
out.val[Matrix4.M22] = (1 - (xx + yy)) * sz;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = v.x;
out.val[Matrix4.M31] = v.y;
out.val[Matrix4.M32] = v.z;
out.val[Matrix4.M32] = 0;
out.val[Matrix4.M03] = v.x;
out.val[Matrix4.M13] = v.y;
out.val[Matrix4.M23] = v.z;
out.val[Matrix4.M33] = 1;
}
@ -158,27 +165,27 @@ public enum RenderMathUtils {
final float far) {
final float f = 1.0f / (float) Math.tan(fovy / 2), nf;
out.val[Matrix4.M00] = f / aspect;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = 0;
out.val[Matrix4.M11] = f;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = 0;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M23] = -1;
out.val[Matrix4.M30] = 0;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M11] = f;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M31] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M32] = -1;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M33] = 0;
if (!Double.isNaN(far) && !Double.isInfinite(far)) {
nf = 1 / (near - far);
out.val[Matrix4.M22] = (far + near) * nf;
out.val[Matrix4.M32] = (2 * far * near) * nf;
out.val[Matrix4.M23] = (2 * far * near) * nf;
}
else {
out.val[Matrix4.M22] = -1;
out.val[Matrix4.M32] = -2 * near;
out.val[Matrix4.M23] = -2 * near;
}
return out;
}
@ -189,32 +196,32 @@ public enum RenderMathUtils {
final float bt = 1 / (bottom - top);
final float nf = 1 / (near - far);
out.val[Matrix4.M00] = -2 * lr;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M03] = 0;
out.val[Matrix4.M10] = 0;
out.val[Matrix4.M11] = -2 * bt;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M13] = 0;
out.val[Matrix4.M20] = 0;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M22] = 2 * nf;
out.val[Matrix4.M23] = 0;
out.val[Matrix4.M30] = 0;
out.val[Matrix4.M30] = (left + right) * lr;
out.val[Matrix4.M31] = (top + bottom) * bt;
out.val[Matrix4.M32] = (far + near) * nf;
out.val[Matrix4.M01] = 0;
out.val[Matrix4.M11] = -2 * bt;
out.val[Matrix4.M21] = 0;
out.val[Matrix4.M31] = 0;
out.val[Matrix4.M02] = 0;
out.val[Matrix4.M12] = 0;
out.val[Matrix4.M22] = 2 * nf;
out.val[Matrix4.M32] = 0;
out.val[Matrix4.M03] = (left + right) * lr;
out.val[Matrix4.M13] = (top + bottom) * bt;
out.val[Matrix4.M23] = (far + near) * nf;
out.val[Matrix4.M33] = 1;
return out;
}
public static void unpackPlanes(final Vector4[] planes, final Matrix4 m) {
final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M01], a02 = m.val[Matrix4.M02],
a03 = m.val[Matrix4.M03], a10 = m.val[Matrix4.M10], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M12],
a13 = m.val[Matrix4.M13], a20 = m.val[Matrix4.M20], a21 = m.val[Matrix4.M21], a22 = m.val[Matrix4.M22],
a23 = m.val[Matrix4.M23], a30 = m.val[Matrix4.M30], a31 = m.val[Matrix4.M31], a32 = m.val[Matrix4.M32],
final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M10], a02 = m.val[Matrix4.M20],
a03 = m.val[Matrix4.M30], a10 = m.val[Matrix4.M01], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M21],
a13 = m.val[Matrix4.M31], a20 = m.val[Matrix4.M02], a21 = m.val[Matrix4.M12], a22 = m.val[Matrix4.M22],
a23 = m.val[Matrix4.M32], a30 = m.val[Matrix4.M03], a31 = m.val[Matrix4.M13], a32 = m.val[Matrix4.M23],
a33 = m.val[Matrix4.M33];
// Left clipping plane
@ -443,4 +450,37 @@ public enum RenderMathUtils {
return out;
}
public static ShortBuffer wrapFaces(final int[] faces) {
final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder())
.asShortBuffer();
for (final int face : faces) {
wrapper.put((short) face);
}
wrapper.clear();
return wrapper;
}
public static ByteBuffer wrap(final byte[] skin) {
final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder());
wrapper.put(skin);
wrapper.clear();
return wrapper;
}
public static FloatBuffer wrap(final float[] positions) {
final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer();
wrapper.put(positions);
wrapper.clear();
return wrapper;
}
public static Buffer wrap(final short[] cornerTextures) {
final ByteBuffer wrapper = ByteBuffer.allocateDirect(cornerTextures.length).order(ByteOrder.nativeOrder());
for (final short face : cornerTextures) {
wrapper.put((byte) face);
}
wrapper.clear();
return wrapper;
}
}

View File

@ -0,0 +1,22 @@
package com.etheller.warsmash.util;
import java.util.HashSet;
import java.util.Set;
public abstract class SubscriberSetNotifier<LISTENER_TYPE> {
protected final Set<LISTENER_TYPE> set; // bad for iteration but there
// should never be a dude subscribed
// 2x
public SubscriberSetNotifier() {
this.set = new HashSet<>();
}
public final void subscribe(final LISTENER_TYPE listener) {
this.set.add(listener);
}
public final void unsubscribe(final LISTENER_TYPE listener) {
this.set.remove(listener);
}
}

View File

@ -564,10 +564,10 @@ public class Vector4 implements Serializable, Vector<Vector4> {
public static Vector4 transformMat4(final Vector4 out, final Vector4 a, final Matrix4 matrix) {
final float x = a.x, y = a.y, z = a.z, w = a.w;
final float[] m = matrix.val;
out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M10] * y) + (m[Matrix4.M20] * z) + (m[Matrix4.M30] * w);
out.y = (m[Matrix4.M01] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M21] * z) + (m[Matrix4.M31] * w);
out.z = (m[Matrix4.M02] * x) + (m[Matrix4.M12] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M32] * w);
out.w = (m[Matrix4.M03] * x) + (m[Matrix4.M13] * y) + (m[Matrix4.M23] * z) + (m[Matrix4.M33] * w);
out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M01] * y) + (m[Matrix4.M02] * z) + (m[Matrix4.M03] * w);
out.y = (m[Matrix4.M10] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M12] * z) + (m[Matrix4.M13] * w);
out.z = (m[Matrix4.M20] * x) + (m[Matrix4.M21] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M23] * w);
out.w = (m[Matrix4.M30] * x) + (m[Matrix4.M31] * y) + (m[Matrix4.M32] * z) + (m[Matrix4.M33] * w);
return out;
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.util;
public class WarsmashConstants {
public static final int MAX_PLAYERS = 16;
}

View File

@ -1,116 +0,0 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
public class BoundingShape extends SceneNode {
private final float[] min = new float[] { -1, -1, -1 };
private final float[] max = new float[] { 1, 1, 1 };
private float radius = (float) Math.sqrt(2);
public void fromBounds(final float[] min, final float[] max) {
System.arraycopy(min, 0, this.min, 0, this.min.length);
System.arraycopy(max, 0, this.max, 0, this.max.length);
final float dX = max[0] - min[0];
final float dY = max[1] - min[1];
final float dZ = max[2] - min[2];
this.radius = (float) Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) / 2;
}
public void fromRadius(final float radius) {
final float s = (float) (radius * Math.cos(radius));
this.min[0] = this.min[1] = this.min[2] = s;
this.max[0] = this.max[1] = this.max[2] = s;
this.radius = radius;
}
public void fromVertices(final float[] vertices) {
final float[] min = new float[] { 1E9f, 1E9f, 1E9f };
final float[] max = new float[] { -1E9f, -1E9f, -1E9f };
for (int i = 0, l = vertices.length; i < l; i += 3) {
final float x = vertices[i];
final float y = vertices[i + 1];
final float z = vertices[i + 2];
if (x > max[0]) {
max[0] = x;
}
if (x < min[0]) {
min[0] = x;
}
if (y > max[1]) {
max[1] = y;
}
if (y < min[1]) {
min[1] = y;
}
if (z > max[2]) {
max[2] = z;
}
if (z < min[2]) {
min[2] = z;
}
}
fromBounds(min, max);
}
public Vector3 getPositiveVertex(final Vector3 out, final Vector3 normal) {
if (normal.x >= 0) {
out.x = this.max[0];
}
else {
out.x = this.min[0];
}
if (normal.y >= 0) {
out.y = this.max[1];
}
else {
out.y = this.min[1];
}
if (normal.z >= 0) {
out.z = this.max[2];
}
else {
out.z = this.min[2];
}
return out;
}
public Vector3 getNegativeVertex(final Vector3 out, final Vector3 normal) {
if (normal.x >= 0) {
out.x = this.min[0];
}
else {
out.x = this.max[0];
}
if (normal.y >= 0) {
out.y = this.min[1];
}
else {
out.y = this.max[1];
}
if (normal.z >= 0) {
out.z = this.min[2];
}
else {
out.z = this.max[2];
}
return out;
}
@Override
protected void updateObject(final Scene scene) {
}
@Override
protected void convertBasis(final Quaternion computedRotation) {
// TODO ???
}
}

View File

@ -1,28 +0,0 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.viewer.ModelView.SceneData;
public class Bucket {
private final ModelView modelView;
private final Model model;
private final int count;
public Bucket(final ModelView modelView) {
final Model model = modelView.model;
final GL20 gl = model.getViewer().gl;
this.modelView = modelView;
this.model = model;
this.count = 0;
// this.instanceIdBuffer =
}
public int fill(final SceneData data, final int baseInstance, final Scene scene) {
// Make believe the bucket is now filled with data for all instances.
// This is because if a non-specific bucket implementation is supplied,
// instancing isn't used, so batching is irrelevant.
return data.instances.size();
}
}

View File

@ -1,314 +0,0 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.Vector4;
public class Camera {
private static final Vector3 vectorHeap = new Vector3();
private static final Vector3 vectorHeap2 = new Vector3();
private static final Vector3 vectorHeap3 = new Vector3();
private static final Quaternion quatHeap = new Quaternion();
private static final Matrix4 matHeap = new Matrix4();
private final Rectangle rect;
private boolean isPerspective;
private float fov;
private float aspect;
private boolean isOrtho;
private float leftClipPlane;
private float rightClipPlane;
private float bottomClipPlane;
private float topClipPlane;
private float nearClipPlane;
private float farClipPlane;
private final Vector3 location;
private final Quaternion rotation;
public Quaternion inverseRotation;
private final Matrix4 worldMatrix;
private final Matrix4 projectionMatrix;
private final Matrix4 worldProjectionMatrix;
private final Matrix4 inverseWorldMatrix;
private final Matrix4 inverseRotationMatrix;
private final Matrix4 inverseWorldProjectionMatrix;
private final Vector3 directionX;
private final Vector3 directionY;
private final Vector3 directionZ;
private final Vector3[] vectors;
private final Vector3[] billboardedVectors;
private final Vector4[] planes;
private boolean dirty;
public Camera() {
// rencered viewport
this.rect = new Rectangle();
// perspective values
this.isPerspective = true;
this.fov = 0;
this.aspect = 0;
// Orthogonal values
this.isOrtho = false;
this.leftClipPlane = 0f;
this.rightClipPlane = 0f;
this.bottomClipPlane = 0f;
this.topClipPlane = 0f;
// Shared values
this.nearClipPlane = 0f;
this.farClipPlane = 0f;
// World values
this.location = new Vector3();
this.rotation = new Quaternion();
// Derived values.
this.inverseRotation = new Quaternion();
this.worldMatrix = new Matrix4();
this.projectionMatrix = new Matrix4();
this.worldProjectionMatrix = new Matrix4();
this.inverseWorldMatrix = new Matrix4();
this.inverseRotationMatrix = new Matrix4();
this.inverseWorldProjectionMatrix = new Matrix4();
this.directionX = new Vector3();
this.directionY = new Vector3();
this.directionZ = new Vector3();
// First four vectors are the corners of a 2x2 rectangle, the last three vectors
// are the unit axes
this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0),
new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) };
// First four vectors are the corners of a 2x2 rectangle billboarded to the
// camera, the last three vectors are the unit axes billboarded
this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(),
new Vector3(), new Vector3(), new Vector3() };
// Left, right, top, bottom, near, far
this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(),
new Vector4() };
this.dirty = true;
}
public void perspective(final float fov, final float aspect, final float near, final float far) {
this.isPerspective = true;
this.isOrtho = false;
this.fov = fov;
this.aspect = aspect;
this.nearClipPlane = near;
this.farClipPlane = far;
this.dirty = true;
}
public void ortho(final float left, final float right, final float bottom, final float top, final float near,
final float far) {
this.isPerspective = false;
this.isOrtho = true;
this.leftClipPlane = left;
this.rightClipPlane = right;
this.bottomClipPlane = bottom;
this.topClipPlane = top;
this.nearClipPlane = near;
this.farClipPlane = far;
}
public void viewport(final Rectangle viewport) {
this.rect.set(viewport);
this.aspect = viewport.width / viewport.height;
this.dirty = true;
}
public void setLocation(final Vector3 location) {
this.location.set(location);
this.dirty = true;
}
public void move(final Vector3 offset) {
this.location.add(offset);
this.dirty = true;
}
public void setRotation(final Quaternion rotation) {
this.rotation.set(rotation);
this.dirty = true;
}
public void rotate(final Quaternion rotation) {
this.rotation.mul(rotation);
this.dirty = true;
}
public void setRotationAngles(final float horizontalAngle, final float verticalAngle) {
this.rotation.idt();
// this.rotateAngles(horizontalAngle, verticalAngle);
throw new UnsupportedOperationException(
"Ghostwolf called a function that does not exist, so I did not know what to do here");
}
public void rotateAround(final Quaternion rotation, final Vector3 point) {
this.rotate(rotation);
quatHeap.conjugate(); // TODO ?????????
vectorHeap.set(this.location);
vectorHeap.sub(point);
rotation.transform(vectorHeap);
vectorHeap.add(point);
this.location.set(vectorHeap);
}
public void setRotationAround(final Quaternion rotation, final Vector3 point) {
this.setRotation(rotation);
;
final float length = vectorHeap.set(this.location).sub(point).len();
quatHeap.conjugate(); // TODO ?????????
vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z);
quatHeap.transform(vectorHeap);
vectorHeap.scl(length);
this.location.set(vectorHeap.add(point));
}
public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) {
quatHeap.idt();
RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle);
RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle);
this.setRotationAround(quatHeap, point);
}
public void face(final Vector3 point, final Vector3 worldUp) {
matHeap.setToLookAt(this.location, point, worldUp);
matHeap.getRotation(this.rotation);
this.dirty = true;
}
public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) {
this.location.set(location);
this.face(target, worldUp);
}
public void reset() {
this.location.set(0, 0, 0);
this.rotation.idt();
this.dirty = true;
}
public void update() {
if (this.dirty) {
this.dirty = true;
final Vector3 location = this.location;
final Quaternion rotation = this.rotation;
final Quaternion inverseRotation = this.inverseRotation;
final Matrix4 worldMatrix = this.worldMatrix;
final Matrix4 projectionMatrix = this.projectionMatrix;
final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix;
final Vector3[] vectors = this.vectors;
final Vector3[] billboardedVectors = this.billboardedVectors;
if (this.isPerspective) {
RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane,
this.farClipPlane);
}
else {
RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane,
this.topClipPlane, this.nearClipPlane, this.farClipPlane);
}
rotation.toMatrix(projectionMatrix.val);
worldMatrix.translate(vectorHeap.set(location).scl(-1));
inverseRotation.set(rotation).conjugate();
// World projection matrix
// World space -> NDC space
worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix);
// Recalculate the camera's frustum planes
RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix);
// Inverse world matrix
// Camera space -> world space
this.inverseWorldMatrix.set(worldMatrix).inv();
this.directionX.set(RenderMathUtils.VEC3_UNIT_X);
inverseRotation.transform(this.directionX);
this.directionY.set(RenderMathUtils.VEC3_UNIT_Y);
inverseRotation.transform(this.directionY);
this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z);
inverseRotation.transform(this.directionZ);
// Inverse world projection matrix
// NDC space -> World space
this.inverseWorldProjectionMatrix.set(worldProjectionMatrix);
this.inverseWorldProjectionMatrix.inv();
for (int i = 0; i < 7; i++) {
billboardedVectors[i].set(vectors[i]);
inverseRotation.transform(billboardedVectors[i]);
}
}
}
public boolean testSphere(final Vector3 center, final float radius) {
for (final Vector4 plane : this.planes) {
if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) {
return false;
}
}
return true;
}
public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.inverseWorldMatrix);
}
public Vector3 worldToCamera(final Vector3 out, final Vector3 v) {
return out.set(v).prj(this.worldMatrix);
}
public float[] screenToWorldRay(final float[] out, final Vector2 v) {
final Vector3 a = vectorHeap;
final Vector3 b = vectorHeap2;
final Vector3 c = vectorHeap3;
final float x = v.x;
final float y = v.y;
final Rectangle viewport = this.rect;
// Intersection on the near-plane
RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport);
// Intersection on the far-plane
RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport);
out[0] = a.x;
out[1] = a.y;
out[2] = a.z;
out[3] = b.x;
out[4] = b.y;
out[5] = b.z;
return out;
}
}

View File

@ -1,9 +0,0 @@
package com.etheller.warsmash.viewer;
public abstract class Model {
private ModelView modelView;
public boolean ok;
public abstract Viewer getViewer();
}

View File

@ -1,5 +0,0 @@
package com.etheller.warsmash.viewer;
public class ModelInstance {
}

View File

@ -1,71 +0,0 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
public abstract class ModelView {
protected final Model model;
protected final HashSet<Object> instanceSet;
protected final HashMap<Scene, SceneData> sceneData;
protected final int renderedInstances;
protected final int renderedParticles;
protected final int renderedBuckets;
protected final int renderedCalls;
public ModelView(final Model model) {
this.model = model;
this.instanceSet = new HashSet<>();
this.sceneData = new HashMap<>();
this.renderedInstances = 0;
this.renderedParticles = 0;
this.renderedBuckets = 0;
this.renderedCalls = 0;
}
public abstract Object getShallowCopy();
public abstract void applyShallowCopy(final Object view);
@Override
public abstract boolean equals(Object view);
@Override
public abstract int hashCode();
// public boo
public void addSceneData(final ModelInstance instance, final Scene scene) {
if (this.model.ok && (scene != null)) {
SceneData data = this.sceneData.get(scene);
if (data == null) {
data = this.createSceneData(scene);
this.sceneData.put(scene, data);
}
}
}
private SceneData createSceneData(final Scene scene) {
return new SceneData(scene, this);
}
public static final class SceneData {
public final Scene scene;
public final ModelView modelView;
public final int baseIndex = 0;
public final List<ModelInstance> instances = new ArrayList<>();
public final List<Bucket> buckets = new ArrayList<>();
public final int usedBuckets = 0;
public SceneData(final Scene scene, final ModelView modelView) {
this.scene = scene;
this.modelView = modelView;
}
}
}

View File

@ -1,5 +0,0 @@
package com.etheller.warsmash.viewer;
public abstract class Scene {
public Camera camera;
}

View File

@ -1,245 +0,0 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SceneNode extends ViewerNode {
public SceneNode() {
}
public SceneNode setPivot(final float[] pivot) {
this.pivot.set(pivot);
this.dirty = true;
return this;
}
public SceneNode setLocation(final float[] location) {
this.localLocation.set(location);
this.dirty = true;
return this;
}
public SceneNode setRotation(final float[] rotation) {
this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]);
this.dirty = true;
return this;
}
public SceneNode setScale(final float[] varying) {
this.localScale.set(varying);
this.dirty = true;
return this;
}
public SceneNode setUniformScale(final float uniform) {
this.localScale.set(uniform, uniform, uniform);
this.dirty = true;
return this;
}
public SceneNode setTransformation(final Vector3 location, final Quaternion rotation, final Vector3 scale) {
// TODO for performance, Ghostwolf did a direct field write on everything here.
// I'm hoping we can get Java's JIT to just figure it out and do it on its own
this.localLocation.set(location);
this.localRotation.set(rotation);
this.localScale.set(scale);
this.dirty = true;
return this;
}
public SceneNode resetTransformation() {
this.pivot.set(Vector3.Zero);
this.localLocation.set(Vector3.Zero);
this.localRotation.set(RenderMathUtils.QUAT_DEFAULT);
this.localScale.set(RenderMathUtils.VEC3_ONE);
this.dirty = true;
return this;
}
public SceneNode movePivot(final float[] offset) {
this.pivot.add(offset[0], offset[1], offset[2]);
this.dirty = true;
return this;
}
public SceneNode move(final float[] offset) {
this.localLocation.add(offset[0], offset[1], offset[2]);
this.dirty = true;
return this;
}
public SceneNode rotate(final Quaternion rotation) {
RenderMathUtils.mul(this.localRotation, this.localRotation, rotation);
this.dirty = true;
return this;
}
public SceneNode rotateLocal(final Quaternion rotation) {
RenderMathUtils.mul(this.localRotation, rotation, this.localRotation);
this.dirty = true;
return this;
}
public SceneNode scale(final float[] scale) {
this.localScale.x *= scale[0];
this.localScale.y *= scale[1];
this.localScale.z *= scale[2];
this.dirty = true;
return this;
}
public SceneNode uniformScale(final float scale) {
this.localScale.x *= scale;
this.localScale.y *= scale;
this.localScale.z *= scale;
this.dirty = true;
return this;
}
public SceneNode setParent(final ViewerNode parent) {
if (this.parent != null) {
this.parent.children.remove(this);
}
this.parent = parent;
if (parent != null) {
parent.children.add(this);
}
this.dirty = true;
return this;
}
public void recalculateTransformation() {
boolean dirty = this.dirty;
final ViewerNode parent = this.parent;
this.wasDirty = this.dirty;
if (parent != null) {
dirty = dirty || parent.wasDirty;
}
this.wasDirty = dirty;
if (dirty) {
this.dirty = false;
if (parent != null) {
Vector3 computedLocation;
Vector3 computedScaling;
final Vector3 parentPivot = parent.pivot;
computedLocation = locationHeap;
computedLocation.x = this.localLocation.x + parentPivot.x;
computedLocation.y = this.localLocation.y + parentPivot.y;
computedLocation.z = this.localLocation.z + parentPivot.z;
if (this.dontInheritScaling) {
computedScaling = scalingHeap;
final Vector3 parentInverseScale = parent.inverseWorldScale;
computedScaling.x = parentInverseScale.x * this.localScale.x;
computedScaling.y = parentInverseScale.y * this.localScale.y;
computedScaling.z = parentInverseScale.z * this.localScale.z;
this.worldScale.x = this.localScale.x;
this.worldScale.y = this.localScale.y;
this.worldScale.z = this.localScale.z;
}
else {
computedScaling = this.localScale;
final Vector3 parentScale = parent.worldScale;
this.worldScale.x = parentScale.x * this.localScale.x;
this.worldScale.y = parentScale.y * this.localScale.y;
this.worldScale.z = parentScale.z * this.localScale.z;
}
RenderMathUtils.fromRotationTranslationScale(this.localRotation, computedLocation, computedScaling,
this.localMatrix);
RenderMathUtils.mul(this.worldMatrix, parent.worldMatrix, this.localMatrix);
RenderMathUtils.mul(this.worldRotation, parent.worldRotation, this.localRotation);
}
else {
RenderMathUtils.fromRotationTranslationScale(this.localRotation, this.localLocation, this.localScale,
this.localMatrix);
this.worldMatrix.set(this.localMatrix);
this.worldRotation.set(this.localRotation);
this.worldScale.set(this.localScale);
}
}
// Inverse world rotation
this.inverseWorldRotation.x = -this.worldRotation.x;
this.inverseWorldRotation.y = -this.worldRotation.y;
this.inverseWorldRotation.z = -this.worldRotation.z;
this.inverseWorldRotation.w = this.worldRotation.w;
// Inverse world scale
this.inverseWorldScale.x = 1 / this.worldScale.x;
this.inverseWorldScale.y = 1 / this.worldScale.y;
this.inverseWorldScale.z = 1 / this.worldScale.z;
// World location
this.worldLocation.x = this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = this.worldMatrix.val[Matrix4.M32];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;
this.inverseWorldLocation.y = -this.worldLocation.y;
this.inverseWorldLocation.z = -this.worldLocation.z;
}
@Override
public void update(final Scene scene) {
if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) {
this.dirty = true;
this.wasDirty = true;
this.recalculateTransformation();
}
else {
this.wasDirty = false;
}
this.updateObject(scene);
this.updateChildren(scene);
}
protected abstract void updateObject(Scene scene);
protected void updateChildren(final Scene scene) {
for (int i = 0, l = this.children.size(); i < l; i++) {
this.children.get(i).update(scene);
}
}
protected abstract void convertBasis(Quaternion computedRotation);
}

View File

@ -1,118 +0,0 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.Descriptor;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SkeletalNode extends ViewerNode {
private final Object object;
private final boolean billboarded = false;
private final boolean billboardedX = false;
private final boolean billboardedY = false;
private final boolean billboardedZ = false;
public SkeletalNode() {
this.object = null;
}
public void recalculateTransformation(final Scene scene) {
final Quaternion computedRotation;
Vector3 computedScaling;
if (this.dontInheritScaling) {
computedScaling = scalingHeap;
final Vector3 parentInverseScale = this.parent.inverseWorldScale;
computedScaling.x = parentInverseScale.x * this.localScale.x;
computedScaling.y = parentInverseScale.y * this.localScale.y;
computedScaling.z = parentInverseScale.z * this.localScale.z;
this.worldScale.x = this.localScale.x;
this.worldScale.y = this.localScale.y;
this.worldScale.z = this.localScale.z;
}
else {
computedScaling = this.localScale;
final Vector3 parentScale = this.parent.worldScale;
this.worldScale.x = parentScale.x * this.worldScale.x;
this.worldScale.y = parentScale.y * this.worldScale.y;
this.worldScale.z = parentScale.z * this.worldScale.z;
}
if (this.billboarded) {
computedRotation = rotationHeap;
computedRotation.set(this.parent.inverseWorldRotation);
computedRotation.mul(scene.camera.inverseRotation);
this.convertBasis(computedRotation);
}
else {
computedRotation = this.localRotation;
}
RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling,
this.localMatrix, this.pivot);
RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix);
RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation);
// Inverse world rotation
this.inverseWorldRotation.x = -this.worldRotation.x;
this.inverseWorldRotation.y = -this.worldRotation.y;
this.inverseWorldRotation.z = -this.worldRotation.z;
this.inverseWorldRotation.w = this.worldRotation.w;
// Inverse world scale
this.inverseWorldScale.x = 1 / this.worldScale.x;
this.inverseWorldScale.y = 1 / this.worldScale.y;
this.inverseWorldScale.z = 1 / this.worldScale.z;
// World location
final float x = this.pivot.x;
final float y = this.pivot.y;
final float z = this.pivot.z;
this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y)
+ (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y)
+ (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y)
+ (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;
this.inverseWorldLocation.y = -this.worldLocation.y;
this.inverseWorldLocation.z = -this.worldLocation.z;
}
protected void updateChildren(final Scene scene) {
for (int i = 0, l = this.children.size(); i < l; i++) {
this.children.get(i).update(scene);
}
}
protected abstract void convertBasis(Quaternion computedRotation);
public static <NODE extends SkeletalNode> Object[] createSkeletalNodes(final int count,
final Descriptor<NODE> nodeDescriptor) {
final List<NODE> nodes = new ArrayList<>();
final List<Matrix4> worldMatrices = new ArrayList<>();
for (int i = 0; i < count; i++) {
final NODE node = nodeDescriptor.create();
nodes.add(node);
worldMatrices.add(node.worldMatrix);
}
final Object[] data = { nodes, worldMatrices };
return data;
}
}

View File

@ -1,7 +0,0 @@
package com.etheller.warsmash.viewer;
import com.badlogic.gdx.graphics.GL20;
public abstract class Viewer {
public GL20 gl;
}

View File

@ -1,65 +0,0 @@
package com.etheller.warsmash.viewer;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
public abstract class ViewerNode {
protected static final Vector3 locationHeap = new Vector3();
protected static final Quaternion rotationHeap = new Quaternion();
protected static final Vector3 scalingHeap = new Vector3();
protected final Vector3 pivot;
protected final Vector3 localLocation;
protected final Quaternion localRotation;
protected final Vector3 localScale;
protected final Vector3 worldLocation;
protected final Quaternion worldRotation;
protected final Vector3 worldScale;
protected final Vector3 inverseWorldLocation;
protected final Quaternion inverseWorldRotation;
protected final Vector3 inverseWorldScale;
protected final Matrix4 localMatrix;
protected final Matrix4 worldMatrix;
protected final boolean dontInheritTranslation;
protected final boolean dontInheritRotation;
protected final boolean dontInheritScaling;
protected boolean visible;
protected boolean wasDirty;
protected boolean dirty;
protected ViewerNode parent;
protected final List<ViewerNode> children;
public ViewerNode() {
this.pivot = new Vector3();
this.localLocation = new Vector3();
this.localRotation = new Quaternion(0, 0, 0, 1);
this.localScale = new Vector3(1, 1, 1);
this.worldLocation = new Vector3();
this.worldRotation = new Quaternion();
this.worldScale = new Vector3();
this.inverseWorldLocation = new Vector3();
this.inverseWorldRotation = new Quaternion();
this.inverseWorldScale = new Vector3();
this.localMatrix = new Matrix4();
this.localMatrix.val[0] = 1;
this.localMatrix.val[5] = 1;
this.localMatrix.val[10] = 1;
this.localMatrix.val[15] = 1;
this.worldMatrix = new Matrix4();
this.dontInheritTranslation = false;
this.dontInheritRotation = false;
this.dontInheritScaling = false;
this.visible = true;
this.wasDirty = false;
this.dirty = true;
this.children = new ArrayList<>();
}
public abstract void update(Scene scene);
}

View File

@ -230,8 +230,6 @@ public class Camera {
public void update() {
if (this.dirty) {
this.dirty = true;
final Vector3 location = this.location;
final Quaternion rotation = this.rotation;
final Quaternion inverseRotation = this.inverseRotation;
@ -281,6 +279,7 @@ public class Camera {
billboardedVectors[i].set(vectors[i]);
inverseRotation.transform(billboardedVectors[i]);
}
this.dirty = false;
}
}

View File

@ -34,6 +34,9 @@ public class EmittedObjectUpdater {
this.objects.set(i, this.objects.remove(this.alive));
i -= 1;
}
else {
this.objects.remove(this.alive);
}
}
}
}

View File

@ -38,7 +38,10 @@ public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJE
}
@Override
public void update(final float dt) {
public void update(final float dt, final boolean objectVisible) {
if (!objectVisible) {
return;
}
this.updateEmission(dt);
final float currentEmission = this.currentEmission;
@ -54,6 +57,9 @@ public abstract class Emitter<MODEL_INSTANCE extends ModelInstance, EMITTED_OBJE
this.alive -= 1;
final EMITTED_OBJECT otherObject = this.objects.get(this.alive);
if (object.index == -1) {
System.err.println("bad");
}
this.objects.set(object.index, otherObject);
this.objects.set(this.alive, object);

View File

@ -0,0 +1,56 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class GdxTextureResource extends Texture {
private com.badlogic.gdx.graphics.Texture gdxTexture;
public GdxTextureResource(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, extension, pathSolver, fetchUrl, handler);
}
public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) {
this.gdxTexture = gdxTexture;
}
@Override
protected void error(final Exception e) {
e.printStackTrace();
}
@Override
public void bind(final int unit) {
this.viewer.webGL.bindTexture(this, unit);
}
@Override
public void internalBind() {
this.gdxTexture.bind();
}
@Override
public int getWidth() {
return this.gdxTexture.getWidth();
}
@Override
public int getHeight() {
return this.gdxTexture.getHeight();
}
@Override
public int getGlTarget() {
return this.gdxTexture.glTarget;
}
public void setWrapS(final boolean wrapS) {
this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap());
}
public void setWrapT(final boolean wrapT) {
this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge);
}
}

View File

@ -3,8 +3,8 @@ package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.math.Vector3;
public class Grid {
private final int x;
private final int y;
private final float x;
private final float y;
private final int width;
private final int depth;
private final int cellWidth;
@ -13,7 +13,7 @@ public class Grid {
private final int rows;
final GridCell[] cells;
public Grid(final int x, int y, final int width, final int depth, final int cellWidth, final int cellDepth) {
public Grid(final float x, float y, final int width, final int depth, final int cellWidth, final int cellDepth) {
final int columns = width / cellWidth;
final int rows = depth / cellDepth;

View File

@ -93,9 +93,6 @@ public abstract class ModelInstance extends Node {
}
public boolean isVisible(final Camera camera) {
if (true) {
return true;
}
final float x = this.worldLocation.x;
final float y = this.worldLocation.y;
final float z = this.worldLocation.z;

View File

@ -21,7 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
public class ModelViewer {
private final DataSource dataSource;
private DataSource dataSource;
public final CanvasProvider canvas;
public List<Resource> resources;
public Map<String, Resource> fetchCache;
@ -45,7 +45,7 @@ public class ModelViewer {
this.resources = new ArrayList<>();
this.fetchCache = new HashMap<>();
this.handlers = new HashSet<ResourceHandler>();
this.frameTime = 1000 / 60;
this.frameTime = 1000 / 6;
this.gl = Gdx.gl;
this.webGL = new WebGL(this.gl);
this.scenes = new ArrayList<>();
@ -71,6 +71,10 @@ public class ModelViewer {
this.textureMappers = new HashMap<Model, List<TextureMapper>>();
}
public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
}
public boolean enableAudio() {
this.audioEnabled = true;
return this.audioEnabled;
@ -200,6 +204,11 @@ public class ModelViewer {
return this.fetchCache.get(key);
}
public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType,
final LoadGenericCallback callback) {
return loadGeneric(path, dataType, callback, this.dataSource);
}
/**
* Load something generic.
*
@ -217,7 +226,7 @@ public class ModelViewer {
* promise resolved to.
*/
public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType,
final LoadGenericCallback callback) {
final LoadGenericCallback callback, final DataSource dataSource) {
final Resource cachedResource = this.fetchCache.get(path);
if (cachedResource != null) {
@ -235,7 +244,7 @@ public class ModelViewer {
// TODO this is a synchronous hack, skipped some Ghostwolf code
try {
resource.loadData(this.dataSource.getResourceAsStream(path), null);
resource.loadData(dataSource.getResourceAsStream(path), null);
}
catch (final IOException e) {
throw new IllegalStateException("Unable to load data: " + path);

View File

@ -21,7 +21,7 @@ public abstract class Node extends GenericNode {
this.localScale = new Vector3(1, 1, 1);
this.worldLocation = new Vector3();
this.worldRotation = new Quaternion();
this.worldScale = new Vector3();
this.worldScale = new Vector3(1, 1, 1);
this.inverseWorldLocation = new Vector3();
this.inverseWorldRotation = new Quaternion();
this.inverseWorldScale = new Vector3();
@ -249,9 +249,9 @@ public abstract class Node extends GenericNode {
this.inverseWorldScale.z = 1 / this.worldScale.z;
// World location
this.worldLocation.x = this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = this.worldMatrix.val[Matrix4.M32];
this.worldLocation.x = this.worldMatrix.val[Matrix4.M03];
this.worldLocation.y = this.worldMatrix.val[Matrix4.M13];
this.worldLocation.z = this.worldMatrix.val[Matrix4.M23];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;

View File

@ -2,4 +2,16 @@ package com.etheller.warsmash.viewer5;
public interface PathSolver {
SolvedPath solve(String src, Object solverParams);
// We generally just use the default path solver.
// These things were apparently meant to work as the Ghostwolf's JavaScript's
// equivalent of the DataSource interface you will find in this Java repo.
// But I did not know that and wasn't sure what it was for, so I kept it in the
// port of his code. Eventually it should be removed.
public static final PathSolver DEFAULT = new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
};
}

View File

@ -0,0 +1,138 @@
package com.etheller.warsmash.viewer5;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
/**
* Similar to GdxTextureResource, but now I'm probably replacing use of that one
* with this one. I'm trying to fight the system here and avoid porting
* Ghostwolf's BLP parser to java, and just use the Java BLP parser that I
* already had, but the libraries are not playing nicely with each other, so
* this class is written to be a lower level solution (OpenGL calls instead of
* LibGDX api) that will work.
*
* My theory is that because doing it THIS way works on Retera Model Studio,
* therefore it should work here as well.
*/
public abstract class RawOpenGLTextureResource extends Texture {
private static final int BYTES_PER_PIXEL = 4;
private final int target;
protected int handle;
private int width;
private int height;
private int wrapS = GL20.GL_CLAMP_TO_EDGE;
private int wrapT = GL20.GL_CLAMP_TO_EDGE;
private final int magFilter = GL20.GL_LINEAR;
private final int minFilter = GL20.GL_LINEAR;
public RawOpenGLTextureResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl, final ResourceHandler handler) {
super(viewer, extension, pathSolver, fetchUrl, handler);
final GL20 gl = this.viewer.gl;
this.handle = gl.glGenTexture();
this.target = GL20.GL_TEXTURE_2D;
gl.glBindTexture(this.target, this.handle);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, this.minFilter);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, this.magFilter);
}
@Override
protected void error(final Exception e) {
e.printStackTrace();
}
@Override
public void bind(final int unit) {
this.viewer.webGL.bindTexture(this, unit);
}
@Override
public void internalBind() {
this.viewer.gl.glBindTexture(this.target, this.handle);
}
@Override
public int getWidth() {
return this.width;
}
@Override
public int getHeight() {
return this.height;
}
@Override
public int getGlTarget() {
return this.target;
}
@Override
public void setWrapS(final boolean wrapS) {
this.wrapS = wrapS ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE;
final GL20 gl = this.viewer.gl;
gl.glBindTexture(this.target, this.handle);
gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_S, this.wrapS);
}
@Override
public void setWrapT(final boolean wrapT) {
this.wrapT = wrapT ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE;
final GL20 gl = this.viewer.gl;
gl.glBindTexture(this.target, this.handle);
gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_T, this.wrapT);
}
public void update(final BufferedImage image) {
final GL20 gl = this.viewer.gl;
final int imageWidth = image.getWidth();
final int imageHeight = image.getHeight();
final int[] pixels = new int[imageWidth * imageHeight];
image.getRGB(0, 0, imageWidth, imageHeight, pixels, 0, imageWidth);
final ByteBuffer buffer = ByteBuffer.allocateDirect(imageWidth * imageHeight * BYTES_PER_PIXEL)
.order(ByteOrder.nativeOrder());
// 4
// for
// RGBA,
// 3
// for
// RGB
for (int y = 0; y < imageHeight; y++) {
for (int x = 0; x < imageWidth; x++) {
final int pixel = pixels[(y * imageWidth) + x];
buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component
buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component
buffer.put((byte) (pixel & 0xFF)); // Blue component
buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component.
// Only for RGBA
}
}
buffer.flip();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.handle);
// if ((this.width == imageWidth) && (this.height == imageHeight)) {
// gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL20.GL_RGBA,
// GL20.GL_UNSIGNED_BYTE, buffer);
// }
// else {
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_SRGB8_ALPHA8, imageWidth, imageHeight, 0, GL20.GL_RGBA,
GL20.GL_UNSIGNED_BYTE, buffer);
this.width = imageWidth;
this.height = imageHeight;
// }
}
}

View File

@ -29,7 +29,7 @@ public class Scene {
public final ModelViewer viewer;
public final Camera camera;
public final Grid grid;
public Grid grid;
public int visibleCells;
public int visibleInstances;
public int updatedParticles;
@ -185,7 +185,8 @@ public class Scene {
if (cell.isVisible(this.camera)) {
this.visibleCells += 1;
for (final ModelInstance instance : cell.instances) {
for (int i = 0, l = cell.instances.size(); i < l; i++) {
final ModelInstance instance = cell.instances.get(i);
if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) {
instance.cullFrame = frame;

View File

@ -10,6 +10,7 @@ import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SkeletalNode extends GenericNode {
protected static final Vector3 locationHeap = new Vector3();
protected static final Quaternion rotationHeap = new Quaternion();
protected static final Quaternion rotationHeap2 = new Quaternion();
protected static final Vector3 scalingHeap = new Vector3();
public UpdatableObject object;
@ -26,7 +27,7 @@ public abstract class SkeletalNode extends GenericNode {
this.localScale = new Vector3(1, 1, 1);
this.worldLocation = new Vector3();
this.worldRotation = new Quaternion();
this.worldScale = new Vector3();
this.worldScale = new Vector3(1, 1, 1);
this.inverseWorldLocation = new Vector3();
this.inverseWorldRotation = new Quaternion();
this.inverseWorldScale = new Vector3();
@ -123,12 +124,12 @@ public abstract class SkeletalNode extends GenericNode {
final float x = this.pivot.x;
final float y = this.pivot.y;
final float z = this.pivot.z;
this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y)
+ (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30];
this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y)
+ (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31];
this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y)
+ (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32];
this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M01] * y)
+ (this.worldMatrix.val[Matrix4.M02] * z) + this.worldMatrix.val[Matrix4.M03];
this.worldLocation.y = (this.worldMatrix.val[Matrix4.M10] * x) + (this.worldMatrix.val[Matrix4.M11] * y)
+ (this.worldMatrix.val[Matrix4.M12] * z) + this.worldMatrix.val[Matrix4.M13];
this.worldLocation.z = (this.worldMatrix.val[Matrix4.M20] * x) + (this.worldMatrix.val[Matrix4.M21] * y)
+ (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M23];
// Inverse world location
this.inverseWorldLocation.x = -this.worldLocation.x;

View File

@ -1,51 +1,26 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class Texture extends HandlerResource<ResourceHandler> {
private com.badlogic.gdx.graphics.Texture gdxTexture;
public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
public Texture(final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl,
final ResourceHandler handler) {
super(viewer, extension, pathSolver, fetchUrl, handler);
}
public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) {
this.gdxTexture = gdxTexture;
}
public abstract void bind(final int unit);
@Override
protected void error(final Exception e) {
e.printStackTrace();
}
public abstract void internalBind();
public void bind(final int unit) {
this.viewer.webGL.bindTexture(this, unit);
}
public abstract int getWidth();
public void internalBind() {
this.gdxTexture.bind();
}
public abstract int getHeight();
public int getWidth() {
return this.gdxTexture.getWidth();
}
public abstract int getGlTarget();
public int getHeight() {
return this.gdxTexture.getHeight();
}
public abstract void setWrapS(final boolean wrapS);
public int getGlTarget() {
return this.gdxTexture.glTarget;
}
public void setWrapS(final boolean wrapS) {
this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap());
}
public void setWrapT(final boolean wrapT) {
this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge);
}
public abstract void setWrapT(final boolean wrapT);
}

View File

@ -1,5 +1,5 @@
package com.etheller.warsmash.viewer5;
public interface UpdatableObject {
void update(float dt);
void update(float dt, boolean visible);
}

View File

@ -3,11 +3,13 @@ package com.etheller.warsmash.viewer5.gl;
import java.nio.Buffer;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
public class DataTexture {
public GL20 gl;
public int texture;
public int format;
public int internalFormat;
public int width = 0;
public int height = 0;
@ -15,11 +17,12 @@ public class DataTexture {
this.gl = gl;
this.texture = gl.glGenTexture();
this.format = (channels == 3 ? GL20.GL_RGB : GL20.GL_RGBA);
this.internalFormat = (channels == 3 ? GL20.GL_RGB : GL30.GL_RGBA32F);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST);
gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
this.reserve(width, height);
@ -33,8 +36,8 @@ public class DataTexture {
this.height = Math.max(this.height, height);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, GL20.GL_FLOAT,
null);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.internalFormat, this.width, this.height, 0, this.format,
GL20.GL_FLOAT, null);
}
}

View File

@ -6,18 +6,16 @@ import java.io.InputStream;
import javax.imageio.ImageIO;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.RawOpenGLTextureResource;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public class BlpTexture extends Texture {
public class BlpTexture extends RawOpenGLTextureResource {
public BlpTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, handler, extension, pathSolver, fetchUrl);
super(viewer, extension, pathSolver, fetchUrl, handler);
}
@Override
@ -30,10 +28,7 @@ public class BlpTexture extends Texture {
BufferedImage img;
try {
img = ImageIO.read(src);
final com.badlogic.gdx.graphics.Texture texture = ImageUtils
.getTexture(ImageUtils.forceBufferedImagesRGB(img));
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
setGdxTexture(texture);
update(img);
}
catch (final IOException e) {
throw new RuntimeException(e);

View File

@ -24,29 +24,33 @@ public class AttachmentInstance implements UpdatableObject {
}
@Override
public void update(final float dt) {
public void update(final float dt, final boolean objectVisible) {
final MdxComplexInstance internalInstance = this.internalInstance;
if (internalInstance.model.ok) {
this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame,
this.instance.counter);
if (visbilityHeap[0] > 0.1) {
// The parent instance might not actually be in a scene.
// This happens if loading a local model, where loading is instant and adding to
// a scene always comes afterwards.
// Therefore, do it here dynamically.
this.instance.scene.addInstance(internalInstance);
if (internalInstance.hidden()) {
internalInstance.show();
// Every time the attachment becomes visible again, restart its first sequence.
internalInstance.setSequence(0);
}
if (!objectVisible) {
internalInstance.hide();
}
else {
internalInstance.hide();
this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame,
this.instance.counter);
if (visbilityHeap[0] > 0.1) {
// The parent instance might not actually be in a scene.
// This happens if loading a local model, where loading is instant and adding to
// a scene always comes afterwards.
// Therefore, do it here dynamically.
this.instance.scene.addInstance(internalInstance);
if (internalInstance.hidden()) {
internalInstance.show();
// Every time the attachment becomes visible again, restart its first sequence.
internalInstance.setSequence(0);
}
}
else {
internalInstance.hide();
}
}
}
}

View File

@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.gl.WebGL;
public class BatchGroup extends GenericGroup {
@ -31,6 +32,7 @@ public class BatchGroup extends GenericGroup {
final List<Integer> replaceables = model.replaceables;
final ModelViewer viewer = model.viewer;
final GL20 gl = viewer.gl;
final WebGL webGL = viewer.webGL;
final boolean isExtended = this.isExtended;
final ShaderProgram shader;
@ -41,7 +43,7 @@ public class BatchGroup extends GenericGroup {
shader = MdxHandler.Shaders.complex;
}
shader.begin();
webGL.useShaderProgram(shader);
shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix);
@ -52,7 +54,7 @@ public class BatchGroup extends GenericGroup {
boneTexture.bind(15);
shader.setUniformf("u_hasBones", 1);
shader.setUniformf("u_boneMap", 15);
shader.setUniformi("u_boneMap", 15);
shader.setUniformf("u_vectorSize", 1f / boneTexture.getWidth());
shader.setUniformf("u_rowSize", 1);
}
@ -86,7 +88,7 @@ public class BatchGroup extends GenericGroup {
shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2);
shader.setUniform2fv("u_uvRot", uvAnim, 2, 2);
shader.setUniform1fv("u_uvRot", uvAnim, 4, 1);
shader.setUniform1fv("u_uvScale", uvAnim, 4, 1);
layer.bind(shader);
@ -101,13 +103,15 @@ public class BatchGroup extends GenericGroup {
}
else {
texture = textures.get(layerTexture);
Texture textureLookup = instance.textureMapper.get(texture);
if (textureLookup == null) {
textureLookup = texture;
}
texture = textureLookup;
}
Texture textureLookup = instance.textureMapper.get(texture);
if (textureLookup == null) {
textureLookup = texture;
}
viewer.webGL.bindTexture(textureLookup, 0);
viewer.webGL.bindTexture(texture, 0);
if (isExtended) {
geoset.bindExtended(shader, layer.coordId);

View File

@ -7,13 +7,21 @@ public class Bone extends GenericObject {
public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) {
super(model, bone, index);
final int geosetAnimationId = bone.getGeosetAnimationId();
if (geosetAnimationId != -1) {
this.geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId);
}
else {
this.geosetAnimation = null;
GeosetAnimation geosetAnimation = null;
final int geosetId = bone.getGeosetId();
if (geosetId != -1) {
final Geoset geoset = model.getGeosets().get(geosetId);
if (geoset.geosetAnimation != null) {
geosetAnimation = geoset.geosetAnimation;
}
else {
final int geosetAnimationId = bone.getGeosetAnimationId();
if (geosetAnimationId != -1) {
geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId);
}
}
}
this.geosetAnimation = geosetAnimation;
}
@Override

View File

@ -30,7 +30,7 @@ public class EmitterGroup extends GenericGroup {
gl.glDisable(GL20.GL_CULL_FACE);
gl.glEnable(GL20.GL_DEPTH_TEST);
shader.begin();
viewer.webGL.useShaderProgram(shader);
shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix);
shader.setUniformf("u_texture", 0);

View File

@ -79,7 +79,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
private float pitch;
private float pitchVariance;
private float volume;
public List<Sound> decodedBuffers;
public List<Sound> decodedBuffers = new ArrayList<>();
/**
* If this is an SPL/UBR emitter object, ok will be set to true if the tables
* are loaded.
@ -111,6 +111,9 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
else if ("UBR".equals(type)) {
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT;
}
else if ("SPN".equals(type)) {
this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN;
}
this.type = type;
this.id = id;
@ -219,6 +222,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
this.columns = getInt(row, "Columns");
this.rows = getInt(row, "Rows");
this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay");
this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") };
this.intervals = new float[][] {
{ getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"),
getFloat(row, "LifespanRepeat") },
@ -262,10 +266,14 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
final GenericResource[] resources = new GenericResource[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
resources[i] = viewer.loadGeneric(
final GenericResource genericResource = viewer.loadGeneric(
pathSolver.solve(((String) animSoundsRow.get("DirectoryBase")) + fileNames[i],
model.solverParams).finalSrc,
FetchDataTypeName.ARRAY_BUFFER, decodedDataCallback);
if (genericResource == null) {
throw new IllegalStateException("Null sound: " + fileNames[i]);
}
resources[i] = genericResource;
}
// TODO JS async removed

View File

@ -6,8 +6,8 @@ public class FilterMode {
private static final int[] ERROR_DEFAULT = new int[] { 0, 0 };
private static final int[] MODULATE_2X = new int[] { GL20.GL_DST_COLOR, GL20.GL_SRC_COLOR };
private static final int[] MODULATE = new int[] { GL20.GL_ZERO, GL20.GL_SRC_COLOR };
private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_ALPHA, GL20.GL_ONE };
private static final int[] BLEND = new int[] { GL20.GL_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA };
private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE };
private static final int[] BLEND = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA };
public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) {
switch (filterMode) {
@ -38,7 +38,7 @@ public class FilterMode {
case MODULATE2X:
return MODULATE_2X; // Modulate 2x
case ALPHAKEY:
return ADDITIVE_ALPHA; // Add alpha
return BLEND; // Add alpha
default:
return ERROR_DEFAULT;
}

View File

@ -62,6 +62,7 @@ public class GeometryEmitterFuncs {
public static final int EMITTER_RIBBON = 1;
public static final int EMITTER_SPLAT = 2;
public static final int EMITTER_UBERSPLAT = 3;
public static final int EMITTER_SPN = 4; // added by Retera because reasons
private static final Vector3 locationHeap = new Vector3();
private static final Vector3 startHeap = new Vector3();
@ -79,7 +80,8 @@ public class GeometryEmitterFuncs {
final int teamColor = instance.teamColor;
int offset = 0;
for (final Particle2 object : objects) {
for (int objectIndex = 0; objectIndex < emitter.alive; objectIndex++) {
final Particle2 object = objects.get(objectIndex);
final int byteOffset = offset * BYTES_PER_OBJECT;
final int floatOffset = offset * FLOATS_PER_OBJECT;
final int p0Offset = floatOffset + FLOAT_OFFSET_P0;
@ -127,6 +129,7 @@ public class GeometryEmitterFuncs {
floatView.put(p0Offset + 8, scale.z);
floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health);
byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail);
byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor);
@ -188,9 +191,9 @@ public class GeometryEmitterFuncs {
shader.setUniform3fv("u_intervals[2]", intervals[2], 0, 3);
shader.setUniform3fv("u_intervals[3]", intervals[3], 0, 3);
shader.setUniform4fv("u_colors[0]", colors[0], 0, 3);
shader.setUniform4fv("u_colors[1]", colors[1], 0, 3);
shader.setUniform4fv("u_colors[2]", colors[2], 0, 3);
shader.setUniform4fv("u_colors[0]", colors[0], 0, 4);
shader.setUniform4fv("u_colors[1]", colors[1], 0, 4);
shader.setUniform4fv("u_colors[2]", colors[2], 0, 4);
shader.setUniform3fv("u_scaling", emitterObject.scaling, 0, 3);
@ -377,9 +380,6 @@ public class GeometryEmitterFuncs {
}
public static void renderEmitter(final MdxEmitter<?, ?, ?> emitter, final ShaderProgram shader) {
if (emitter == null) {
System.err.println("NULL EMITTER");
}
int alive = emitter.alive;
final EmitterObject emitterObject = emitter.emitterObject;
final int emitterType = emitterObject.getGeometryEmitterType();
@ -387,6 +387,9 @@ public class GeometryEmitterFuncs {
if (emitterType == EMITTER_RIBBON) {
alive -= 1;
}
else if (emitterType == EMITTER_SPN) {
return;
}
if (alive > 0) {
final ModelViewer viewer = emitter.instance.model.viewer;
@ -395,6 +398,8 @@ public class GeometryEmitterFuncs {
final GL20 gl = viewer.gl;
final int size = alive * BYTES_PER_OBJECT;
buffer.reserve(size);
switch (emitterType) {
case EMITTER_PARTICLE2:
bindParticleEmitter2Buffer((ParticleEmitter2) emitter, buffer);

View File

@ -94,7 +94,7 @@ public class Geoset {
public void bind(final ShaderProgram shader, final int coordId) {
// TODO use indices instead of strings for attributes
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
// shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8));
shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset);
shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset + 4);

View File

@ -90,10 +90,10 @@ public class Layer extends AnimatedObject {
}
if (this.twoSided != 0) {
gl.glEnable(GL20.GL_CULL_FACE);
gl.glDisable(GL20.GL_CULL_FACE);
}
else {
gl.glDisable(GL20.GL_CULL_FACE);
gl.glEnable(GL20.GL_CULL_FACE);
}
if (this.noDepthTest != 0) {

View File

@ -345,8 +345,8 @@ public class MdxComplexInstance extends ModelInstance {
// This includes attachments and emitters.
final UpdatableObject object = node.object;
if ((object != null) && objectVisible) {
object.update(dt);
if (object != null) {
object.update(dt, objectVisible);
}
// Update all of the node's non-skeletal children, which will update their
@ -462,20 +462,20 @@ public class MdxComplexInstance extends ModelInstance {
for (int i = 0, l = this.worldMatrices.length; i < l; i++) {
final Matrix4 worldMatrix = this.worldMatrices[i];
this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]);
this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M01]);
this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M02]);
this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M03]);
this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M10]);
this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]);
this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]);
this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]);
this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]);
this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]);
this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M12]);
this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M13]);
this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M20]);
this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M21]);
this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]);
this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]);
this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]);
this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]);
this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]);
this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M23]);
this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M30]);
this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M31]);
this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M32]);
this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]);
this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]);
this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]);
this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]);
this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]);
}
this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap);
@ -515,7 +515,7 @@ public class MdxComplexInstance extends ModelInstance {
this.allowParticleSpawn = true;
if (this.frame >= interval[1]) {
if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 0) && (sequence.getFlags() == 0))) {
if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 1) && (sequence.getFlags() == 0))) {
this.frame = (int) interval[0]; // TODO not cast
this.resetEventEmitters();

View File

@ -17,9 +17,12 @@ public abstract class MdxEmitter<MODEL_INSTANCE extends ModelInstance, EMITTER_O
}
@Override
public void update(final float dt) {
public void update(final float dt, final boolean objectVisible) {
if (!objectVisible) {
return;
}
if (this.emitterObject.ok()) {
super.update(dt);
super.update(dt, objectVisible);
}
}
}

View File

@ -32,8 +32,8 @@ public class MdxHandler extends ModelHandler {
public boolean load(final ModelViewer viewer) {
viewer.addHandler(new BlpHandler());
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex);
Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex,
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, MdxShaders.fsComplex);
Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded,
MdxShaders.fsComplex);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles);
Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple);

View File

@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.parsers.mdlx.Extent;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
@ -56,6 +57,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
super(handler, viewer, extension, pathSolver, fetchUrl);
}
@Override
public ModelInstance createInstance(final int type) {
if (type == 1) {
return new MdxSimpleInstance(this);
@ -173,7 +175,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
final List<Texture> teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows;
if (teamColors.isEmpty()) {
for (int i = 0; i < 28; i++) {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
final String id = ReplaceableIds.getIdString(i);
teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt,

View File

@ -10,8 +10,8 @@ public class MdxNode extends SkeletalNode {
@Override
protected void convertBasis(final Quaternion computedRotation) {
computedRotation.mulLeft(HALF_PI_Y);
computedRotation.mulLeft(HALF_PI_X);
computedRotation.mul(HALF_PI_Y);
computedRotation.mul(HALF_PI_X);
}
@Override

View File

@ -40,16 +40,16 @@ public class MdxRenderBatch extends RenderBatch {
final int offset = i * 12;
floatView.put(offset + 0, worldMatrix.val[Matrix4.M00]);
floatView.put(offset + 1, worldMatrix.val[Matrix4.M01]);
floatView.put(offset + 2, worldMatrix.val[Matrix4.M02]);
floatView.put(offset + 3, worldMatrix.val[Matrix4.M03]);
floatView.put(offset + 4, worldMatrix.val[Matrix4.M10]);
floatView.put(offset + 5, worldMatrix.val[Matrix4.M11]);
floatView.put(offset + 6, worldMatrix.val[Matrix4.M12]);
floatView.put(offset + 7, worldMatrix.val[Matrix4.M13]);
floatView.put(offset + 8, worldMatrix.val[Matrix4.M20]);
floatView.put(offset + 9, worldMatrix.val[Matrix4.M21]);
floatView.put(offset + 10, worldMatrix.val[Matrix4.M22]);
floatView.put(offset + 1, worldMatrix.val[Matrix4.M10]);
floatView.put(offset + 2, worldMatrix.val[Matrix4.M20]);
floatView.put(offset + 3, worldMatrix.val[Matrix4.M01]);
floatView.put(offset + 4, worldMatrix.val[Matrix4.M11]);
floatView.put(offset + 5, worldMatrix.val[Matrix4.M21]);
floatView.put(offset + 6, worldMatrix.val[Matrix4.M02]);
floatView.put(offset + 7, worldMatrix.val[Matrix4.M12]);
floatView.put(offset + 8, worldMatrix.val[Matrix4.M22]);
floatView.put(offset + 9, worldMatrix.val[Matrix4.M03]);
floatView.put(offset + 10, worldMatrix.val[Matrix4.M13]);
floatView.put(offset + 11, worldMatrix.val[Matrix4.M23]);
}
@ -88,7 +88,8 @@ public class MdxRenderBatch extends RenderBatch {
transposeHeap.set(this.scene.camera.viewProjectionMatrix);
transposeHeap.tra();
shader.setUniformMatrix4fv("u_VP", transposeHeap.val, 0, transposeHeap.val.length);
shader.setUniformMatrix4fv("u_VP", this.scene.camera.viewProjectionMatrix.val, 0,
this.scene.camera.viewProjectionMatrix.val.length);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer);

View File

@ -66,8 +66,7 @@ public class MdxShaders {
" varying vec2 v_uv;\r\n" + //
" void main() {\r\n" + //
" v_uv = a_uv;\r\n" + //
// " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + //
" gl_Position = u_VP * vec4(a_position, 1.0);\r\n" + //
" gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + //
" }\r\n";
public static final String fsSimple = "\r\n" + //
@ -84,8 +83,7 @@ public class MdxShaders {
" gl_FragColor = color;\r\n" + //
" }\r\n";
public static final String vsComplex = Shaders.boneTexture + "\r\n" + //
" uniform mat4 u_mvp;\r\n" + //
public static final String vsComplex = " uniform mat4 u_mvp;\r\n" + //
" uniform vec4 u_vertexColor;\r\n" + //
" uniform vec4 u_geosetColor;\r\n" + //
" uniform float u_layerAlpha;\r\n" + //
@ -105,6 +103,7 @@ public class MdxShaders {
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void transform(inout vec3 position, inout vec3 normal) {\r\n" + //
" // For the broken models out there, since the game supports this.\r\n" + //
" if (a_boneNumber > 0.0) {\r\n" + //
@ -132,6 +131,7 @@ public class MdxShaders {
" position = p.xyz / a_boneNumber;\r\n" + //
" normal = normalize(n.xyz);\r\n" + //
" }\r\n" + //
"\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" vec3 position = a_position;\r\n" + //
@ -146,6 +146,65 @@ public class MdxShaders {
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String vsComplexUnshaded = " uniform mat4 u_mvp;\r\n" + //
" uniform vec4 u_vertexColor;\r\n" + //
" uniform vec4 u_geosetColor;\r\n" + //
" uniform float u_layerAlpha;\r\n" + //
" uniform vec2 u_uvTrans;\r\n" + //
" uniform vec2 u_uvRot;\r\n" + //
" uniform float u_uvScale;\r\n" + //
" uniform bool u_hasBones;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" attribute vec4 a_bones;\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" attribute vec4 a_extendedBones;\r\n" + //
" #endif\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void transform(inout vec3 position) {\r\n" + //
" // For the broken models out there, since the game supports this.\r\n" + //
" if (a_boneNumber > 0.0) {\r\n" + //
" vec4 position4 = vec4(position, 1.0);\r\n" + //
" mat4 bone;\r\n" + //
" vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_bones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_extendedBones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #endif\r\n" + //
" position = p.xyz / a_boneNumber;\r\n" + //
// " position.x *= fetchMatrix(0.0, 0.0)[0][0];\r\n" + //
" } else {\r\n" + //
" position.x += 100.0;\r\n" + //
" }\r\n" + //
"\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" vec3 position = a_position;\r\n" + //
" if (u_hasBones) {\r\n" + //
" transform(position);\r\n" + //
" }\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + //
" v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + //
" v_uvScale = u_uvScale;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform float u_filterMode;\r\n" + //

Some files were not shown because too many files have changed in this diff Show More