btmux-0.6-rc4/doc/
btmux-0.6-rc4/event/
btmux-0.6-rc4/game/
btmux-0.6-rc4/game/maps/
btmux-0.6-rc4/game/mechs/
btmux-0.6-rc4/game/text/help/
btmux-0.6-rc4/game/text/help/cat_faction/
btmux-0.6-rc4/game/text/help/cat_inform/
btmux-0.6-rc4/game/text/help/cat_misc/
btmux-0.6-rc4/game/text/help/cat_mux/
btmux-0.6-rc4/game/text/help/cat_mux/cat_commands/
btmux-0.6-rc4/game/text/help/cat_mux/cat_functions/
btmux-0.6-rc4/game/text/help/cat_templates/
btmux-0.6-rc4/game/text/wizhelp/
btmux-0.6-rc4/include/
btmux-0.6-rc4/misc/
btmux-0.6-rc4/python/
btmux-0.6-rc4/src/hcode/btech/
btmux-0.6-rc4/tree/
/*
 * Author: Markus Stenberg <fingon@iki.fi>
 *
 *  Copyright (c) 1996 Markus Stenberg
 *  Copyright (c) 1998-2002 Thomas Wouters
 *  Copyright (c) 2000-2002 Cord Awtry
 *       All rights reserved
 *
 */

/* 
   Artillery code for
   - standard rounds (damage to target hex, damage/2 to neighbor hexes)
   - smoke rounds (to be implemented)
   - fascam rounds (to be implemented) 
 */

#include <math.h>

#include "mech.h"
#include "artillery.h"
#include "mech.events.h"
#include "create.h"
#include "p.artillery.h"
#include "p.mech.fire.h"
#include "p.mech.combat.h"
#include "p.mech.combat.misc.h"
#include "p.mech.damage.h"
#include "p.mech.utils.h"
#include "p.map.obj.h"
#include "p.mech.hitloc.h"
#include "p.mine.h"
#include "spath.h"

struct artillery_shot_type *free_shot_list = NULL;
static void artillery_hit(artillery_shot * s);

static const char *artillery_type(artillery_shot * s)
{
	if(s->type == CL_ARROW || s->type == IS_ARROW)
		return "a missile";
	return "a round";
}

static struct {
	int dir;
	char *desc;
} arty_dirs[] = {
	{
	0, "north"}, {
	60, "northeast"}, {
	90, "east"}, {
	120, "southeast"}, {
	180, "south"}, {
	240, "southwest"}, {
	270, "west"}, {
	300, "northwest"}, {
	0, NULL}
};

static const char *artillery_direction(artillery_shot * s)
{
	float fx, fy, tx, ty;
	int b, d, i, best = -1, bestd = 0;

	MapCoordToRealCoord(s->from_x, s->from_y, &fx, &fy);
	MapCoordToRealCoord(s->to_x, s->to_y, &tx, &ty);
	b = FindBearing(fx, fy, tx, ty);
	for(i = 0; arty_dirs[i].desc; i++) {
		d = abs(b - arty_dirs[i].dir);
		if(best < 0 || d < bestd) {
			best = i;
			bestd = d;
		}
	}
	if(best < 0)
		return "Invalid";
	return arty_dirs[best].desc;
}

int artillery_round_flight_time(float fx, float fy, float tx, float ty)
{
	int delay = MAX(ARTILLERY_MINIMUM_FLIGHT,
					(FindHexRange(fx, fy, tx, ty) / ARTY_SPEED));

	/* XXX Different weapons, diff. speed? */
	return delay;
}

static void artillery_hit_event(MUXEVENT * e)
{
	artillery_shot *s = (artillery_shot *) e->data;

	artillery_hit(s);
	ADD_TO_LIST_HEAD(free_shot_list, next, s);
}

