          -------------------------------------------
          King of the Hill Weapon Writer's HOWTO v1.2
          -------------------------------------------

Greetings!  The purpose of of this document is to explain how to go
about adding new weapons to KOTH.  It is assumed you are a competent C
programmer and are somewhat familiar with the GGI API, but are not
otherwise terribly familiar with the KOTH source code.  This document
is primarily composed of several examples of how several weapons were
written with explanation and justification, in a very "literate
programming" style.  As of this writing KOTH is at version 0.7.2, and
hopefully the weapons system outlined here will be stable for a while.
Now, without further ado, on to the show!


Example #1:  The MIRV
---------------------
MIRV:  Multiple Independently-Armed Reentry Vehicle

This is a pretty simple weapon.  Fire one shot, and at the apex of its 
arc it breaks up into five seperate shells, allowing you strike
multiple targets with ease.  However, if it strikes ground before
arcing, it only explodes with the power of one shell.

First, a little introduction to the weapons system.  Here is the
weapon data structure:

typedef struct Weapon_wep
{
    char* name;
    
    Shellstat_bal (*initguidance)(struct Projectilepos_bal *prjpos, void** guideshotinfo,
								  Shellstat_bal (*initexplosion)(struct Projectilepos_bal *prjpos, 
																 void** explosioninfo), 
								  void** explosioninfo);
    Shellstat_bal(*doguidance)(void* info, struct Projectilepos_bal *prjpos, 
							   Shellstat_bal (*initexplosion)(struct Projectilepos_bal *prjpos, 
															  void** explosioninfo), 
							   void** explosioninfo);
    void (*drawshot)(struct Projectilepos_bal *prjpos, void* info); /* handles drawing/erasing of the shell */
    Shellstat_bal (*initexplosion)(struct Projectilepos_bal *prjpos, 
								   void** explosioninfo);
    Shellstat_bal (*doexplosion)(void* info);
    void (*drawexplosion)(void* info); /* handles drawing/erasing of the explosion */

    struct Weapon_wep *next;
    struct Weapon_wep *prev;

	int cost;
	int count;
	int id;
} Weapon_wep;

So, the basic properties of a weapon are:

name 
  Its name

initguidance
  The initializer function; this is called when the weapon is shot

doguidance
  As the weapon is in flight, this function is called to update its
  position and other information

drawshot
  Draws the shell in flight

initexplosion
  The initializer function; called when the weapons starts to explode

doexplosion
  As the explosion occurs, this updates its effects

drawexplosion
  Draws the explosive effect as it occurs

next, prev
  For managing the linked list of all weapons.  You probably won't
  need to mess with this.

cost
  What it costs players to buy one of this weapon (if 0, they cannot buy it
  at all)

count
  The number of this weapons the player must buy, so the actual
  market price is cost x count
  (it is debatable whether this is a good or bad thing, 
  eventually it will probably be that people will be forced to buy
  weapons in packs, but they will be able to sell back individual
  weapons and recover the cost of the weaponry they didn't want)

id
  Each weapon has a identifier number, this number is used by
  the wepLookupWeaponByID() function

This is the static weapon structure.  In addition, when a weapon is
actually used it enters the ballistics code and the following
two structures becomes important:

typedef struct Projectilepos_bal
{
    int id; /* player who fired this shot */
    int wid; /* weapon which fired this shot */
    double rox, roy; /* real ox and real oy (not modified by balEnvironmentAdjustProjPos when the wall type is wraparound) */
    double ox, oy;
    double x, y;
    double vx, vy;
} Projectilepos_bal;

struct Projectilelist_bal
{
    Shellstat_bal stat;
    int gen;

    Projectilepos_bal prjpos;
	
    void* guidanceinfo;
    void* explosioninfo;
    struct Weapon_wep *wpn;

    struct Projectilelist_bal *next;
    struct Projectilelist_bal *prev;
};

I'll talk about these structures in more detail a little later.

Now, let's go through the life (in code) of a MIRV.  First the player
buys it.  The cost, as specified in the weapon structure, is deducted
from their money and the appropriate number (the count field) is added
to his or her stock of weapons.  This is already handled, you don't
have to worry about any of that.  Then the game starts.  The player
goes decides to try out this spiffy new MIRV they have, and fires it.
The following will happen:

1) The weapon is activated and begins its flight (initguidance) 
2) It flys through the air a bit.  doguidance is periodically called
to update its position
3) It reaches its apex.  We can tell by when the vertical velocity
(stored in the projectilepos structure) flips from positive to
negative.
4) Create five shells starting at the same position with slightly
different horizontal velocities as the MIRV, and have the original
shell explode.  The "multiple" effect has now occured :)
5) Each new shell continues to travel through the air on its
downward course and will explode when it finally hits something.

