- Joined
- Aug 3, 2008

- Messages
- 2,345

Introduction:

I decided to make these scripts because I was making a spell that changed the terrain temporarily, and I ran into all kinds of problems. How to store the old terrain that was there was the problem: I fixed that by making a simple array of values holding the terrain type and variation of the original. It worked great until I cast another spell overlapping the first -- by the time both spells had expired, the terrain stayed changed where they overlapped. I tried to build something into the spell, but it was futile -- it was too complicated to incorporate into a spell script. I needed something else. That's why I wrote these libraries.

So what do they do?

TerrainTile can change the terrain at any given point, and no matter how many are created over each other, when they are all removed, the original tile will still be there.

TerrainSwap is a holder for multiple TerrainTiles. It allows you to create multiple TerrainTiles over any area, by a selection of several methods, and apply or remove them all at once.

Here is the code:

Requires LinkedList by Ammorth

[jass=TerrainTile]/***************************************************************************

* TerrainTile by Element of Water

****************************************************************************

* What is it?

* TerrainTile provides a way to swap any single terrain tile on the map

* with a different one, just like SetTerrainType. The difference is, with

* this system, when the TerrainTile is destroyed, the tile that was

* originally there, that the TerrainTile overwrote, will reappear. You

* can also stack multiple TerrainTiles on top of one another, and

* destroying the top one will reveal the next, etc, in the same order

* they were applied in. Don't worry about destroying tiles that weren't

* on top, either -- they will be removed from the list.

****************************************************************************

* How do I use it?

* Using TerrainTile couldn't be easier -- there are just a few functions

* to call to control it.

* 1) Create your TerrainTile:

* set MyTerrainTile = TerrainTile.create(real x, real y, integer terrainType, integer variation)

* real x/y -- the coordinates of your TerrainTile

* integer terrainType -- the terrain tile's type id, this can be found

* by converting an Environment - Change Terrain

* Type GUI action into JASS and copying the value

* in 'AAAA' that you see there

* integer variation -- this is just the same as the "variation" when

* placing tiles in the terrain editor, except

* you can have a variation of -1, which means

* "random variation"

* 2) Apply (show) your TerrainTile:

* call MyTerrainTile.apply()

* 3) Change its properties:

* set MyTerrainTile.terrainType = NewTerrainType

* set MyTerrainTile.variation = NewVariation

* 4) Reapply it, or change its location:

* call MyTerrainTile.setCoords(newX, newY)

* 5) When you're done with it, but you'll use it again, remove it:

* call MyTerrainTile.remove()

* 6) Or if you're finished with it for good, destroy it:

* call MyTerrainTile.destroy()

*

* There is also a function called TerrainTile.coord2TileCoords, which

* takes any single coordinate x, and returns its closest multiple of 128,

* the size of a tile. I figured I'd make it public because some people

* might want to use it.

***************************************************************************/

//! zinc

library TerrainTile