void artillery_shoot(MECH * mech, int targx, int targy, int windex,
					 int wmode, int ishit)
{
	struct artillery_shot_type *s;
	float fx, fy, tx, ty;

	if(free_shot_list) {
		s = free_shot_list;
		free_shot_list = free_shot_list->next;
	} else
		Create(s, artillery_shot, 1);
	s->from_x = MechX(mech);
	s->from_y = MechY(mech);
	s->to_x = targx;
	s->to_y = targy;
	s->type = windex;
	s->mode = wmode;
	s->ishit = ishit;
	s->shooter = mech->mynum;
	s->map = mech->mapindex;
	MechLOSBroadcast(mech, tprintf("shoots %s towards %s!",
								   artillery_type(s),
								   artillery_direction(s)));
	MapCoordToRealCoord(s->from_x, s->from_y, &fx, &fy);
	MapCoordToRealCoord(s->to_x, s->to_y, &tx, &ty);
	muxevent_add(artillery_round_flight_time(fx, fy, tx, ty), 0, EVENT_DHIT,
				 artillery_hit_event, (void *) s, NULL);
}

static int blast_arcf(float fx, float fy, MECH * mech)
{
	int b, dir;

	b = FindBearing(MechFX(mech), MechFY(mech), fx, fy);
	dir = AcceptableDegree(b - MechFacing(mech));
	if(dir > 120 && dir < 240)
		return BACK;
	if(dir > 300 || dir < 60)
		return FRONT;
	if(dir > 180)
		return LEFTSIDE;
	return RIGHTSIDE;
}

#define TABLE_GEN   0
#define TABLE_PUNCH 1
#define TABLE_KICK  2

void blast_hit_hexf(MAP * map, int dam, int singlehitsize, int heatdam,
					float fx, float fy, float tfx, float tfy, char *tomsg,
					char *otmsg, int table, int safeup, int safedown,
					int isunderwater)
{
	MECH *tempMech;
	int loop;
	int isrear = 0, iscritical = 0, hitloc;
	int damleft, arc, ndam;
	int ground_zero;
	short tx, ty;

	/* Not on a map so just return */
	if(!map)
		return;

	RealCoordToMapCoord(&tx, &ty, fx, fy);
	if(tx < 0 || ty < 0 || tx >= map->map_width || ty >= map->map_height)
		return;
	if(!tomsg || !otmsg)
		return;
	if(isunderwater)
		ground_zero = Elevation(map, tx, ty);
	else
		ground_zero = MAX(0, Elevation(map, tx, ty));

	for(loop = 0; loop < map->first_free; loop++)
		if(map->mechsOnMap[loop] >= 0) {
			tempMech = (MECH *)
				FindObjectsData(map->mechsOnMap[loop]);
			if(!tempMech)
				continue;
			if(MechX(tempMech) != tx || MechY(tempMech) != ty)
				continue;
			/* Far too high.. */
			if(MechZ(tempMech) >= (safeup + ground_zero))
				continue;
			/* Far too below (underwater, mostly) */
			if(					/* MechTerrain(tempMech) == WATER &&  */
				  MechZ(tempMech) <= (ground_zero - safedown))
				continue;
			MechLOSBroadcast(tempMech, otmsg);
			mech_notify(tempMech, MECHALL, tomsg);
			arc = blast_arcf(tfx, tfy, tempMech);

			if(arc == BACK)
				isrear = 1;
			damleft = dam;

			while (damleft > 0) {
				if(singlehitsize <= damleft)
					ndam = singlehitsize;
				else
					ndam = damleft;

				damleft -= ndam;

				switch (table) {
				case TABLE_PUNCH:
					FindPunchLoc(tempMech, hitloc, arc, iscritical, isrear);
					break;
				case TABLE_KICK:
					FindKickLoc(tempMech, hitloc, arc, iscritical, isrear);
					break;
				default:
					hitloc =
						FindHitLocation(tempMech, arc, &iscritical, &isrear);
				}

				DamageMech(tempMech, tempMech, 0, -1, hitloc, isrear,
						   iscritical, ndam, 0, -1, 0, -1, 0, 0);
			}
			heat_effect(NULL, tempMech, heatdam, 0);
		}
}

