/*	$NetBSD: phaser.c,v 1.15 2009/08/12 08:54:54 dholland Exp $	*/

/*
 * Copyright (c) 1980, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)phaser.c	8.1 (Berkeley) 5/31/93";
#else
__RCSID("$NetBSD: phaser.c,v 1.15 2009/08/12 08:54:54 dholland Exp $");
#endif
#endif /* not lint */

#include <stdio.h>
#include <math.h>
#include "trek.h"
#include "getpar.h"

/* factors for phaser hits; see description below */

#define ALPHA		3.0		/* spread */
#define BETA		3.0		/* franf() */
#define GAMMA		0.30		/* cos(angle) */
#define EPSILON		150.0		/* dist ** 2 */
#define OMEGA		10.596		/* overall scaling factor */

/* OMEGA ~= 100 * (ALPHA + 1) * (BETA + 1) / (EPSILON + 1) */

/*
**  Phaser Control
**
**	There are up to NBANKS phaser banks which may be fired
**	simultaneously.  There are two modes, "manual" and
**	"automatic".  In manual mode, you specify exactly which
**	direction you want each bank to be aimed, the number
**	of units to fire, and the spread angle.  In automatic
**	mode, you give only the total number of units to fire.
**
**	The spread is specified as a number between zero and
**	one, with zero being minimum spread and one being maximum
**	spread.  You  will normally want zero spread, unless your
**	short range scanners are out, in which case you probably
**	don't know exactly where the Klingons are.  In that case,
**	you really don't have any choice except to specify a
**	fairly large spread.
**
**	Phasers spread slightly, even if you specify zero spread.
**
**	Uses trace flag 30
*/

static struct cvntab Matab[] = {
	{ "m",		"anual",	(cmdfun) 1,	0 },
	{ "a",		"utomatic",	(cmdfun) 0,	0 },
	{ NULL,		NULL,		NULL,		0 }
};

struct banks {
	int	units;
	double	angle;
	double	spread;
};