{

private keyword next;

private keyword prev;

private struct TerrainList

{

TerrainTile first = 0;

method addLink (TerrainTile link)

{

link.next = first;

first.prev = link;

first = link;

}

method removeLink (TerrainTile link)

{

link.next.prev = link.prev;

link.prev.next = link.next;

if (link == first) first = link.next;

link.next = 0;

link.prev = 0;

}

}

public struct TerrainTile

{

// shouldn't be changed, unless blizz changes it in a patch,

// which is highly unlikely

static constant real TILE_SIZE = 128.;

// the hashtable to store the tiles in

private static hashtable hash = InitHashtable();

// internal variable used to separate default tiles,

// the ones which are there at the start of the map,

// from those which are changed with this system

private boolean isDefault = false;

// internal variables basically there to save processing

// power

private integer ix = 0, iy = 0;

// the coordinates of the tile -- access them with "x"/"y",

// not "xx"/"xy" -- don't worry if these are

// different to the ones you put into the create function

// as they are rounded to the nearest multiple of 128

// can't be altered directly, as that could screw up

// the whole system

private real xx = 0., xy = 0.;

// the rawcode of the terrain type of the tile -- you can

// alter this directly but you won't see anything unless

// you call apply() again

integer terrainType = 0;

// the variation of the tile -- again, you can alter this

// but it won't be visible until apply() is called

// note: a variation of -1 means random, and this means

// it might change in appearance when another tile is

// created over it and then removed, or even if you call

// apply() again.

integer variation = 0;

// you can store data on the TerrainTile if you want to...

integer data = 0;

// linked list data, used to store which tiles are at the

// same place as this one

TerrainTile prev = 0;

TerrainTile next = 0;

private TerrainList list = 0;

// this method changes the position of the tile

method setCoords (real newX, real newY)

{

// if the tile has been applied already, flag it for

// reappliance

boolean reapply = list != 0;

// if it needs to be reapplied, temporarily remove the

// tile

if (reapply) remove();

// convert the new coordinates into usable ones, store them

xx = coord2TileCoord(newX);

xy = coord2TileCoord(newY);

// also store them as integers as multiples of 128, for

// optimization reasons

ix = R2I(xx / TILE_SIZE);

iy = R2I(xy / TILE_SIZE);

// if it needs to be reapplied, apply it again

if (reapply) apply();

}

// these operators return the values of the readonly variables x and y

method operator x () -> real {return xx;}

method operator y () -> real {return xy;}

// this method adds the tile to the top of the list and displays it

method apply ()

{

TerrainTile t;

// I need to comment functions like these to keep my sanity...

// if the tile is not already linked, link it

if (list == 0)

{

// load the list for this xx/xy position from the hashtable

list = TerrainTile(LoadInteger(hash, ix, iy));

// if the list doesn't exist...

if (list == 0)

{

// create one...

list = TerrainList.create();

// ...and save it in the hashtable

SaveInteger(hash, ix, iy, integer(list));

// also, this means the tile has not been changed, so we need

// to store the tile that was originally there...

// allocate the new tile struct...

t = TerrainTile.allocate();

// store the coordinates

t.ix = ix; t.iy = iy;

t.xx = xx; t.xy = xy;

// store the terrain type

t.terrainType = GetTerrainType(xx, xy);

// store the terrain variation (why does blizz call it "Variance"?

t.variation = GetTerrainVariance(xx, xy);

// this is the default terrain, make sure the system knows so

t.isDefault = true;

// create a link for the new tile struct in the new list

list.addLink(t);

}

// create the link in the list

list.addLink(this);

}

// else, bring the link to the front of the list

// if it isn't there already

else if (this != list.first)

{

// destroy the link, ready to recreate it...

list.removeLink(this);

// recreate it at the front of the list

list.addLink(this);

}

// finally, change the actual visible terrain!

SetTerrainType(xx, xy, terrainType, variation, 1, 0);

}

// this method removes the tile from the list, and replaces

// it with the next highest if it is at the top

method remove ()

{

TerrainTile t;

// again, this definitely needs commenting...

// if the tile isn't linked, we don't need to remove it --

// it doesn't actually exist

// otherwise, yeah, remove it...

if (list != 0)

{

// first thing's first, destroy the link

list.removeLink(this);

// now if the list contains other data, apply that terrain type

if (list.first != 0)

{

// store the previous tile in a variable so it can be accessed easily

t = list.first;

// apply the previous terrain type

SetTerrainType(xx, xy, t.terrainType, t.variation, 1, 0);

// if the previous tile was the default one, we can safely destroy it

if (t.isDefault)

{

// destroy the link to the default tile

list.removeLink(t);

// null the reference so the destructor doesn't screw up

t.list = 0;

// destroy the default tile

t.destroy();

// now the list should be empty, we can destroy that, too

list.destroy();

// make sure the hashtable knows the list is gone

RemoveSavedInteger(hash, ix, iy);

}

}

// if the list doesn't contain other data, it is empty, destroy it

// this shouldn't happen since there should always be a default tile

// at the bottom, but it's just a precaution

else

{

// destroy the list

list.destroy();

// make sure the hashtable knows the list is gone

RemoveSavedInteger(hash, ix, iy);

}

// now it is safe to null the reference to the list

list = 0;

}

}

// this could be a useful function I guess, so I'll leave it

// public...

// basically, it rounds xx to the nearest multiple of 128

static method coord2TileCoord (real x) -> real

{

// get the modulus of the coordinate divided by 128

real mod = x - I2R(R2I(x / TILE_SIZE)) * TILE_SIZE;

// make sure the modulus is in the range 0...128

if (mod < 0.) mod += TILE_SIZE;

// round it down to start with

x -= mod;

// and if it was at the upper end of the multiple

// of 128, bring it back up by 128

if (mod > TILE_SIZE / 2.) x += TILE_SIZE;

// return the rounded value

return x;

}

static method create (real x, real y, integer terrainType, integer variation) -> TerrainTile

{

// allocate a new TerrainTile instance

TerrainTile t = TerrainTile.allocate();

// store useable coordinates (multiples of 128)

// for real-space operations

t.xx = coord2TileCoord(x);

t.xy = coord2TileCoord(y);

// store shortened integer versions of the

// coordinates for hashtable operations

t.ix = R2I(t.xx / TILE_SIZE);

t.iy = R2I(t.xy / TILE_SIZE);

// store the terrain type id and variation

// for later access

t.terrainType = terrainType;

t.variation = variation;

// return the new struct

return t;

}

method onDestroy ()

{

// make sure the TerrainTile is completely erased

remove();

// to let users check if the TerrainTile still

// exists and is associated with their data

data = 0;

}

}

}