Pretty straightforward.  So, to implement this weapon it is up to us
to provide the code to initialize, to guide, to initialize the
explosion, and to do the explosion.  Each is pointer to a function in
the weapon structure, so we can easily mix and match functions to
produce new weapons and re-use other code.  We can re-use enough code,
in fact, that all we'll have to write for the MIRV is a new guidance function!

Time to get down and dirty with code.  Weapons are added with the
following function:

void wepAddWeapon(char *name, int cost, int count,
		  Shellstat_bal(*initguidance) (struct Projectilepos_bal *
						prjpos,
						void **guideshotinfo,
						Shellstat_bal
						(*initexplosion) (struct
								  Projectilepos_bal
								  * prjpos,
								  void
								  **explosioninfo),
						void **explosioninfo),
		  Shellstat_bal(*doguidance) (void *info,
					      struct Projectilepos_bal *
					      prjpos,
					      Shellstat_bal(*initexplosion)
					      (struct Projectilepos_bal *
					       prjpos,
					       void **explosioninfo),
					      void **explosioninfo),
		  Shellstat_bal(*initexplosion) (struct Projectilepos_bal *
						 prjpos,
						 void **explosioninfo),
		  Shellstat_bal(*doexplosion) (void *info));

Don't worry, it's a lot simpler than it looks.  Here's the actual
function call we'll use to actually register our new MIRV:


    wepAddWeapon("MIRV", 300, 1,
		 wepBasicInit,
		 wepMIRVGuidance,
		 wepBasicExplosionInit,
		 wepSimpleExplosion);

This is the shell that MIRV release (It's like a Basic Shell):
		 
    wepAddWeapon("MIRV Shell", 0, 1,
		 wepBasicInit,
		 wepBasicGuidance,
		 wepBasicExplosionInit,
		 wepSimpleExplosion);


So the function we need to implement is wepMIRVGuidance.  Let's take a 
look at wepBasicGuidance in basic.c:


Shellstat_bal wepBasicGuidance(void* info, struct Projectilepos_bal *prjpos, 
							   Shellstat_bal (*initexplosion)(struct Projectilepos_bal *prjpos, 
															  void** explosioninfo), 
							   void **explosioninfo)
{
    Shellstat_bal res;
    Player_pl *plhit = NULL;
    int ix, iy;

/* (1) */	
    prjpos->rox = prjpos->ox = prjpos->x;
    prjpos->roy = prjpos->oy = prjpos->y;          
/* (2) */
    prjpos->x += prjpos->vx;
    prjpos->y += prjpos->vy;
/* (3) */
    prjpos->vy -= bal_grav/bal_lerp_tweak;	
/* (4) */
    if((res = balEnvironmentAdjustProjPos(prjpos)) != FLYING
/* (5) */
	   || balCheckIntersect(prjpos->ox, prjpos->oy, prjpos->x, prjpos->y, &plhit, &ix, &iy))
    {
		switch(res) 
		{
		case HOLDING:
			break;
		case FLYING:
			prjpos->x=ix;
			prjpos->y=iy;
			/* fall through */
		case EXPLODING:
			return initexplosion(prjpos, explosioninfo);
			break;
		case FREEING:
			return FREEING;
			break;
		}
    }
/* (6) */
    return FLYING;
}

Here's what's going on:

1) Save the old projectile position values
You can see that we have two old positions vars (ro[xy] and o[xy]);
ox and oy is used for calculations, these vars has sometimes an false old
position value when the wall type is 'wraparound'.
So, drawing functions uses rox and roy for erasing the projectiles,
because these vars always has the true old position value.

2) Update the projectile position based on current horizontal/vertical 
velocity
3) Update the vertical velocity to account for acceleration due to
gravity 
4) Apply environmental effects (wind, walls)
5) Check to see if the shot has hit anything (either dirt, a wall, or
a tank) if so then adjust position to the exact point it hit and
initialize explosion
6) otherwise, just keep flying through the air


So, now we want to write modify wepBasicGuidance to get
wepMIRVGuidance.  As you recall, we want to check and see if we have
reached the apex of our arc and split up when we have.  We create the
following new function in mirv.c:

Shellstat_bal
wepMIRVGuidance(void *info, struct Projectilepos_bal *prjpos,
		Shellstat_bal(*initexplosion) (struct Projectilepos_bal *
					       prjpos,
					       void **explosioninfo),
		void **explosioninfo)
{
    Weapon_wep *bs;
    struct Projectilelist_bal *prj;
    Shellstat_bal res;
    int ix, iy;
    Player_pl *plhit = NULL;

    prjpos->rox = prjpos->ox = prjpos->x;
    prjpos->roy = prjpos->oy = prjpos->y;
    prjpos->x += prjpos->vx;
    prjpos->y += prjpos->vy;
    prjpos->vy -= bal_grav / bal_lerp_tweak;

    if((res = balEnvironmentAdjustProjPos(prjpos)) != FLYING
       || balCheckIntersect(prjpos->ox, prjpos->oy, prjpos->x, prjpos->y,
			    &plhit, &ix, &iy))
    {
	if(plLookupPlayer(prjpos->id))
	{
	    logPrintf(DEBUG, "Intersection at (%i, %i) of %s's shot\n", ix,
		      iy, plLookupPlayer(prjpos->id)->name);
	}
	else
	{
	    logPrintf(DEBUG, "Intersection at (%i, %i) of null's shot\n", ix,
		      iy);
	}
	if(plhit != NULL)
	    logPrintf(DEBUG, "and hit %s\n", plhit->name);
	switch (res)
	{
	    case HOLDING:
		/* wtf ??? */
		break;
	    case FLYING:
		prjpos->x = ix;
		prjpos->y = iy;
		/* fall through */
	    case EXPLODING:
		return initexplosion(prjpos, explosioninfo);
		break;
	    case FREEING:
		return FREEING;
		break;
	}
    }

    if(prjpos->vy > 0 && prjpos->vy - (bal_grav / bal_lerp_tweak) < 0)
    {
	bs = wepLookupWeapon("MIRV Shell");
	prj =
	    balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y, prjpos->vx,
			 prjpos->vy, bs);
	prj->stat = INITSHOT(prj);

	prj =
	    balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y, prjpos->vx + 5,
			 prjpos->vy, bs);
	prj->stat = INITSHOT(prj);

	prj =
	    balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y,
			 prjpos->vx + 10, prjpos->vy, bs);
	prj->stat = INITSHOT(prj);

	prj =
	    balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y, prjpos->vx - 5,
			 prjpos->vy, bs);
	prj->stat = INITSHOT(prj);

	prj =
	    balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y,
			 prjpos->vx - 10, prjpos->vy, bs);
	prj->stat = INITSHOT(prj);

	return initexplosion(prjpos, explosioninfo);
    }
    return FLYING;
}

It's pretty straightforward.  We check to see if we've reached the
apex, then create the new shots.  You're probably wondering about all
that "prj->stat" stuff, though.  Stat is the projectile's status -
either holding, flying, exploding, or freeing.  Because projectiles
are initialized holding (this is because of the asynchrosity of the
network protocol, they have to be activated seperately) we hold onto
their status until actual activation.  However, in this case we want
to activate them immediatly, so we use the macro INITSHOT() which
calls that shot's initguidance function and returns the new status.

That's all the code we have to write to implement the MIRV!  However,
there is a little bit of housekeeping to do to add the weapon to the
right lists so it can actually be used in the game.

We need to put wepAddWeapon in the right place in weapon.c:

void wepInit()
{
/* ...
   other weapon definitions 
   ...
*/
    wepAddWeapon("MIRV", 300, 1,
		 wepBasicInit,
		 wepMIRVGuidance,
		 wepBasicExplosionInit,
		 wepSimpleExplosion);
		 
    wepAddWeapon("MIRV Shell", 0, 1,
		 wepBasicInit,
		 wepBasicGuidance,
		 wepBasicExplosionInit,
		 wepSimpleExplosion);
}

MIRV Shell is like a Basic Shell, so we don't need write
extra-code to support it.

Secondly, we need to set the weapon-drawing functions for the
MIRV and MIRV Shell in weapongfx.c.  We'll be using functions
already included for our drawing; creating new weapon graphics
will be covered in a later example.