/*ARGSUSED*/
void
phaser(int v __unused)
{
	int		i;
	int		j;
	struct kling	*k;
	double		dx, dy;
	double		anglefactor, distfactor;
	struct banks	*b;
	int		manual, flag, extra = 0;
	int		hit;
	double		tot;
	int		n;
	int		hitreqd[NBANKS];
	struct banks	bank[NBANKS];
	const struct cvntab	*ptr;

	if (Ship.cond == DOCKED) {
		printf("Phasers cannot fire through starbase shields\n");
		return;
	}
	if (damaged(PHASER)) {
		out(PHASER);
		return;
	}
	if (Ship.shldup) {
		printf("Sulu: Captain, we cannot fire through shields.\n");
		return;
	}
	if (Ship.cloaked) {
		printf("Sulu: Captain, surely you must realize that we cannot "
		       "fire\n");
		printf("  phasers with the cloaking device up.\n");
		return;
	}

	/* decide if we want manual or automatic mode */
	manual = 0;
	if (testnl()) {
		if (damaged(COMPUTER)) {
			printf("%s", Device[COMPUTER].name);
			manual++;
		} else if (damaged(SRSCAN)) {
			printf("%s", Device[SRSCAN].name);
			manual++;
		}
		if (manual)
			printf(" damaged, manual mode selected\n");
	}

	if (!manual) {
		ptr = getcodpar("Manual or automatic", Matab);
		manual = (long) ptr->value;
	}
	if (!manual && damaged(COMPUTER)) {
		printf("Computer damaged, manual selected\n");
		skiptonl(0);
		manual++;
	}

	/* initialize the bank[] array */
	flag = 1;
	for (i = 0; i < NBANKS; i++)
		bank[i].units = 0;
	if (manual) {
		/* collect manual mode statistics */
		while (flag) {
			printf("%d units available\n", Ship.energy);
			extra = 0;
			flag = 0;
			for (i = 0; i < NBANKS; i++) {
				b = &bank[i];
				printf("\nBank %d:\n", i);
				hit = getintpar("units");
				if (hit < 0)
					return;
				if (hit == 0)
					break;
				extra += hit;
				if (extra > Ship.energy) {
					printf("available energy exceeded.  ");
					skiptonl(0);
					flag++;
					break;
				}
				b->units = hit;
				hit = getintpar("course");
				if (hit < 0 || hit > 360)
					return;
				b->angle = hit * 0.0174532925;
				b->spread = getfltpar("spread");
				if (b->spread < 0 || b->spread > 1)
					return;
			}
			Ship.energy -= extra;
		}
		extra = 0;
	} else {
		/* automatic distribution of power */
		if (Etc.nkling <= 0) {
			printf("Sulu: But there are no Klingons in this "
			       "quadrant\n");
			return;
		}
		printf("Phasers locked on target.  ");
		while (flag) {
			printf("%d units available\n", Ship.energy);
			hit = getintpar("Units to fire");
			if (hit <= 0)
				return;
			if (hit > Ship.energy) {
				printf("available energy exceeded.  ");
				skiptonl(0);
				continue;
			}
			flag = 0;
			Ship.energy -= hit;
			extra = hit;
			n = Etc.nkling;
			if (n > NBANKS)
				n = NBANKS;
			tot = n * (n + 1) / 2;
			for (i = 0; i < n; i++) {
				k = &Etc.klingon[i];
				b = &bank[i];
				distfactor = k->dist;
				anglefactor = ALPHA * BETA * OMEGA /
					(distfactor * distfactor + EPSILON);
				anglefactor *= GAMMA;
				distfactor = k->power;
				distfactor /= anglefactor;
				hitreqd[i] = distfactor + 0.5;
				dx = Ship.sectx - k->x;
				dy = k->y - Ship.secty;
				b->angle = atan2(dy, dx);
				b->spread = 0.0;
				b->units = ((n - i) / tot) * extra;
#ifdef xTRACE
				if (Trace) {
					printf("b%d hr%d u%d df%.2f af%.2f\n",
						i, hitreqd[i], b->units,
						distfactor, anglefactor);
				}
#endif
				extra -= b->units;
				hit = b->units - hitreqd[i];
				if (hit > 0) {
					extra += hit;
					b->units -= hit;
				}
			}

			/* give out any extra energy we might have around */
			if (extra > 0) {
				for (i = 0; i < n; i++) {
					b = &bank[i];
					hit = hitreqd[i] - b->units;
					if (hit <= 0)
						continue;
					if (hit >= extra) {
						b->units += extra;
						extra = 0;
						break;
					}
					b->units = hitreqd[i];
					extra -= hit;
				}
				if (extra > 0)
					printf("%d units overkill\n", extra);
			}
		}
	}

#ifdef xTRACE
	if (Trace) {
		for (i = 0; i < NBANKS; i++) {
			b = &bank[i];
			printf("b%d u%d", i, b->units);
			if (b->units > 0)
				printf(" a%.2f s%.2f\n", b->angle, b->spread);
			else
				printf("\n");
		}
	}
#endif

	/* actually fire the shots */
	Move.free = 0;
	for (i = 0; i < NBANKS; i++) {
		b = &bank[i];
		if (b->units <= 0) {
			continue;
		}
		printf("\nPhaser bank %d fires:\n", i);
		n = Etc.nkling;
		k = Etc.klingon;
		for (j = 0; j < n; j++) {
			if (b->units <= 0)
				break;
			/*
			** The formula for hit is as follows:
			**
			**  zap = OMEGA * [(sigma + ALPHA) * (rho + BETA)]
			**	/ (dist ** 2 + EPSILON)]
			**	* [cos(delta * sigma) + GAMMA]
			**	* hit
			**
			** where sigma is the spread factor,
			** rho is a random number (0 -> 1),
			** GAMMA is a crud factor for angle (essentially
			**	cruds up the spread factor),
			** delta is the difference in radians between the
			**	angle you are shooting at and the actual
			**	angle of the klingon,
			** ALPHA scales down the significance of sigma,
			** BETA scales down the significance of rho,
			** OMEGA is the magic number which makes everything
			**	up to "* hit" between zero and one,
			** dist is the distance to the klingon
			** hit is the number of units in the bank, and
			** zap is the amount of the actual hit.
			**
			** Everything up through dist squared should maximize
			** at 1.0, so that the distance factor is never
			** greater than one.  Conveniently, cos() is
			** never greater than one, but the same restric-
			** tion applies.
			*/
			distfactor = BETA + franf();
			distfactor *= ALPHA + b->spread;
			distfactor *= OMEGA;
			anglefactor = k->dist;
			distfactor /= anglefactor * anglefactor + EPSILON;
			distfactor *= b->units;
			dx = Ship.sectx - k->x;
			dy = k->y - Ship.secty;
			anglefactor = atan2(dy, dx) - b->angle;
			anglefactor = cos((anglefactor * b->spread) + GAMMA);
			if (anglefactor < 0.0) {
				k++;
				continue;
			}
			hit = anglefactor * distfactor + 0.5;
			k->power -= hit;
			printf("%d unit hit on Klingon", hit);
			if (!damaged(SRSCAN))
				printf(" at %d,%d", k->x, k->y);
			printf("\n");
			b->units -= hit;
			if (k->power <= 0) {
				killk(k->x, k->y);
				continue;
			}
			k++;
		}
	}

	/* compute overkill */
	for (i = 0; i < NBANKS; i++)
		extra += bank[i].units;
	if (extra > 0)
		printf("\n%d units expended on empty space\n", extra);
}