//! endzinc

[/code]

[jass=TerrainSwap]/***************************************************************************

* TerrainSwap by Element of Water

****************************************************************************

* Requirements:

* TerrainTile by Element of Water

* LinkedList by Ammorth

****************************************************************************

* What is it?

* A TerrainSwap object is simply a container for several TerrainTiles.

* It provides ways of creating several at once, arranged it circles,

* squares, or contained within a certain rect, and it can also apply

* and remove them all at once.

****************************************************************************

* How do I use it?

* The principle is basically the same as TerrainTile -- you create it,

* do things to it, apply it, then remove or destroy it.

* 1) Create your TerrainTile:

* set MyTerrainSwap = TerrainSwap.create(integer terrainType, integer variation)

* integer terrainType -- the terrain tile's type id, this can be found

* by converting an Environment - Change Terrain

* Type GUI action into JASS and copying the value

* in 'AAAA' that you see there

* integer variation -- this is just the same as the "variation" when

* placing tiles in the terrain editor, except

* you can have a variation of -1, which means

* "random variation"

* 4) Add some tiles to your TerrainSwap:

* This adds a single tile using the type and variation of the whole

* TerrainSwap, at the specified x/y coordinates.

* call MyTerrainSwap.addTile(real x, real y)

* This adds a circle just like SetTerrainType with shape 0, at

* coordinates x/y with size area.

* call MyTerrainSwap.addCircle(real x, real y, integer area)

* This adds a square just like SetTerrainType with shape 1, at

* coordinates x/y with size area.

* call MyTerrainSwap.addSquare(real x, real y, integer area)

* This adds a circle or a square just like SetTerrainType, at

* coordinates x/y with size area. TERRAIN_SHAPE_CIRCLE is for a circle

* and TERRAIN_SHAPE_SQUARE is for a square.

* call MyTerrainSwap.addShape(real x, real y, integer area, integer shape)

* This one takes a rect and fills it with tiles:

* call MyTerrainSwap.addRect(rect r)

* 3) Apply (show) your TerrainSwap:

* call MyTerrainSwap.apply()

* 4) When you're done with it, but you'll use it again, remove it:

* call MyTerrainSwap.remove()

* 5) Or if you're finished with it for good, destroy it:

* call MyTerrainSwap.destroy()

***************************************************************************/

//! zinc

library TerrainSwap requires TerrainTile, LinkedList