void wgxInit()
{
/* ...
   other weapon graphics definitions
   ...
*/
    wgxSetWeaponDrawFunc("MIRV", wgxDrawSimpleShot, wgxDrawSimpleRedExplosion);
    wgxSetWeaponDrawFunc("MIRV Shell", wgxDrawSimpleShot,
			 wgxDrawSimpleOrangeExplosion);
}

That's it!  Now we have our MIRV weapon!



Example #2:  The dirt ball
--------------------------
Unlike our previous example where we used pre-packaged explosions, for
this one we'll use the standard guidance functions and write our own
doexplosion and drawexplosion routines.  The weapon will be a nice big
clod of dirt you can use to bury your enemies.  Whether to buy you some
time to recover from getting pounded, force them to take damage
blasting their way out, or to just piss them off, it's guaranteed to
be a useful part of your strategic arsenal.

Here's the function call we'll use to add the weapon in:

wepAddWeapon("Dirt Ball", 300, 1,
			 wepBasicInit,
			 wepBasicGuidance,
			 wepDirtExplosionInit,
			 wepDirtExplosion);

wepBasicInit and wepBasicGuidance provide basic ballistics for us,
we're interested in what happens when it hits the ground.

First a bit about how terrain is represented.  On first thought, the
obvious ways to represent terrain is with either a hightmap or a
bitmap.  There are problems with each, however.  First, heightmaps
cannot have terrain suspended in air.  They're really easy to code for
of course, but their limitations are quickly encountered.  In this
case, then, representing the terrain as a bitmap would seem like an
obvious solution.  However, KOTH usually represents terrain internally
at a much higher resolution than the screen.  The terrain map is by
default 2000x1500, meaning 2000*1500=3000000 possible dirt points.  If
each point is an int, that's nearly twelve megabytes!  If each point
is a char we bring it down to about three megabytes and if we do some
fancy bit packing we can get it down to about 366k.  However, once the
bit-packing sets in, the code to constantly pack and unpack the
terrain structure gets to be very tricky, and things such as falling
dirt turn out to be _always_ difficult and inefficient with some sort
of terrain bitmap.

So, KOTH uses something called "spans".  Basically they are an
extension of hightmaps into the second dimension.  Each vertical
column is a linked list of areas of dirt "spans" (contiguous dirt
sections) where there is dirt; each node of the list stores a y
coordinate and the height.  This turns out to make transmitting the
terrain structure fairly easy and calculating falling dirt REALLY
easy.  There are a few fairly tricky algorithms working behind the
scenes to manipulate the span data structure, fortunatly they've
already been written and now abstracted behind an easy to use API: we
can check arbitrary positions with terCheckPos(), clear out spans with
terDelSpan() and add new spans with terAddSpan().  The last one is the
one we're interested in.