void blast_hit_hex(MAP * map, int dam, int singlehitsize, int heatdam,
				   int fx, int fy, int tx, int ty, char *tomsg, char *otmsg,
				   int table, int safeup, int safedown, int isunderwater)
{
	float ftx, fty;
	float ffx, ffy;

	MapCoordToRealCoord(tx, ty, &ftx, &fty);
	MapCoordToRealCoord(fx, fy, &ffx, &ffy);
	blast_hit_hexf(map, dam, singlehitsize, heatdam, ffx, ffy, ftx, fty,
				   tomsg, otmsg, table, safeup, safedown, isunderwater);
}

void blast_hit_hexesf(MAP * map, int dam, int singlehitsize, int heatdam,
					  float fx, float fy, float ftx, float fty, char *tomsg,
					  char *otmsg, char *tomsg1, char *otmsg1, int table,
					  int safeup, int safedown, int isunderwater,
					  int doneighbors)
{
	int x1, y1, x2, y2;
	int dm;
	short tx, ty;
	float hx, hy;
	float t = FindXYRange(fx, fy, ftx, fty);

	dm = MAX(1, (int) t + 1);
	blast_hit_hexf(map, dam / dm, singlehitsize, heatdam / dm, fx, fy, ftx,
				   fty, tomsg, otmsg, table, safeup, safedown, isunderwater);
	if(!doneighbors)
		return;
	RealCoordToMapCoord(&tx, &ty, fx, fy);
	for(x1 = (tx - doneighbors); x1 <= (tx + doneighbors); x1++)
		for(y1 = (ty - doneighbors); y1 <= (ty + doneighbors); y1++) {
			int spot;

			if((dm = MyHexDist(tx, ty, x1, y1, 0)) > doneighbors)
				continue;
			if((tx == x1) && (ty == y1))
				continue;
			x2 = BOUNDED(0, x1, map->map_width - 1);
			y2 = BOUNDED(0, y1, map->map_height - 1);
			if(x1 != x2 || y1 != y2)
				continue;
			spot = (x1 == tx && y1 == ty);
			MapCoordToRealCoord(x1, y1, &hx, &hy);
			dm++;
			if(!(dam / dm))
				continue;
			blast_hit_hexf(map, dam / dm, singlehitsize, heatdam / dm, hx,
						   hy, ftx, fty, spot ? tomsg : tomsg1,
						   spot ? otmsg : otmsg1, table, safeup, safedown,
						   isunderwater);

			/*
			 * Added in burning woods when a mech's engine goes nova
			 *
			 * -Kipsta
			 * 8/4/99
			 */

			switch (GetRTerrain(map, x1, y1)) {
			case LIGHT_FOREST:
			case HEAVY_FOREST:
				if(!find_decorations(map, x1, y1)) {
					add_decoration(map, x1, y1, TYPE_FIRE, FIRE,
								   FIRE_DURATION);
				}

				break;
			}
		}
}

void blast_hit_hexes(MAP * map, int dam, int singlehitsize, int heatdam,
					 int tx, int ty, char *tomsg, char *otmsg, char *tomsg1,
					 char *otmsg1, int table, int safeup, int safedown,
					 int isunderwater, int doneighbors)
{
	float fx, fy;

	MapCoordToRealCoord(tx, ty, &fx, &fy);
	blast_hit_hexesf(map, dam, singlehitsize, heatdam, fx, fy, fx, fy,
					 tomsg, otmsg, tomsg1, otmsg1, table, safeup, safedown,
					 isunderwater, doneighbors);
}