{

public struct TerrainSwap

{

// these are for use in addShape() -- I don't think they

// need any explaining

static constant integer SHAPE_CIRCLE = 0;

static constant integer SHAPE_SQUARE = 1;

// the list used to store all the TerrainTiles

private List list = 0;

// stored values of terrain type rawcode and variation --

// I might make these public and add the necessary

// functionality one day, but not now

private integer terrainType = 0, variation = 0;

// a rather simple method that adds a tile to the

// TerrainSwap, ready to be swapped

method addTile (real x, real y)

{

// create a link in the list containing data from a

// newly created TerrainTile

Link.create(list, integer(TerrainTile.create(x, y, terrainType, variation)));

}

// a more complicated method to add several tiles in

// the shape of a circle to the TerrainSwap, with

// the integer area being the diameter of the circle in

// number of tiles, just like the SetTerrainType

// native with the shape parameter set to 0

private real xp; // these variables are used

private real yp; // to pass values between addCircle

private real rad; // and addCircle_child (see below)

private real radsq;

private real xx, yy;

method addCircle (real x, real y, integer area)

{

// calculate the distance the method needs to

// check to draw the tiles

real dist = (area - 1) * TerrainTile.TILE_SIZE;

// the starting x coordinate is simply the origin

// minus the distance

xp = - dist;

// the radius is simply the distance to the starting

// x/y positions, but it needs to have a small value

// added to make sure the checks encompass all points

rad = dist + 1.;

// square the radius for efficiency in distance checking

radsq = rad * rad;

// make sure the child method has access to the x

// and y origins of the circle

xx = TerrainTile.coord2TileCoord(x);

yy = TerrainTile.coord2TileCoord(y);

while (xp < rad)

{

// tells the child method where to start its

// y loop from

yp = - dist;

// the y loop needs to be run in a different

// thread to avoid hitting the op limit

addCircle_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addCircle runs the y loop in a

// separate thread to avoid hitting the op limit

private method addCircle_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < rad)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addCircle_child.execute();

// and break the current loop

break;

}

// use Pythagoras' theorem to determine whether

// the point is within the circle

else if (xp * xp + yp * yp <= radsq)

// if the point is within the circle, add

// a tile there

addTile(xx + xp, yy + yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

// adds several tiles in the shape of a square to the

// TerrainSwap -- works exactly like SetTerrainType

// with shape 1

method addSquare (real x, real y, integer area)

{

// calculate the distance the method needs to

// check to draw the tiles

real dist = (area - 1) * TerrainTile.TILE_SIZE;

// I'm using the same variables as for the circle

// method, even if they are used for different

// things -- in this case, the radius is simply

// half the width of the square (plus the small

// value)

rad = dist + 1.;

// the starting x coordinate is simply the origin

// minus the distance

xp = - dist;

// make sure the child method has access to the x

// and y origins of the square

xx = TerrainTile.coord2TileCoord(x);

yy = TerrainTile.coord2TileCoord(y);

while (xp < rad)

{

// tells the child method where to start its

// y loop from

yp = - dist;

// the y loop needs to be run in a different

// thread to avoid hitting the op limit

addSquare_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addSquare runs the y loop in a

// separate thread to avoid hitting the op limit

private method addSquare_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < rad)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addSquare_child.execute();

// and break the current loop

break;

}

// no need for checks if the point is in the

// circle now, it's a SQUARE!

else addTile(xx + xp, yy + yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

// for those who like the format of SetTerrainType, this

// adds a circle or a square to the TerrainSwap

// the parameters mean exactly the same as their equivelent

// parameters in SetTerrainType.

method addShape (real x, real y, integer area, integer shape)

{

// if the shape is a circle, add a circle

if (shape == SHAPE_CIRCLE) addCircle(x, y, area);

// or if the shape is a square, add a square

else if (shape == SHAPE_SQUARE) addSquare(x, y, area);

// unrecognised shape -- give an error message

else debug BJDebugMsg("TerrainSwap.addShape: ERROR -- invalid shape specified (" + I2S(shape) + ").");

}

// a method to swap the terrain of all tiles in a rect

private real minx, maxx;

private real miny, maxy;

method addRect (rect r)

{

// store the rect as a set of coordinates

// temporarily

minx = TerrainTile.coord2TileCoord(GetRectMinX(r));

miny = TerrainTile.coord2TileCoord(GetRectMinY(r));

maxx = TerrainTile.coord2TileCoord(GetRectMaxX(r));

maxy = TerrainTile.coord2TileCoord(GetRectMaxY(r));

// store where to start off across the x plane

xp = minx;

// loop through x as long as we're within the rect

while (xp < maxx)

{

// tell the child method where to start the y

// from

yp = miny;

// run the y loop in a separate thread to avoid

// hitting the op limit

addRect_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addRect runs the y loop in a

// separate thread to avoid hitting the op limit

private method addRect_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < maxy)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addRect_child.execute();

// and break the current loop

break;

}

// add the tile

else addTile(xp, yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

method apply ()

{

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// apply the TerrainTile associated with the

// current link

TerrainTile(l.data).apply();

// move on to the next link

l = l.next;

}

}

method remove ()

{

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// remove the TerrainTile associated with the

// current link

TerrainTile(l.data).remove();

// move on to the next link

l = l.next;

}

}

static method create (integer terrainType, integer variation) -> TerrainSwap

{

TerrainSwap t = TerrainSwap.allocate();

t.list = List.create();

t.terrainType = terrainType;

t.variation = variation;

return t;

}

method onDestroy ()

{

// iterate through all the TerrainTiles and destroy them...

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// remove the TerrainTile associated with the

// current link

TerrainTile(l.data).destroy();

// move on to the next link

l = l.next;

}

list.destroy();

}

}

}

//! endzinc[/code]

I decided to make these scripts because I was making a spell that changed the terrain temporarily, and I ran into all kinds of problems. How to store the old terrain that was there was the problem: I fixed that by making a simple array of values holding the terrain type and variation of the original. It worked great until I cast another spell overlapping the first -- by the time both spells had expired, the terrain stayed changed where they overlapped. I tried to build something into the spell, but it was futile -- it was too complicated to incorporate into a spell script. I needed something else. That's why I wrote these libraries.