Our dirt clod will be circular and be about same size (actually a bit
bigger) as a tac nuke - 225 terrain units (TU's).  What we want is a
function that will create a filled circle of terrain of some arbitrary
radius, so we can get that "expanding" effect that explosion have.
Fortunatly, there is a function terClearCircle() that does just this,
only in reverse: it is used by explosion functions to vaporize a disc
of dirt.  We just need to copy it and modify it to make our own
version that fills in dirt instead of takes it away.

void terCircleClearSpans(int xo, int yo, int x, int y)
{
    if(xo+x>=0 && xo+x<ter_sizex) 
		terDelSpan(&ter_data[xo+x], yo-y, y*2);
    if(xo+y>=0 && xo+y<ter_sizex) 
		terDelSpan(&ter_data[xo+y], yo-x, x*2);
    if(xo-x>=0 && xo-x<ter_sizex) 
		terDelSpan(&ter_data[xo-x], yo-y, y*2);
    if(xo-y>=0 && xo-y<ter_sizex) 
		terDelSpan(&ter_data[xo-y], yo-x, x*2);
}

void terClearCircle(int xo, int yo, int r)
{
    int x = 0;
    int y = r;
    int d = 1 - r;
    int deltaE = 3;
    int deltaSE = -2 * r + 5;

    terCircleClearSpans(xo, yo, x, y);
    while(y>x) {
		if(d<0) {
			d+=deltaE;
			deltaE+=2;
			deltaSE+=2;
		} else {
			d+=deltaSE;
			deltaE+=2;
			deltaSE+=4;
			y--;
		}
		x++;
		terCircleClearSpans(xo, yo, x, y);
    }
}

And here's the new function. terAddSpan() and terDelSpan() handle all
the messy special cases of possible overlap, so we can just use them
freely to modify the terrain.

void terCircleAddSpans(int xo, int yo, int x, int y)
{
    if(xo+x>=0 && xo+x<ter_sizex) 
		terAddSpan(&ter_data[xo+x], yo-y, y*2);
    if(xo+y>=0 && xo+y<ter_sizex) 
		terAddSpan(&ter_data[xo+y], yo-x, x*2);
    if(xo-x>=0 && xo-x<ter_sizex) 
		terAddSpan(&ter_data[xo-x], yo-y, y*2);
    if(xo-y>=0 && xo-y<ter_sizex) 
		terAddSpan(&ter_data[xo-y], yo-x, x*2);
}

void terAddCircle(int xo, int yo, int r)
{
    int x = 0;
    int y = r;
    int d = 1 - r;
    int deltaE = 3;
    int deltaSE = -2 * r + 5;

    terCircleAddSpans(xo, yo, x, y);
    while(y>x) {
		if(d<0) {
			d+=deltaE;
			deltaE+=2;
			deltaSE+=2;
		} else {
			d+=deltaSE;
			deltaE+=2;
			deltaSE+=4;
			y--;
		}
		x++;
		terCircleAddSpans(xo, yo, x, y);
    }
}


So now we can add our dirt clod to the terrain!  Now we need to write
our initialization function for the Dirt Clod.  Since it looks a lot
like a tac nuke in reverse, take a look at the tac nuke explosion init:

Shellstat_bal
wepTacNukeExplosionInit(struct Projectilepos_bal *prjpos, void **explosioninfo)
{
    struct SimpleExplosion_wep *e =
	(struct SimpleExplosion_wep *)
	malloc(sizeof(struct SimpleExplosion_wep));

    e->x = prjpos->x;
    e->y = prjpos->y;
    e->r = 0;
    e->dx = 9;
    e->id = prjpos->id;
    e->max_radius = 250;
    e->wid = prjpos->wid;

    *((struct SimpleExplosion_wep **) explosioninfo) = e;
    return EXPLODING;
}

Now with a bit of slight of hand, it becomes a dirt ball init:

Shellstat_bal
wepDirtExplosionInit(struct Projectilepos_bal *prjpos, void **explosioninfo)
{
    struct SimpleExplosion_wep *e =
	(struct SimpleExplosion_wep *)
	malloc(sizeof(struct SimpleExplosion_wep));

    e->x = prjpos->x;
    e->y = prjpos->y;
    e->r = 0;
    e->dx = 8;
    e->id = prjpos->id;
    e->max_radius = 225;
    e->wid = prjpos->wid;

    *((struct SimpleExplosion_wep **) explosioninfo) = e;
    return EXPLODING;
}

Some explanation is in order.  You may have noticed a couple void
pointers called guidanceinfo and explosioninfo hanging about in the
Projectilelist_bal structure.  These contain "weapon specific" data.
This could be extra state info for weapons with really weird guidance,
and in the case of explosion all information is stored in this
structure.  SimpleExplosion_wep describes the position, maximum
radius, current radius, source player and expansion rate (dx,
technically should be dr, so sue me) for a simple circular explosion.
We can use this for dirt explosions, as well.

Now for doing the actual explosion itself. Let's take the standard
simple explosion function:

Shellstat_bal wepSimpleExplosion(void *info)
{
    struct SimpleExplosion_wep *prj = (struct SimpleExplosion_wep *) info;
    Player_pl *pcur;

    prj->r += prj->dx;
    if(prj->r > prj->max_radius)
    {
	return FREEING;
    }
    else			/* amazing how much the word "else" looks like the face of Yoda at 4:16 in the morning */
    {
	terClearCircle(prj->x, prj->y, prj->r);
	for(pcur = pl_begin; pcur; pcur = pcur->next)
	{
	    if(pcur->ready == READY
	       && plPlayerInCircleArea(pcur, prj->x, prj->y, prj->r))
	    {
		pcur->last_hit = prj->id;
		plDamageTank(pcur, EXPLOSIVE,
			     prj->id, prj->wid, 1);
	    }
	}
	return EXPLODING;
    }
}
See the call to terClearCircle()?  Now, change it to use our new
function, and take out the player-damage code, and we get this:

Shellstat_bal wepDirtExplosion(void *info)
{
    struct SimpleExplosion_wep *prj = (struct SimpleExplosion_wep *) info;

    prj->r += prj->dx;
    if(prj->r > prj->max_radius)
    {
	return FREEING;
    }
    else			/* amazing how much the word "else" looks like the face of Yoda at 4:16 in the morning */
    {
	terAddCircle(prj->x, prj->y, prj->r);
	return EXPLODING;
    }
}

Easy!

Just one more step left.  We need to animate this expanding
dirtball. It's easier than it sounds.

Here's the drawing function for SimpleExplosion:

void wgxDrawSimpleExplosion(void *info)
{
    int i;
    ggi_color c;
    int tx = ((struct SimpleExplosion_wep *) info)->x;
    int ty = ((struct SimpleExplosion_wep *) info)->y;
    int tr = ((struct SimpleExplosion_wep *) info)->r;
    int sx = gfxTerrainToScreenXCoord(tx);
    int sy = gfxTerrainToScreenYCoord(ty);
    int sa = (tr * gfx_xsize) / ter_sizex;
    int sb = (tr * gfx_ysize) / ter_sizey;

    if(sa >= sb)
    {
	for(i = sa; i > 0; i--)
	{
	    c.r = ((sa - i) * (255.0 / sa)) * 255.0;
	    c.g = 0x24 << 8;
	    c.b = 0x24 << 8;
	    c.a = 0xFF << 8;
	    ggiSetGCForeground(gfx_vis, ggiMapColor(gfx_vis, &c));
	    gfxDrawThickEllipse(sx, sy, i, (i * sb) / sa);
	}
    }
    else
    {
	for(i = sb; i > 0; i--)
	{
	    c.r = ((sb - i) * (255.0 / sb)) * 255.0;
	    c.g = 0x24 << 8;
	    c.b = 0x24 << 8;
	    c.a = 0xFF << 8;
	    ggiSetGCForeground(gfx_vis, ggiMapColor(gfx_vis, &c));
	    gfxDrawThickEllipse(sx, sy, (i * sa) / sb, sb);
	}
    }

    if(tr + ((struct SimpleExplosion_wep *) info)->dx >=
       ((struct SimpleExplosion_wep *) info)->max_radius)
    {
	gfxDrawArea(tx - tr - 2 * ((struct SimpleExplosion_wep *) info)->dx,
		    ty - tr - 2 * ((struct SimpleExplosion_wep *) info)->dx,
		    tr * 2 + 4 * ((struct SimpleExplosion_wep *) info)->dx,
		    tr * 2 + 4 * ((struct SimpleExplosion_wep *) info)->dx);
    }
}

Now, here's what we need to do to make it draw our dirt:

void wgxDrawDirtExplosion(void *info)
{
    int tx = ((struct SimpleExplosion_wep *) info)->x;
    int ty = ((struct SimpleExplosion_wep *) info)->y;
    int tr = ((struct SimpleExplosion_wep *) info)->r;

    gfxDrawArea(tx - tr - 2 * ((struct SimpleExplosion_wep *) info)->dx,
		ty - tr - 2 * ((struct SimpleExplosion_wep *) info)->dx,
		tr * 2 + 4 * ((struct SimpleExplosion_wep *) info)->dx,
		tr * 2 + 4 * ((struct SimpleExplosion_wep *) info)->dx);
}

Yep!  That's it.  The gfxDrawArea function takes care of all the sky,
terrain and tank drawing for us.

So our dirt clod is DONE!  All we have to do is add it in like before:

void wepInit()
{
/* ...
   other weapon definitions 
   ...
*/
	wepAddWeapon("Dirt Ball", 300, 1,
				 wepBasicInit,
				 wepBasicGuidance,
				 wepDirtExplosionInit,
				 wepDirtExplosion);
}

void wgxInit()
{
/* ...
   other weapon graphics definitions
   ...
*/
    wgxSetWeaponDrawFunc("Dirt Ball", wgxDrawSimpleShot, wgxDrawDirtExplosion);
}



Example #3:  The Roller
-----------------------
Ahhh, the roller.  The lazy man's weapon, that is, as long as your
opponent is in a vally :)  When the roller hits ground it does not
explode, but instead rolls along until it hits a tank or an incline
too high to scale.  Then, and only then, does it unleash its might.

