Creating Custom Classes

Custom VASSAL classes and elements

Custom Classes allow you to override parts of VASSAL’s code to create your own custom features. They can range from overriding single aspects of VASSAL, such as an oblique hexagonal grid numbering, to a larger collection of classes that touch almost every aspects of VASSAL e.g., VASL.

For simple custom code, a simplified workflow can be used, while for a larger collection, a full workflow is more appropriate.

Assumptions

The below assumes that you are

  • Familiar with Java and development with Java.
  • Familiar with running programs from the command line. See also here.
  • Familiar with extracting and updating files in a ZIP archive.
  • A fair bit of patience :slight_smile:

Simple Workflow

If you plan to override a single or a few features of VASSAL - for example to have obligue hexagonal grid numbering - you typically only need to provide a few user defined Java classes. For this kind of workflow, you need

  • A code editor. Examples are anything from a general purpose text editor, over more dedicated code editors, to fully fledge Integrated Development Environments (IDE). Some examples of editors are given below

    Editor Platform Type
    NotePad windows General purpose
    TextEdit macosx General purpose
    TextEditor windows macosx linux General purpose
    Vi windows macosx linux General purpose
    Emacs windows macosx linux General purpose
    BlueJ windows macosx linux Code editor
    Visual Studio Code windows macosx linux Code editor
    XCode macosx Code editor
    Kate windows macosx linux Code editor
    IntelliJ windows macosx linux IDE
    Eclipse windows macosx linux IDE
    JDeveloper windows macosx linux IDE

    Choose what you are comfortable with - not what has the most features - you don’t need them.

  • The location of the VASSAL Java class archive Vengine.jar. Presumably you have already installed VASSAL on your target machine, and you will therefore have that file. If not, then download and install VASSAL

    If VASSAL is installed in directory then you can find the file in some sub-directory of directory, often directory/lib/Vengine.jar. Some common examples are

    Platform VASSAL installation Vengine.jar location
    windows C:\Program Files\VASSAL C:\Program Files\VASSAL\lib\Vengine.jar
    windows C:\Users\username\AppData\Programs\VASSAL C:\Users\username\AppData\Programs\VASSAL\lib\Vengine.jar
    macosx /Applications/VASSAL.app/Contents/MacOS/ /Applications/VASSAL.app/Contents/Resources/Java/Vengine.jar
    macosx /home/username/Applications/VASSAL.app/Contents/MacOS/ /home/username/Applications/VASSAL.app/Contents/Resources/Java/Vengine.jar
    linux /opt/VASSAL /opt/VASSAL/lib/Vengine.jar
    linux /home/username/VASSAL /home/username/VASSAL/lib/Vengine.jar

    where username is your local user name.

  • A Java compiler.

    • windows Download and install from Oracle*
    • macosx Download and install from Oracle*
    • linux Use your package manager to install OpenJDK - for example
      $ sudo apt install default-jdk
      
      Chances are you already have it, in which case the command above will tell you so.
  • An internet connection so you can look at the VASSAL source code at GitHub.

* Although VASSAL comes with a full Java Runtime Environment (JRE) on these platforms, it does not include the Java compiler.

You may also want to look at some examples and templates.

Write your custom code - say MyElement.java - in your editor of choice. Once you have completed that, then you need to compile it into a class

$ javac -cp Vengine_file_location MyElement.java

where Vengine_file_location is the location of Vengine.jar as discussed above. This will create MyElement.class which can be included into your VASSAL module.

See also here for more on how to run programs from the command line.

For example, if VASSAL is installed in /opt/VASSAL, then the above command line will be

$ javac -cp /opt/VASSAL/lib/Vengine.jar MyElement.java

If you have more files - say A,java and B.java - you can compile them in one go or one at a time - e.g., with VASSAL installed in C:\Program Files\VASSAL

$ javac -cp "C:\Program Files\VASSAL\lib\Vengine.jar" A.java B.java

Full Workflow

In order to get started creating a custom class, you should first:

Now, make a separate folder for your custom classes project (NOT under the folder where you have VASSAL code stored, etc), and start a new git local repository there.

$ mkdir MyCustomClasses
$ cd MyCustomClasses
$ git init

Then, clone the VASSAL custom code template:

$ git clone https://github.com/vassalengine/vassal-module-template

That grabs our module template (source code for sample custom classes, and a Maven project file) from the VASSAL Git repository.

Then, perform these steps:

  • Launch IntelliJ
  • From the File menu, select Open…
  • Browse to your MyCustomClasses folder, and inside the vassal-module-template sub-directory, open the pom.xml file.
  • You will be informed this is a Maven project and asked if you want to open it as a project - click Yes.
  • You may need to adjust the location of your VASSAL installation in the project settings.

The project will now open, and you will see the VASSAL custom class “template”, which contains examples of how to create custom classes (e.g. a MyChatter class to customize the Chat Log). You can build these and experiment with them if you like, and learn how to install them in your modules.

Information on how to install a particular class in your module is contained in the comments of the .java files in the template, e.g. MyChatter.java..java.

Once you’ve experimented with using the sample custom classes we have provided, you are ready to branch out and start customizing classes in your own way!

Install classes into a module