So what do they do?

TerrainTile can change the terrain at any given point, and no matter how many are created over each other, when they are all removed, the original tile will still be there.

TerrainSwap is a holder for multiple TerrainTiles. It allows you to create multiple TerrainTiles over any area, by a selection of several methods, and apply or remove them all at once.

Here is the code:

Requires LinkedList by Ammorth

[jass=TerrainTile]/***************************************************************************

* TerrainTile by Element of Water

****************************************************************************

* What is it?

* TerrainTile provides a way to swap any single terrain tile on the map

* with a different one, just like SetTerrainType. The difference is, with

* this system, when the TerrainTile is destroyed, the tile that was

* originally there, that the TerrainTile overwrote, will reappear. You

* can also stack multiple TerrainTiles on top of one another, and

* destroying the top one will reveal the next, etc, in the same order

* they were applied in. Don't worry about destroying tiles that weren't

* on top, either -- they will be removed from the list.

****************************************************************************

* How do I use it?

* Using TerrainTile couldn't be easier -- there are just a few functions

* to call to control it.

* 1) Create your TerrainTile:

* set MyTerrainTile = TerrainTile.create(real x, real y, integer terrainType, integer variation)

* real x/y -- the coordinates of your TerrainTile

* integer terrainType -- the terrain tile's type id, this can be found

* by converting an Environment - Change Terrain

* Type GUI action into JASS and copying the value

* in 'AAAA' that you see there

* integer variation -- this is just the same as the "variation" when

* placing tiles in the terrain editor, except

* you can have a variation of -1, which means

* "random variation"

* 2) Apply (show) your TerrainTile:

* call MyTerrainTile.apply()

* 3) Change its properties:

* set MyTerrainTile.terrainType = NewTerrainType

* set MyTerrainTile.variation = NewVariation

* 4) Reapply it, or change its location:

* call MyTerrainTile.setCoords(newX, newY)

* 5) When you're done with it, but you'll use it again, remove it:

* call MyTerrainTile.remove()

* 6) Or if you're finished with it for good, destroy it:

* call MyTerrainTile.destroy()

*

* There is also a function called TerrainTile.coord2TileCoords, which

* takes any single coordinate x, and returns its closest multiple of 128,

* the size of a tile. I figured I'd make it public because some people

* might want to use it.

***************************************************************************/

//! zinc

library TerrainTile