The roller is an example of a completly twisted guidance system.
Rolling along the ground requires completly different logic than
flying through the air.

The roller will consist of two parts.  One part flys through the air,
the other actually does the rolling.  Here's how we'll add them:

wepAddWeapon("Roller", 550, 1,
			 wepBasicInit,
			 wepBasicGuidance,
			 wepRollerExplosionInit,
			 NULL);
wepAddWeapon("Roller Ball", 0, 1,
			 wepRollerInit,
			 wepRollerGuidance,
			 wepTacNukeExplosionInit,
			 wepSimpleExplosion);

The basic plan of attack is this: the player shoots a roller and it
flys through the air like a normal shell
(wepBasicInit/wepBasicGuidance).  When it hits the ground,
wepRollerExplosionInit is called, the original shell will fizzle
(which is why it has a NULL doexplosion function) and create the new
shell, with our special guidance function.  When it hits something it
doesn't like, it explodes with the force of a tac nuke.

So, the first thing to do is to write wepRollerExplosionInit().  We've 
seen what a normal explosion initializer looks like
(wepTacNukeExplosionInit) and we've seen how to create new shells
(wepMIRVGuidance) so let's put them together:

Shellstat_bal
wepRollerExplosionInit(struct Projectilepos_bal * prjpos, void **explosioninfo)
{
    struct Projectilelist_bal *prj;
    Weapon_wep *wp;

    wp = wepLookupWeapon("Roller Ball");
    prj =
	balNewShotXY(prjpos->id, 0, prjpos->x, prjpos->y, prjpos->vx,
		     prjpos->vy, wp);
    prj->stat = INITSHOT(prj);

    return FREEING;
}

