[messages] [Developers] Custom SnapTo

Rhett rtr101 at hotmail.com
Mon May 12 15:46:05 CEST 2014


I have now managed to make a custom modification of VASSAL. What follows
will describe what was done and also serve as a tutorial for other
newbies.

VASSAL uses Java. You don't have to have prior knowledge of Java but you
obviously need some general programming experience (my background is in
C# and .NET).

I used the Eclipse Java IDE[1]. You need to add the library Vengine.jar
which is found in the lib directory of the VASSAL installation. You also
need the Java Developers Kit (JDK)[2].

__Problem:__ I wanted to modify the Wooden Ships & Iron Men[3] module
(an old AH game of naval warfare from the late 18th and early 19th
centuries). The original designer did a good job, but I wanted to add
the real map with shore outlines and make the counters (ships) snap to
hexsides. Making the real map involved scanning in the original map and
removing the grid with GIMP. When it comes to the hexside snapping, the
problem is this: The counters (ships) are two hexes long. Depending on
the facing of each counter, only two sides of every hex are legal
snap-to points. 

__Solution:__ VASSAL is so designed that it dynamically loads classes at
run time. This makes it easy to extend classes and add them to the
buildFile.

After a while I found out that you need to extend three classes: Map,
HexGrid and Board.

Here is the code for MyMap.java:

Code:

package MySnapTo;

import java.awt.Point;
import java.awt.Rectangle;

import VASSAL.build.module.Map;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.counters.GamePiece;
import VASSAL.counters.Properties;
import VASSAL.counters.Stack;



public class MyMap extends Map {
	

	public MyMap() {
		
		super ();
	}
	
	
	public int findFacing() {
		 
		int facing = 1;
		GamePiece piece = null;
		
		GamePiece[] pieces = this.getPieces();
			
			
		for (int i = 0; i < pieces.length; i++)
		{
			
			piece = pieces[i];
			
			if (piece != null)
			{
				Object propertyObject = piece.getProperty(Properties.SELECTED);
				
				if (propertyObject != null)						
					if ((boolean)propertyObject == true)	
						break;
			}
		}
		
		
		if (piece != null)
		{	
			Object propertyObject =	piece.getProperty("_Facing");
			
			if (propertyObject != null)
				facing = Integer.parseInt(propertyObject.toString());
		}
		
	
		return facing;
	}
	
	
	// copy from Map except call to custom snapTo
	@Override
	public Point snapTo(Point p) {
		
		int facing = findFacing();
		
		
		Point snap = new Point(p);

	    final Board b = findBoard(p);
	    
	    if (b == null) return snap;
	    
	    // custom cast
	    final MyBoard mb = (MyBoard) b;

	    final Rectangle r = mb.bounds();
	    snap.translate(-r.x, -r.y);
	    
	    // call to custom snap
	    snap = mb.snapTo(snap, facing);
	    
	    
	    snap.translate(r.x, r.y);
	    
	    if (findBoard(snap) == null) {
	      snap.translate(-r.x, -r.y);
	      if (snap.x == r.width) {
	        snap.x = r.width - 1;
	      }
	      else if (snap.x == -1) {
	        snap.x = 0;
	      }
	      if (snap.y == r.height) {
	        snap.y = r.height - 1;
	      }
	      else if (snap.y == -1) {
	        snap.y = 0;
	      }
	      snap.translate(r.x, r.y);
	    }
	    
	    return snap;
	}
	

}



Here is the code for MyHexGrid.java:

Code:

package MySnapTo;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.List;

import static java.lang.Math.*;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.build.module.Chatter;
import VASSAL.build.module.Map;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.PieceCollection;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.build.module.map.boardPicker.board.HexGrid;
import VASSAL.build.module.map.boardPicker.board.mapgrid.GridNumbering;
import VASSAL.build.module.map.PieceMover;
import VASSAL.command.Command;
import VASSAL.counters.EditablePiece;
import VASSAL.counters.GamePiece;
import VASSAL.counters.PieceFinder.Movable;
import VASSAL.counters.Stack;
import VASSAL.tools.imports.adc2.ADC2Module.Piece;
import VASSAL.counters.*;



public class MyHexGrid extends HexGrid {	

	
	public MyHexGrid ()
	{
		super();

	}
	 
	 
	/*
		holds number of whole dx increments along x-axis
		is communicated from sideX to sideY
	 */
	private int nx = 0;
	 
	/*
		x-coordinate of closest snap point to input x
		facing runs clockwise from 1 to 6 with 1 north
		dx is the vertical length of two hexsides
	*/
	protected int sideX(int x, int facing) 
	{	 
		 double inc = dx;
		 int start = 0;
		  
		
		 if (facing == 1 || facing == 4)
			 start = 0;
		 else // facing 2,3,5,6
			 start = (int) (dx / 2);
		 
		 
		 nx =  (int)(floor(((x - origin.x) / dx) + 0.25));
		 
		 
		 return (int) (nx * inc + start + origin.x);
	}
	
	 	
	
	/*
	 	y-coordinate of closest snap point to input y
	 	facing runs clockwise from 1 to 6 with 1 north
		dy is the horizontal length of two hexsides
	*/
	protected int sideY(int y, int facing)
	{
		 
		double start;
		double inc;
		double interval;
		
		
		inc = dy;
		interval = dy / 2;
		
		
			 
		if (facing == 1 || facing == 4)
		{
			 if (nx % 2 == 0)
				 start = -(dy / 2);
			 else
				 start = 0;	
		}
		else if (facing == 2 || facing == 5)
		{
			 if (nx % 2 == 0)
				 start = -(dy / 4);
			 else
				 start =  dy / 4;
		}
		else // facing 3 and 6
		{
			 if (nx % 2 == 0)
				 start = dy / 4;
			 else
				 start = -(dy / 4);
		}
		
		 
		return (int) ( (int)(floor((y - origin.y - start + interval) / inc) *
inc) + start + origin.y ); 
		
	}
	  
	 
	 
	 /*
	    snap to nearest hexside depending on counter facing
	 */
	 public Point snapToHexSide(Point p, int facing)
	 { 		
		 
	    int x = sideX(p.x, facing);
	    int y = sideY(p.y, facing);
		
	    Point result = new Point(x, y);
	    
	    return result;
	 }
	 
	 
	 /*
 		my SnapTo with facing
	  */
	 public Point snapTo(Point p, int facing) {
		 
		 // make sure we have a legal facing
		 if (facing < 1 || facing > 6)
			 facing = 1;
		 
		 Point n  = snapToHexSide(p, facing);
		 
		 return n;
	 }
	 

	
	public void draw(Graphics arg0, Rectangle arg1, Rectangle arg2,
			double arg3, boolean arg4) {	
		 
		 super.draw(arg0, arg1, arg2, arg3, arg4);
	}


	// snipped rest of calls to super functions

        .
        .
        .
	

	public static void main(String[] args) {
		
	}


}



Finally the code for MyBoard.java:

Code:

package MySnapTo;

import java.awt.Point;

import MySnapTo.MyHexGrid;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.build.module.map.boardPicker.board.HexGrid;

public class MyBoard extends Board {
	

	public MyBoard() {
		
		super();
		
	}
	
	
	public Point snapTo(Point p, int facing) {
		
		if (grid instanceof HexGrid)
		{
			MyHexGrid mgrid = (MyHexGrid)grid;
			return mgrid == null ? p :
globalCoordinates(mgrid.snapTo(localCoordinates(p), facing));
		}
		else
			return grid == null ? p :
globalCoordinates(grid.snapTo(localCoordinates(p)));
		
	  }

}



I changed the buildFile to use the new classes MyMap, MyBoard and
MyHexGrid (from the package MySnapTo):

Code:

<MySnapTo.MyMap allowMultiple="false" backgroundcolor="255,255,255" 
...... >
        <VASSAL.build.module.map.BoardPicker addColumnText="Add column"
....... >
            <MySnapTo.MyBoard image="wsim-new.png" name="Board 1"
reversible="false">
                <MySnapTo.MyHexGrid color="102,102,102"
cornersLegal="false" ....... >
                   
<VASSAL.build.module.map.boardPicker.board.mapgrid.HexGridNumbering
...... >
                </MySnapTo.MyHexGrid>
            </MySnapTo.MyBoard>
        </VASSAL.build.module.map.BoardPicker>
   
	.
	.

</MySnapTo.MyMap>



It all looks simple when done, but understanding and modifying software
you have not made yourself is never easy.


A few issues:

  1.  When a counter is first dragged onto the map it sometimes snaps
wrongly. I have not been able to reproduce this error consistently.
  2. I discovered that the GamePiece array increases in length by simply
pivoting a counter (?) 
  3. Also note that the code above works only for a map with
non-stackable counters. If stacking is allowed then a GamePiece must be
cast to a Stack and topPiece() called.
  4.  How do you load a custom class that is not Buildable (for instance
an extension of a class in Counters)?




But it was fun doing this. I learned a lot and I have to compliment the
developers that made VASSAL.

[1] http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/keplersr1
[2] http://www.oracle.com/technetwork/java/javase/downloads/index.html
[3] http://en.wikipedia.org/wiki/Wooden_Ships_and_Iron_Men


_______________________________________________
Read this topic online here:
http://www.vassalengine.org/forum/viewtopic.php?p=44898#p44898


More information about the messages mailing list