{

private keyword next;

private keyword prev;

private struct TerrainList

{

TerrainTile first = 0;

method addLink (TerrainTile link)

{

link.next = first;

first.prev = link;

first = link;

}

method removeLink (TerrainTile link)

{

link.next.prev = link.prev;

link.prev.next = link.next;

if (link == first) first = link.next;

link.next = 0;

link.prev = 0;

}

}

public struct TerrainTile

{

// shouldn't be changed, unless blizz changes it in a patch,

// which is highly unlikely

static constant real TILE_SIZE = 128.;

// the hashtable to store the tiles in

private static hashtable hash = InitHashtable();

// internal variable used to separate default tiles,

// the ones which are there at the start of the map,

// from those which are changed with this system

private boolean isDefault = false;

// internal variables basically there to save processing

// power

private integer ix = 0, iy = 0;

// the coordinates of the tile -- access them with "x"/"y",

// not "xx"/"xy" -- don't worry if these are

// different to the ones you put into the create function

// as they are rounded to the nearest multiple of 128

// can't be altered directly, as that could screw up

// the whole system

private real xx = 0., xy = 0.;

// the rawcode of the terrain type of the tile -- you can

// alter this directly but you won't see anything unless

// you call apply() again

integer terrainType = 0;

// the variation of the tile -- again, you can alter this

// but it won't be visible until apply() is called

// note: a variation of -1 means random, and this means

// it might change in appearance when another tile is

// created over it and then removed, or even if you call

// apply() again.

integer variation = 0;

// you can store data on the TerrainTile if you want to...

integer data = 0;

// linked list data, used to store which tiles are at the

// same place as this one

TerrainTile prev = 0;

TerrainTile next = 0;

private TerrainList list = 0;

// this method changes the position of the tile

method setCoords (real newX, real newY)

{

// if the tile has been applied already, flag it for

// reappliance

boolean reapply = list != 0;

// if it needs to be reapplied, temporarily remove the

// tile

if (reapply) remove();

// convert the new coordinates into usable ones, store them

xx = coord2TileCoord(newX);

xy = coord2TileCoord(newY);

// also store them as integers as multiples of 128, for

// optimization reasons

ix = R2I(xx / TILE_SIZE);

iy = R2I(xy / TILE_SIZE);

// if it needs to be reapplied, apply it again

if (reapply) apply();

}

// these operators return the values of the readonly variables x and y

method operator x () -> real {return xx;}

method operator y () -> real {return xy;}

// this method adds the tile to the top of the list and displays it

method apply ()

{

TerrainTile t;

// I need to comment functions like these to keep my sanity...

// if the tile is not already linked, link it

if (list == 0)

{

// load the list for this xx/xy position from the hashtable

list = TerrainTile(LoadInteger(hash, ix, iy));

// if the list doesn't exist...

if (list == 0)

{

// create one...

list = TerrainList.create();

// ...and save it in the hashtable

SaveInteger(hash, ix, iy, integer(list));

// also, this means the tile has not been changed, so we need

// to store the tile that was originally there...

// allocate the new tile struct...

t = TerrainTile.allocate();

// store the coordinates

t.ix = ix; t.iy = iy;

t.xx = xx; t.xy = xy;

// store the terrain type

t.terrainType = GetTerrainType(xx, xy);

// store the terrain variation (why does blizz call it "Variance"?

t.variation = GetTerrainVariance(xx, xy);

// this is the default terrain, make sure the system knows so

t.isDefault = true;

// create a link for the new tile struct in the new list

list.addLink(t);

}

// create the link in the list

list.addLink(this);

}

// else, bring the link to the front of the list

// if it isn't there already

else if (this != list.first)

{

// destroy the link, ready to recreate it...

list.removeLink(this);

// recreate it at the front of the list

list.addLink(this);

}

// finally, change the actual visible terrain!

SetTerrainType(xx, xy, terrainType, variation, 1, 0);

}

// this method removes the tile from the list, and replaces

// it with the next highest if it is at the top

method remove ()

{

TerrainTile t;

// again, this definitely needs commenting...

// if the tile isn't linked, we don't need to remove it --

// it doesn't actually exist

// otherwise, yeah, remove it...

if (list != 0)

{

// first thing's first, destroy the link

list.removeLink(this);

// now if the list contains other data, apply that terrain type

if (list.first != 0)

{

// store the previous tile in a variable so it can be accessed easily

t = list.first;

// apply the previous terrain type

SetTerrainType(xx, xy, t.terrainType, t.variation, 1, 0);

// if the previous tile was the default one, we can safely destroy it

if (t.isDefault)

{

// destroy the link to the default tile

list.removeLink(t);

// null the reference so the destructor doesn't screw up

t.list = 0;

// destroy the default tile

t.destroy();

// now the list should be empty, we can destroy that, too

list.destroy();

// make sure the hashtable knows the list is gone

RemoveSavedInteger(hash, ix, iy);

}

}

// if the list doesn't contain other data, it is empty, destroy it

// this shouldn't happen since there should always be a default tile

// at the bottom, but it's just a precaution

else

{

// destroy the list

list.destroy();

// make sure the hashtable knows the list is gone

RemoveSavedInteger(hash, ix, iy);

}

// now it is safe to null the reference to the list

list = 0;

}

}

// this could be a useful function I guess, so I'll leave it

// public...

// basically, it rounds xx to the nearest multiple of 128

static method coord2TileCoord (real x) -> real

{

// get the modulus of the coordinate divided by 128

real mod = x - I2R(R2I(x / TILE_SIZE)) * TILE_SIZE;

// make sure the modulus is in the range 0...128

if (mod < 0.) mod += TILE_SIZE;

// round it down to start with

x -= mod;

// and if it was at the upper end of the multiple

// of 128, bring it back up by 128

if (mod > TILE_SIZE / 2.) x += TILE_SIZE;

// return the rounded value

return x;

}

static method create (real x, real y, integer terrainType, integer variation) -> TerrainTile

{

// allocate a new TerrainTile instance

TerrainTile t = TerrainTile.allocate();

// store useable coordinates (multiples of 128)

// for real-space operations

t.xx = coord2TileCoord(x);

t.xy = coord2TileCoord(y);

// store shortened integer versions of the

// coordinates for hashtable operations

t.ix = R2I(t.xx / TILE_SIZE);

t.iy = R2I(t.xy / TILE_SIZE);

// store the terrain type id and variation

// for later access

t.terrainType = terrainType;

t.variation = variation;

// return the new struct

return t;

}

method onDestroy ()

{

// make sure the TerrainTile is completely erased

remove();

// to let users check if the TerrainTile still

// exists and is associated with their data

data = 0;

}

}

}