Now we get to the interesting functions: actually rolling over the
terrain.  We've already looked at the normal guidance function, but it
won't help us much here.  We'll decide our exact plan of attack and
then implement it.

So, how exactly should rolling over terrain work?  How about this:
1) the direction the roller rolls is determined by the sign of
prjpos->vx.  positive for right, negative for left
2) When the roller first hits the ground, check the immediate
surroundings.  If it slopes left, the roller goes left.  If it slopes
right, the roller goes right.  If there isn't any slope, then the
roller continues in the direction it was traveling in the air.
3) With each guidance call, advance the roller some distance.  How to
determine that distance is harder that it sounds.  Do we want to get
the physics of rolling downhill roughly correct, or just do it the
easy way?  Well, I think we'll do it the easy way.  Each frame we'll
advance the roller a total of 20 TU's either horizontally and
vertically.  If the roller is not flush with the ground, it falls.  If
it is, it rolls forward.  If it runs into low enough incline it
climbs.
4) the roller will climb up to 1 TU.  If it encounters anything
higher, it explodes.  If it crashes into a tank, it also explodes.


Now for the implementation let's take a look at the wepBasicInit():

Shellstat_bal
wepBasicInit(struct Projectilepos_bal *prjpos, void **guide,
	     Shellstat_bal(*initexplosion) (struct Projectilepos_bal *
					    prjpos, void **explosioninfo),
	     void **explosioninfo)
{
    Player_pl *plhit = NULL;
    int ix, iy;

    *guide = NULL;

    if(balCheckIntersect
       (prjpos->ox, prjpos->oy, prjpos->x, prjpos->y, &plhit, &ix, &iy))
    {
	prjpos->x = ix;
	prjpos->y = iy;
	aihExplosionHook(prjpos);
	aihExplosionHook(prjpos);
	return initexplosion(prjpos, explosioninfo);
    }
    else
    {
	prjpos->rox = prjpos->ox = prjpos->x;
	prjpos->roy = prjpos->oy = prjpos->y;
	return FLYING;
    }
}

You'll notice that it looks an awful lot like wepBasicGuidance().
prjpos is the projectile position structure used in normal guidance,
so we set prjpos's id to the appropriate source id, then we move the
shell position one quanta and see if it hits anything.  If it does, go 
straight to explosion, if it doesn't, then keep flying.  The roller,
however, needs to do different stuff:

Shellstat_bal
wepRollerInit(struct Projectilepos_bal *prjpos, void **guide,
	      Shellstat_bal(*initexplosion) (struct Projectilepos_bal *
					     prjpos, void **explosioninfo),
	      void **explosioninfo)
{
    Player_pl *plhit = NULL;
    int ix, iy;

    *guide = NULL;

    if(balCheckTankIntersect(prjpos->x, prjpos->y, &plhit, &ix, &iy))
    {
	prjpos->x = ix;
	prjpos->y = iy;
	aihExplosionHook(prjpos);
	return initexplosion(prjpos, explosioninfo);
    }
    else
    {
	if(terCheckPos(ter_data, prjpos->x + 1, prjpos->y + 1) &&
	   !terCheckPos(ter_data, prjpos->x - 1, prjpos->y + 1))
	{
	    prjpos->vx = -1;
	}
	if(terCheckPos(ter_data, prjpos->x - 1, prjpos->y + 1) &&
	   !terCheckPos(ter_data, prjpos->x + 1, prjpos->y + 1))
	{
	    prjpos->vx = 1;
	}
	prjpos->rox = prjpos->ox = prjpos->x;
	prjpos->roy = prjpos->oy = prjpos->y;
	return FLYING;
    }
}