static void artillery_hit_hex(MAP * map, artillery_shot * s, int type,
							  int mode, int dam, int tx, int ty, int isdirect)
{
	char buf[LBUF_SIZE];
	char buf1[LBUF_SIZE];
	char buf2[LBUF_SIZE];

	/* Safety check -- shouldn't happen */
	if(tx < 0 || tx >= map->map_width || ty < 0 || ty >= map->map_height)
		return;

	if((mode & SMOKE_MODE)) {
		/* Add smoke */
		add_decoration(map, tx, ty, TYPE_SMOKE, SMOKE, SMOKE_DURATION);
		return;
	}
	if(mode & MINE_MODE) {
		add_mine(map, tx, ty, dam);
		return;
	}
	if(!(mode & CLUSTER_MODE)) {
		if(isdirect)
			sprintf(buf1, "receives a direct hit!");
		else
			sprintf(buf1, "is hit by fragments!");
		if(isdirect)
			sprintf(buf2, "You receive a direct hit!");
		else
			sprintf(buf2, "You are hit by fragments!");
	} else {
		if(dam > 2)
			strcpy(buf, "bomblets");
		else
			strcpy(buf, "a bomblet");
		sprintf(buf1, "is hit by %s!", buf);
		sprintf(buf2, "You are hit by %s!", buf);
	}
	blast_hit_hex(map, dam, (mode & CLUSTER_MODE) ? 2 : 5, 0, tx, ty, tx,
				  ty, buf2, buf1,
				  (mode & CLUSTER_MODE) ? TABLE_PUNCH : TABLE_GEN, 10, 4, 0);
}

static artillery_shot *hit_neighbors_s;
static int hit_neighbors_type;
static int hit_neighbors_mode;
static int hit_neighbors_dam;

static void artillery_hit_neighbors_callback(MAP * map, int x, int y)
{
	artillery_hit_hex(map, hit_neighbors_s, hit_neighbors_type,
					  hit_neighbors_mode, hit_neighbors_dam, x, y, 0);
}

static void artillery_hit_neighbors(MAP * map, artillery_shot * s,
									int type, int mode, int dam, int tx,
									int ty)
{
	hit_neighbors_s = s;
	hit_neighbors_type = type;
	hit_neighbors_mode = mode;
	hit_neighbors_dam = dam;
	visit_neighbor_hexes(map, tx, ty, artillery_hit_neighbors_callback);
}

static void artillery_cluster_hit(MAP * map, artillery_shot * s, int type,
								  int mode, int dam, int tx, int ty)
{
	/* Main idea: Pick <dam/2> bombs of 2pts each, and scatter 'em
	   over 5x5 area with weighted numbers */
	int xd, yd, x, y;
	int i;

	int targets[5][5];
	int d;

	bzero(targets, sizeof(targets));
	for(i = 0; i < dam; i++) {
		do {
			xd = Number(-2, 0) + Number(0, 2);
			yd = Number(-2, 0) + Number(0, 2);
			x = tx + xd;
			y = ty + yd;
		}
		while (x < 0 || x >= map->map_width || y < 0 || y >= map->map_height);
		/* Whee.. it's time to drop a bomb to the hex */
		targets[xd + 2][yd + 2]++;
	}
	for(xd = 0; xd < 5; xd++)
		for(yd = 0; yd < 5; yd++)
			if((d = targets[xd][yd]))
				artillery_hit_hex(map, s, type, mode, d * 2, xd + tx - 2,
								  yd + ty - 2, 1);
}