//! endzinc

[/code]

[jass=TerrainSwap]/***************************************************************************

* TerrainSwap by Element of Water

****************************************************************************

* Requirements:

* TerrainTile by Element of Water

* LinkedList by Ammorth

****************************************************************************

* What is it?

* A TerrainSwap object is simply a container for several TerrainTiles.

* It provides ways of creating several at once, arranged it circles,

* squares, or contained within a certain rect, and it can also apply

* and remove them all at once.

****************************************************************************

* How do I use it?

* The principle is basically the same as TerrainTile -- you create it,

* do things to it, apply it, then remove or destroy it.

* 1) Create your TerrainTile:

* set MyTerrainSwap = TerrainSwap.create(integer terrainType, integer variation)

* integer terrainType -- the terrain tile's type id, this can be found

* by converting an Environment - Change Terrain

* Type GUI action into JASS and copying the value

* in 'AAAA' that you see there

* integer variation -- this is just the same as the "variation" when

* placing tiles in the terrain editor, except

* you can have a variation of -1, which means

* "random variation"

* 4) Add some tiles to your TerrainSwap:

* This adds a single tile using the type and variation of the whole

* TerrainSwap, at the specified x/y coordinates.

* call MyTerrainSwap.addTile(real x, real y)

* This adds a circle just like SetTerrainType with shape 0, at

* coordinates x/y with size area.

* call MyTerrainSwap.addCircle(real x, real y, integer area)

* This adds a square just like SetTerrainType with shape 1, at

* coordinates x/y with size area.

* call MyTerrainSwap.addSquare(real x, real y, integer area)

* This adds a circle or a square just like SetTerrainType, at

* coordinates x/y with size area. TERRAIN_SHAPE_CIRCLE is for a circle

* and TERRAIN_SHAPE_SQUARE is for a square.

* call MyTerrainSwap.addShape(real x, real y, integer area, integer shape)

* This one takes a rect and fills it with tiles:

* call MyTerrainSwap.addRect(rect r)

* 3) Apply (show) your TerrainSwap:

* call MyTerrainSwap.apply()

* 4) When you're done with it, but you'll use it again, remove it:

* call MyTerrainSwap.remove()

* 5) Or if you're finished with it for good, destroy it:

* call MyTerrainSwap.destroy()

***************************************************************************/

//! zinc

library TerrainSwap requires TerrainTile, LinkedList