Check to see if we've hit a tank, if not, then try to figure out which 
direction to roll.  Now, on to the rolling function!

Shellstat_bal
wepRollerGuidance(void *info, struct Projectilepos_bal * prjpos,
		  Shellstat_bal(*initexplosion) (struct Projectilepos_bal *
						 prjpos,
						 void **explosioninfo),
		  void **explosioninfo)
{
    int i, ix, iy;
    Player_pl *plhit = NULL;

/* (1) */

    prjpos->rox = prjpos->ox = prjpos->x;
    prjpos->roy = prjpos->oy = prjpos->y;
    
    for(i = 0; i < 20; i++)
    {
/* (2) */

	if(balCheckTankIntersect(prjpos->x, prjpos->y + 5, &plhit, &ix, &iy))
	{
	    prjpos->x = ix;
	    prjpos->y = iy;
	    aihExplosionHook(prjpos);
	    return initexplosion(prjpos, explosioninfo);
	}
/* (3) */

	if(prjpos->x <= 0 || prjpos->x >= ter_sizex)
	{
	    prjpos->x = -1;
	    prjpos->y = -1;
	    return FREEING;
	}
/* (4) */

	if(!terCheckPos(ter_data, prjpos->x, prjpos->y - 1) &&
	   prjpos->vy == 0 && prjpos->y > 0)
	{
	    prjpos->y--;
	    continue;
	}
/* (5) */

	if(prjpos->vx > 0)
	{
/* (6) */

	    if(!terCheckPos(ter_data, prjpos->x + 1, prjpos->y))
	    {
		prjpos->x++;
		prjpos->vy = 0;
		continue;
	    }
	    else
	    {
		if(!terCheckSpan
		   (&ter_data[(int) rint(prjpos->x) + 1], prjpos->y, 2))
		{
		    prjpos->y++;
		    prjpos->vy = 1;
		    continue;
		}
		else
		{
		    aihExplosionHook(prjpos);
		    return initexplosion(prjpos, explosioninfo);
		}
	    }
	}
/* (7) */

	else
	{
	    if(!terCheckPos(ter_data, prjpos->x - 1, prjpos->y))
	    {
		prjpos->x--;
		prjpos->vy = 0;
		continue;
	    }
	    else
	    {
		if(!terCheckSpan
		   (&ter_data[(int) rint(prjpos->x) - 1], prjpos->y, 2))
		{
		    prjpos->y++;
		    prjpos->vy = 1;
		    continue;
		}
		else
		{
		    aihExplosionHook(prjpos);
		    return initexplosion(prjpos, explosioninfo);
		}
	    }
	}
    }
    return FLYING;
}


This function, on first glance, looks rather long and tricky.  In fact 
it is't all that bad (although I admit it took a bit of tweaking to
finally get right.)  Here's a point-by-point description of what it
does:

1) Save old position (for animation purposes)
rox and roy is used for drawings
ox and oy is used for calculations
2) check to see if it has hit anything.  if so, explode
3) check to see if it has gone off the edge;  free if so
4) check to see if it is on solid ground or climbing.  if not, fall
5) decide which direction the roller is rolling
6) if the ground is clear, keep rolling.  climb if necessary, and if
we can't move at all, explode
7) same thing in the other direction.


And that's that.  Once we've added Roller and Roller Ball to the
wepInit() and wgxInit() function, we have our roller weapon!




Conclusion
----------

Well, that's about it for the second draft of the weapon-writer's
guide.  I haven't covered everything, but this should give you a good
idea of where to go from here.  If you still have questions, ask on
the mailing list (see http://savannah.nongnu.org/mail/?group=koth)
and I or someone else will be able to help you.  If you have any suggestions
for the Weaponry-HOWTO, please post it to the list or email me,
constructive criticism is quite welcome.  Have fun!


Allan Douglas
allan_douglas@gmx.net
Mon Feb  3 21:22:55 BRST 2003