void artillery_FriendlyAdjustment(dbref mechnum, MAP * map, int x, int y)
{
	MECH *mech;
	MECH *spotter;
	MECH *tempMech = NULL;

	if(!(mech = getMech(mechnum)))
		return;
	/* Ok.. we've a valid guy */
	spotter = getMech(MechSpotter(mech));
	if(!((MechTargX(mech) == x && MechTargY(mech) == y)
		 || (spotter && (MechTargX(spotter) == x &&
						 MechTargY(spotter) == y))))
		return;
	/* Ok.. we've a valid target to adjust fire on */
	/* Now, see if we've any friendlies in LOS.. NOTE: FRIENDLIES ;-) */
	if(spotter) {
		if(MechSeesHex(spotter, map, x, y))
			tempMech = spotter;
	} else
		tempMech = find_mech_in_hex(mech, map, x, y, 2);
	if(!tempMech)
		return;
	if(!Started(tempMech) || !Started(mech))
		return;
	if(spotter) {
		mech_printf(mech, MECHSTARTED,
					"%s sent you some trajectory-correction data.",
					GetMechToMechID(mech, tempMech));
		mech_printf(tempMech, MECHSTARTED,
					"You provide %s with information about the miss.",
					GetMechToMechID(tempMech, mech));
	}
	MechFireAdjustment(mech)++;
}

static void artillery_hit(artillery_shot * s)
{
	/* First, we figure where it exactly hits. Our first-hand information
	   is only whether it hits or not, not _where_ it hits */
	double dir;
	int di;
	int dist;
	int weight;
	MAP *map = getMap(s->map);
	int original_x = 0, original_y = 0;
	int dam = MechWeapons[s->type].damage;

	if(!map)
		return;
	if(!s->ishit) {
		/* Shit! We missed target ;-) */
		/* Time to calculate a new target hex */
		di = Number(0, 359);
		dir = di * TWOPIOVER360;
		dist = Number(2, 7);
		weight = 100 * (dist * 6) / ((dist * 6 + map->windspeed));
		di = (di * weight + map->winddir * (100 - weight)) / 100;
		dist = (dist * weight + (map->windspeed / 6) * (100 - weight)) / 100;
		original_x = s->to_x;
		original_y = s->to_y;
		s->to_x = s->to_x + dist * cos(dir);
		s->to_y = s->to_y + dist * sin(dir);
		s->to_x = BOUNDED(0, s->to_x, map->map_width - 1);
		s->to_y = BOUNDED(0, s->to_y, map->map_height - 1);
		/* Time to calculate if any friendlies have LOS to hex,
		   and if so, adjust fire adjustment unless you lack information /
		   have changed target */
	}
	/* It's time to run for your lives, lil' ones ;-) */
	if(!(s->mode & ARTILLERY_MODES))
		HexLOSBroadcast(map, s->to_x, s->to_y, tprintf("%s fire hits $H!",
													   &MechWeapons[s->type].
													   name[3]));
	else if(s->mode & CLUSTER_MODE)
		HexLOSBroadcast(map, s->to_x, s->to_y,
						"A rain of small bomblets hits $H's surroundings!");
	else if(s->mode & MINE_MODE)
		HexLOSBroadcast(map, s->to_x, s->to_y,
						"A rain of small bomblets hits $H!");
	else if(s->mode & SMOKE_MODE)
		HexLOSBroadcast(map, s->to_x, s->to_y,
						tprintf
						("A %s %s hits $h, and smoke starts to billow!",
						 &MechWeapons[s->type].name[3],
						 &(artillery_type(s)[2])));

	/* Basic theory:
	   - smoke / ordinary rounds are spread with the ordinary functions 
	   - mines are otherwise ordinary except no hitting of neighbor hexes
	   - cluster bombs are special 
	 */
	if(!(s->mode & CLUSTER_MODE)) {
		/* Enjoy ourselves in all neighbor hexes, too */
		artillery_hit_hex(map, s, s->type, s->mode, dam, s->to_x, s->to_y, 1);
		if(!(s->mode & MINE_MODE))
			artillery_hit_neighbors(map, s, s->type, s->mode, dam / 2,
									s->to_x, s->to_y);
	} else
		artillery_cluster_hit(map, s, s->type, s->mode, dam, s->to_x,
							  s->to_y);
	if(!s->ishit)
		artillery_FriendlyAdjustment(s->shooter, map, original_x, original_y);
}