Add the .class files to the module

  • Using what ever ZIP file manager you like, open up the module file (.vmod) in that manager.

    Note, some platforms - notably windows Windows - associate file types with file-name endings, rather than the actual file content. On those platforms you may need to temporarily rename the module from ending in .vmod to .zip.

  • Find the .class files that you have compiled, and add them to the archive.

    • If you have put your classes into a package or packages, you must add your classes with a corresponding directory structure rooted in the root of the ZIP archive.

      For example, suppose you have the class

      package ak;
      
      class ObliqueHexGridNumbering ...
      

      Then the class file ObliqueHexGridNumbering.class must be added to a sub-directory named ak inside the ZIP archive.

      If you have multiple packages or nested packages, be sure to add your .class files into the correct sub-directory that corresponds to those packages.

  • When you have added your .class files, close the ZIP file manager.

    If you had to rename your module from ending in .vmod to .zip, above, then you should rename the module file back to ending in .vmod now.

Use the custom class in the VASSAL Editor

  • Right-click the element to which you want to add a custom element.

    For example, if you want to add an oblique hexagonal grid numbering to some hexagonal grid, you should right click the [Hex Grid] element.

  • In the context menu, select Add imported class

  • This will open a simple dialog in which you must enter the fully qualified class name.

    Continuing the example above were we created the class ObliqueHexGridNumbering in the package ak, the fully qualified class name is ak.ObliqueHexGridNumbering which is what should be entered into the dialog box.

  • Press OK.

  • Now, you can double-click the user defined element to edit its properties.

Modifying the buildFile.xml directly

  • Open up the module file (.vmod) in your favourite ZIP file manager (see above).

  • Find the file buildFile.xml at the root of ZIP archive.

  • If you ZIP file manager allows you to modify files in-place, then go a head and open the buildFile.xml in your favourite text editor.

    If the ZIP file manager cannot modify files in-place, then extract the buildFile.xml file to disk, and then open it up in your favourite text editor.

  • Find the element you want to add your custom code to.

    For example, if you are adding an oblique hexagonal grid numbering to a hex grid, find that hex grid tag

    <VASSAL.build.module.map.boardPicker.board.HexGrid ...>
    </VASSAL.build.module.map.boardPicker.board.HexGrid>
    
  • Add the custom element to that element.

    Continuing the above example

    <VASSAL.build.module.map.boardPicker.board.HexGrid ...>
      <ak..ObliqueHexGridNumbering ...>
      </ak..ObliqueHexGridNumbering>
    </VASSAL.build.module.map.boardPicker.board.HexGrid>
    

    Note that the XML tag must be the fully qualified class name.

  • Save the buildFile.xml.

    If your ZIP file manager allows you to do in-place modifications, then you should be done.

    If not, then open the module in the ZIP file manager and add the updated buildFile.xml. Save and close the module file.

Walk-through of an example

Let us walk through ObliqueHexGridNumbering.java mentioned a couple of times above.

To start

package ak;

We define the package we want to put the code into. Here ak (because this code was originally written for Afrika Korps)

Next, we import some classes that we need.

import java.awt.Point;
import org.apache.commons.lang3.ArrayUtils;
import VASSAL.build.Buildable;

We want to make a new kind of hex grid numbering, so we want to create a derivative of the standard HexGridNumbering numbering class. Thus we import that too.

import VASSAL.build.module.map.boardPicker.board.mapgrid.HexGridNumbering;

Now for our code. We will call our class ObliqueHexGridNumbering which will be public and derive from the standard HexGridNumbering.

Note, we could have made the package oblique and then called the class HexGridNumbering, because we will use the fully qualified class names and hence there’s no problem with name collisions.

public class ObliqueHexGridNumbering extends HexGridNumbering {

We will add the attribute direction to the other attributes of the default HexGridNumbering. This member (a boolean) will say whether the grid slants right or left

    public boolean direction = true;
    public static final String DIRECTION = "direction"; //NON-NLS

Here, we’ve also added the string constant DIRECTION which we will use to define the editable interface.

Next up, we override the methods getAttributeDescriptions, getAttributeNames, getAttributeTypes, and getAttributeValueString to add our DIRECTION attribute to the attributes of the default HexGridNumbering

    @Override
    public String[] getAttributeDescriptions() {
	    return ArrayUtils.add(super.getAttributeDescriptions(),
		  	      "Slanted right");
    }

    @Override
    public String[] getAttributeNames() {
	    return ArrayUtils.add(super.getAttributeNames(), DIRECTION);
    }

    @Override
    public Class<?>[] getAttributeTypes() {
	    return ArrayUtils.add(super.getAttributeTypes(),Boolean.class);
    }
    
    @Override
    public String getAttributeValueString(String key) {
	    if (DIRECTION.equals(key)) 
	        return String.valueOf(direction);
	    else 
	        return super.getAttributeValueString(key);
    }

These four methods will alert the VASSAL editor and back-end to the existence of the DIRECTION attribute. Next, we also need to be able to set that attribute

    @Override
    public void setAttribute(String key, Object value) {
	    if (DIRECTION.equals(key)) {
	        if (value instanceof String) 
		        value = Boolean.valueOf((String) value);

	        direction = (Boolean) value;
	    }
	    else 
	        super.setAttribute(key, value);	
    }

That’s it for the attribute.

Now for the real meat of the class. We want to change the numbering of hex rows so that it becomes oblique. This we do in the two following methods

    @Override
    public int getRow(Point p) {
  	    return slantRow(super.getRow(p),super.getColumn(p));
    }
    public int slantRow(int row, int column)
    {
	    return direction ?
	        (int)Math.floor(column/2) + row :
	        row - (int)Math.floor(column/2)+1;
    }

The method getRow overrides the same method from the default HexGridNumbering, and uses the computations done by slantRow. In that method, we calculate the new row number based on both the current row and column number.

That finishes our class.

}

Pretty simple, eh!?