{

public struct TerrainSwap

{

// these are for use in addShape() -- I don't think they

// need any explaining

static constant integer SHAPE_CIRCLE = 0;

static constant integer SHAPE_SQUARE = 1;

// the list used to store all the TerrainTiles

private List list = 0;

// stored values of terrain type rawcode and variation --

// I might make these public and add the necessary

// functionality one day, but not now

private integer terrainType = 0, variation = 0;

// a rather simple method that adds a tile to the

// TerrainSwap, ready to be swapped

method addTile (real x, real y)

{

// create a link in the list containing data from a

// newly created TerrainTile

Link.create(list, integer(TerrainTile.create(x, y, terrainType, variation)));

}

// a more complicated method to add several tiles in

// the shape of a circle to the TerrainSwap, with

// the integer area being the diameter of the circle in

// number of tiles, just like the SetTerrainType

// native with the shape parameter set to 0

private real xp; // these variables are used

private real yp; // to pass values between addCircle

private real rad; // and addCircle_child (see below)

private real radsq;

private real xx, yy;

method addCircle (real x, real y, integer area)

{

// calculate the distance the method needs to

// check to draw the tiles

real dist = (area - 1) * TerrainTile.TILE_SIZE;

// the starting x coordinate is simply the origin

// minus the distance

xp = - dist;

// the radius is simply the distance to the starting

// x/y positions, but it needs to have a small value

// added to make sure the checks encompass all points

rad = dist + 1.;

// square the radius for efficiency in distance checking

radsq = rad * rad;

// make sure the child method has access to the x

// and y origins of the circle

xx = TerrainTile.coord2TileCoord(x);

yy = TerrainTile.coord2TileCoord(y);

while (xp < rad)

{

// tells the child method where to start its

// y loop from

yp = - dist;

// the y loop needs to be run in a different

// thread to avoid hitting the op limit

addCircle_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addCircle runs the y loop in a

// separate thread to avoid hitting the op limit

private method addCircle_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < rad)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addCircle_child.execute();

// and break the current loop

break;

}

// use Pythagoras' theorem to determine whether

// the point is within the circle

else if (xp * xp + yp * yp <= radsq)

// if the point is within the circle, add

// a tile there

addTile(xx + xp, yy + yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

// adds several tiles in the shape of a square to the

// TerrainSwap -- works exactly like SetTerrainType

// with shape 1

method addSquare (real x, real y, integer area)

{

// calculate the distance the method needs to

// check to draw the tiles

real dist = (area - 1) * TerrainTile.TILE_SIZE;

// I'm using the same variables as for the circle

// method, even if they are used for different

// things -- in this case, the radius is simply

// half the width of the square (plus the small

// value)

rad = dist + 1.;

// the starting x coordinate is simply the origin

// minus the distance

xp = - dist;

// make sure the child method has access to the x

// and y origins of the square

xx = TerrainTile.coord2TileCoord(x);

yy = TerrainTile.coord2TileCoord(y);

while (xp < rad)

{

// tells the child method where to start its

// y loop from

yp = - dist;

// the y loop needs to be run in a different

// thread to avoid hitting the op limit

addSquare_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addSquare runs the y loop in a

// separate thread to avoid hitting the op limit

private method addSquare_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < rad)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addSquare_child.execute();

// and break the current loop

break;

}

// no need for checks if the point is in the

// circle now, it's a SQUARE!

else addTile(xx + xp, yy + yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

// for those who like the format of SetTerrainType, this

// adds a circle or a square to the TerrainSwap

// the parameters mean exactly the same as their equivelent

// parameters in SetTerrainType.

method addShape (real x, real y, integer area, integer shape)

{

// if the shape is a circle, add a circle

if (shape == SHAPE_CIRCLE) addCircle(x, y, area);

// or if the shape is a square, add a square

else if (shape == SHAPE_SQUARE) addSquare(x, y, area);

// unrecognised shape -- give an error message

else debug BJDebugMsg("TerrainSwap.addShape: ERROR -- invalid shape specified (" + I2S(shape) + ").");

}

// a method to swap the terrain of all tiles in a rect

private real minx, maxx;

private real miny, maxy;

method addRect (rect r)

{

// store the rect as a set of coordinates

// temporarily

minx = TerrainTile.coord2TileCoord(GetRectMinX(r));

miny = TerrainTile.coord2TileCoord(GetRectMinY(r));

maxx = TerrainTile.coord2TileCoord(GetRectMaxX(r));

maxy = TerrainTile.coord2TileCoord(GetRectMaxY(r));

// store where to start off across the x plane

xp = minx;

// loop through x as long as we're within the rect

while (xp < maxx)

{

// tell the child method where to start the y

// from

yp = miny;

// run the y loop in a separate thread to avoid

// hitting the op limit

addRect_child.execute();

// increment the x value

xp += TerrainTile.TILE_SIZE;

}

}

// child method of addRect runs the y loop in a

// separate thread to avoid hitting the op limit

private method addRect_child ()

{

// we need to make sure the method doesn't hit

// the op limit, and that is what this variable is

// for

integer i = 5;

// loop through y as long as we're within the radius

while (yp < maxy)

{

// if i reaches 0..

if (i == 0)

{

// restart the thread

addRect_child.execute();

// and break the current loop

break;

}

// add the tile

else addTile(xp, yp);

// decrement the op checker

i -= 1;

// increment the y value

yp += TerrainTile.TILE_SIZE;

}

}

method apply ()

{

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// apply the TerrainTile associated with the

// current link

TerrainTile(l.data).apply();

// move on to the next link

l = l.next;

}

}

method remove ()

{

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// remove the TerrainTile associated with the

// current link

TerrainTile(l.data).remove();

// move on to the next link

l = l.next;

}

}

static method create (integer terrainType, integer variation) -> TerrainSwap

{

TerrainSwap t = TerrainSwap.allocate();

t.list = List.create();

t.terrainType = terrainType;

t.variation = variation;

return t;

}

method onDestroy ()

{

// iterate through all the TerrainTiles and destroy them...

// get the first link in the list ready to operate on

Link l = list.first;

// loop through the entire list

while (l != 0)

{

// remove the TerrainTile associated with the

// current link

TerrainTile(l.data).destroy();

// move on to the next link

l = l.next;

}

list.destroy();

}

}

}

//! endzinc[/code]

Last edited: