#ifndef lint
static char *RCSid = "$Header: fire.c,v 1.22 90/03/20 01:09:30 mr-frog Exp $";
#endif /* not lint */

/*
 * multifire.c
 *
 * fire at other sectors, ships.
 *
 * from PSL Empire, 1985
 */

#include "misc.h"
#include "var.h"
#include "xy.h"
#include "treaty.h"
#include "nat.h"
#include "ship.h"
#include "sect.h"
#include "news.h"
#include "nsc.h"
#include "file.h"
#include "queue.h"
#include <ctype.h>

#ifdef MULTIFIRE
enum targ_type {
	targ_land, targ_ship, targ_sub, targ_bogus
};

struct flist{
	struct qelem	queue;	/* list of fired things */
	int		isship;	/* ship? otherwise sector */
	int		uid;	/* ship uid */
	coord		x,y;	/* sector coords */
	int		defdam;	/* damage defenders did */
	int		victim;	/* who I was shooting at */
};

int fire_issector();

union item_u {
        struct shpstr ship;
	struct sctstr sect;
#ifdef LANDUNITS
        struct landstr land;
#endif /* LANDUNITS */
};

multifire()
{
	extern	char *argp[];
	char	vbuf[10];
	char	fbuf[10];
	char	*ptr;
	char	buf[80];
	double	range2, range;
	int	trange;
	coord	fx;
	coord	fy;
	coord	x;
	coord	y;
	int	btused=0;
	int	mil;
	int	gun;
	int	shell;
	int	shots;
	double	guneff;
	int	dam;
	int	totaldefdam=0;
	int	def;
	int	vdef;
	int	fshipno;
	int	vshipno;
	double	prb;
	natid	vict;
	struct	shpstr fship;
	struct	sctstr fsect;
	struct	shpstr vship;
	struct	sctstr vsect;
	enum	targ_type target, attacker;
#ifdef SLOW_WAR
	int	rel;
	struct natstr	*natp;
#endif /* SLOW_WAR */
	struct  nstr_item nbst;
	int	type;
	char    *sav;
	char    *p;
	int     nfiring=0;
	int     ndefending=0;
	union   item_u item;
	struct	qelem fired, defended, *qp;
	struct	flist *fp;
	double	odds;

#ifdef BETTERARMOR
        struct  mchrstr *mcp;
#endif /* BETTERARMOR */

	initque(&fired);
	initque(&defended);
	type=(-1);
	while ((type != EF_SECTOR) && (type != EF_SHIP)){
        	if ((p = getstarg(argp[1],
			"Firing from ship(s) or sect(s)? ")) == 0)
			return RET_SYN;
        	argp[1]=0;
        	if (bcmp(p,"ship",4) && bcmp(p,"sect",4))
        		continue;
        	type = ef_byname(p);
        	if (type == EF_SECTOR){
                	attacker=targ_land;
			shots = 1;
		}
        	else if (type == EF_SHIP) {
                	attacker=targ_ship;
		}
		else
			pr("Please type 'ship' or 'sect'!\n");
	}
	if ((ptr = getstarg(argp[2], "Firing from? ")) == 0 || *ptr == '\0')
		return RET_SYN;

        if (!snxtitem(&nbst, type, ptr))
                return RET_SYN;

        if (!snxtitem(&nbst, type, ptr))
                return RET_SYN;

while(nxtitem(&nbst, (char *)&item)){
	if (attacker == targ_ship){
		if (!getship(item.ship.shp_uid, &fship))
			continue;
		if (item.ship.shp_own != cnum)
			continue;
               	if (getvar(V_MILIT, (char *)&item.ship, EF_SHIP) < 1){
			pr(fmt("Not enough mil on ship #%d\n",item.ship.shp_uid));
			continue;
		}
                gun = getvar(V_GUN, (char *)&item.ship, EF_SHIP);
               	gun = min(gun, mchr[item.ship.shp_type].m_glim);
               	if (mchr[item.ship.shp_type].m_frnge == 0){
			pr(fmt("Ships %d cannot fire guns!\n",item.ship.shp_uid));
			continue;
		}
		if (gun == 0){
			pr(fmt("Not enough guns on ship #%d\n",item.ship.shp_uid));
			continue;
		}
                if (getvar(V_SHELL, (char *)&item.ship, EF_SHIP) == 0){
			pr(fmt("Not enough shells on ship #%d\n",item.ship.shp_uid));
			continue;
		}
               	if (item.ship.shp_effic < 60){
			pr(fmt("Ship #%d is crippled!\n",item.ship.shp_uid));
			continue;
		}
		fshipno=fship.shp_uid;
	}
	else if (attacker == targ_land){
		if (!getsect(item.sect.sct_x,item.sect.sct_y,&fsect))
			continue;
		if (item.sect.sct_own != cnum)
			continue;
		if (item.sect.sct_type != SCT_FORTR)
			continue;
		if (getvar(V_GUN, (char *)&item.sect, EF_SECTOR) == 0){
			pr(fmt("Not enough guns in sector %s!\n",xyas(item.sect.sct_x,item.sect.sct_y,cnum)));
			continue;
		}
		if (getvar(V_SHELL, (char *)&item.sect, EF_SECTOR) == 0){
			pr(fmt("Not enough shells in sector %s!\n",xyas(item.sect.sct_x,item.sect.sct_y,cnum)));
			continue;
		}
		if (getvar(V_MILIT, (char *)&item.sect, EF_SECTOR) < 5){
			pr(fmt("Not enough military in sector %s!\n",xyas(item.sect.sct_x,item.sect.sct_y,cnum)));
			continue;
		}
		pr(fmt("\nSector %s firing\n",xyas(fsect.sct_x,fsect.sct_y,cnum)));
	}
	if ((ptr = getstarg(argp[3], "Firing at? ")) == 0 || *ptr == '\0')
		continue;
	(void)strcpy(vbuf, ptr);
	if (fire_issector(vbuf))
		target = targ_land;
	else
		target = targ_ship;
	if (target == targ_ship) {
		vshipno = atoi(vbuf);
		if (vshipno < 0 || !getship(vshipno, &vship) || 
			(!vship.shp_own))  {
				pr("No such ship exists!\n");
				continue;
		}
		vdef = seadef(vship.shp_type);
		target = (mchr[vship.shp_type].m_flags & M_SUB) ?
			targ_sub : targ_ship;
		vict = vship.shp_own;
		x = vship.shp_x;
		y = vship.shp_y;
		if (!getsect(x, y, &vsect)) {
			pr("No such sector exists!\n");
			continue;
		}
	} else {
		if (!sarg_xy(vbuf, &x, &y) || !getsect(x, y, &vsect)) {
			pr("No such sector exists!\n");
			return RET_SYN;
		}
		if (vsect.sct_type == SCT_SANCT || 
		    vsect.sct_type == SCT_WATER)
			target = targ_bogus;
		else
			target = targ_land;
		vdef = landdef((int)vsect.sct_type);
		vict = vsect.sct_own;
		x = vsect.sct_x;
		y = vsect.sct_y;
	}
	if (attacker == targ_ship) {
		shots = -1; /* convert to max later */
		if (fship.shp_own != cnum) {
			pr("Not your ship!\n");
			continue;
		}
		fx = fship.shp_x;
		fy = fship.shp_y;
		attacker = (mchr[fship.shp_type].m_flags & M_SUB) ?
			targ_sub : targ_ship;
		if (attacker == targ_sub){
			pr("Subs may not fire normally.. use torpedo.\n");
			continue;
		}
		if ((mil = getvar(V_MILIT, (char *)&fship, EF_SHIP)) < 1) {
			pr("Not enough military for firing crew.\n");
			continue;
		}
		gun = getvar(V_GUN, (char *)&fship, EF_SHIP);
		gun = min(gun, mchr[fship.shp_type].m_glim);
		if (mchr[fship.shp_type].m_frnge == 0 || gun == 0) {
			pr("Insufficient arms.\n");
			continue;
		}
		if ((shell = getvar(V_SHELL, (char *)&fship, EF_SHIP)) == 0) {
			pr("Klick!     ...\n");
			continue;
		}
		if (fship.shp_effic < 60) {
			pr(fmt("Ship #%d is crippled (%d%%)\n", fshipno,
			       fship.shp_effic));
			continue;
		}
		range = techfact(fship.shp_tech,
				 mchr[fship.shp_type].m_frnge / 2.0);
		range2 = roundrange(range);
		pr(fmt("range is %.2f (%.2f)\n", range2, range));
		if (target == targ_sub) {
			if ((mchr[fship.shp_type].m_flags & M_DCH) == 0) {
				pr(fmt("A %s can't drop depth charges!\n",
				       mchr[fship.shp_type].m_name));
				continue;
			}
#ifndef NEWSUBS
			if (fx != x || fy != y) {
				pr("Y-guns can't shoot that far!\n");
				continue;
			}
#endif /* NEWSUBS */
			if (shell < 2) {
				pr("Not enough shells for depth charge!\n");
				continue;
			}
			shots = 2;
		} else {
			gun = min(gun, shell);
			gun = min(gun, mil / 2);
			gun = max(gun, 1);
			if (shots > gun || shots < 0)
				shots = gun;
			else if (shots == 0) {
				pr("No shots fired.\n");
				continue;
			}
		}
		guneff = seagun(fship.shp_type, fship.shp_effic, shots);
		def = seadef(fship.shp_type);
		shell -= shots;
		putvar(V_SHELL, shell, (char *)&fship, EF_SHIP);
#ifndef NOMOBCOST
		fship.shp_mobil = max(fship.shp_mobil - 15, -100);
#endif /* NOMOBCOST */
	} else {
		fx=fsect.sct_x;
		fy=fsect.sct_y;
		if (fsect.sct_own != cnum) {
			pr(fmt("No fortress at %s\n", xyas(fsect.sct_x,
				fsect.sct_y,cnum)));
			continue;
		}
		if (fsect.sct_type != SCT_FORTR) {
			pr("Not a fortress.\n");
			continue;
		}
		attacker = targ_land;
		if ((gun = getvar(V_GUN, (char *)&fsect, EF_SECTOR)) == 0) {
			pr("Insufficient arms.\n");
			continue;
		}
		if ((shell = getvar(V_SHELL, (char *)&fsect, EF_SECTOR)) == 0) {
			pr("Klick!     ...\n");
			continue;
		}
		if (getvar(V_MILIT, (char *)&fsect, EF_SECTOR) < 5) {
			pr("Not enough military for firing crew.\n");
			continue;
		}
		if (target == targ_sub) {
			pr("Target ship not sighted!\n");
			continue;
		}
		range = tfact(cnum, (double)min(gun, 7));
		range2 = roundrange(range);
		pr(fmt("range is %.2f (%.2f)\n", range2, range));
		guneff = landgun((int)fsect.sct_effic);
		def = landdef((int)fsect.sct_type);
		putvar(V_SHELL, --shell, (char *)&fsect, EF_SECTOR);
	}
	trange = mapdist(x, y, fx, fy);
	if (trange > range2){
		switch (target) {
		case targ_land:
		case targ_bogus:
			pr("Target out of range.  Thud.\n");
			break ;
		default:
			pr("Target ship out of range.  Splash.\n");
			break ;
		}	
        	switch (attacker) {
        	case targ_land:
                	putsect(&fsect);
                	break ;
        	default:
			putship(fshipno, &fship);
		}
		continue;
	}
	if (target == targ_bogus) {
		if (vsect.sct_type == SCT_SANCT) {
			pr(fmt("%s is a %s!!\n", vbuf,
				dchr[SCT_SANCT].d_name));
			continue;
		} else if (vsect.sct_type == SCT_WATER) {
			pr(fmt("You must specify a ship in sector %s!\n",
				vbuf));
			continue;
		}
	}
	if (!trechk(cnum, vict, target == targ_land ? LANFIR : SEAFIR))
		continue;

#ifdef SLOW_WAR
	if (target == targ_land){
		natp = getnatp(cnum);
		rel = getrel(natp,vict);
		if ((rel != AT_WAR) && (cnum != vict) && (vict) && (vsect.sct_oldown != cnum)){
			pr("You're not at war with them!\n");
			continue;
		}
	}
#endif /* SLOW_WAR */
	nfiring++;
	switch (target) {
	case targ_sub:
		pr("Kawhomp!!!\07\n");
#ifdef RETREAT
		if (vship.shp_rflags & RET_DCHRGED)
			retreat(&vship,'d');
#endif /* RETREAT */
		break ;
	default:
		pr("Kaboom!!!\07\n");
		prb = (double)trange / range2;
		prb *= prb;
		srandom(random());
		if (chance(prb)) {
			pr(fmt("Wind deflects shell%s.\n", splur(shots)));
			guneff *= (chance(0.50) ? 0.5 : 1.0 - prb);
		}
		break ;
	}
	dam = shelldam((double)guneff, vdef);
	btused = roundavg(guneff / 20.0);
	switch (target) {
	case targ_land:
		nreport(cnum, N_SCT_SHELL, vict, 1);
		sectdamage(&vsect, dam);
		if (vict && vict != cnum)
			wu(0, vict,
			   fmt("Country #%d shelled sector %s for %d%% damage",
			       cnum, xyas(x, y, vict), dam));
		pr(fmt("Shell%s hit sector %s for %d%% damage.\n",
		       splur(shots), xyas(x, y, cnum), dam));
		break ;
	case targ_ship:
		nreport(cnum, N_SHP_SHELL, vict, 1);
	default:
#ifdef NEWSUBS
		if (target == targ_sub){
			dam = (random() % 30) + 10;
			if (trange)
				dam = (((float)dam)/((float)(trange+2.0)/2.0));
			dam *= ((float)fship.shp_effic/100.0);
		}
#endif /* NEWSUBS */
#ifdef BETTERARMOR
        	mcp = &mchr[vship.shp_type];
		dam = dam * (63.0/(float)mcp->m_armor);
#endif /* BETTERARMOR */
#ifdef RETREAT
		if ((target != targ_sub) ||
			((vship.shp_rflags & RET_DCHRGED) == 0))
#endif /* RETREAT */
			check_retreat_and_do_shipdamage(&vship, dam);
#ifdef RETREAT
                else
                        shipdamage(&vship, dam);
#endif /* RETREAT */
		if (vict)
			wu(0, vict,
#ifdef	SHIPNAMES
			   fmt("Country #%d shelled %s %s(#%d) for %d%% damage",
			       cnum, mchr[vship.shp_type].m_name,
			       vship.shp_name, vshipno,
#else
			   fmt("Country #%d shelled %s #%d for %d%% damage",
			       cnum, mchr[vship.shp_type].m_name, vshipno,
#endif	SHIPNAMES
			       dam));
		if (vship.shp_effic > 20)
#ifdef	SHIPNAMES
			pr(fmt("Shell%s hit %s %s(#%d) for %d%% damage.\n",
			       splur(shots), mchr[vship.shp_type].m_name,
			       vship.shp_name,
#else
			pr(fmt("Shell%s hit %s #%d for %d%% damage.\n",
			       splur(shots), mchr[vship.shp_type].m_name,
#endif	SHIPNAMES
			       vshipno, dam));
		if (vship.shp_effic <= 20)
#ifdef	SHIPNAMES
			pr(fmt("%s %s(#%d) sunk!\n",
			       mchr[vship.shp_type].m_name, vship.shp_name,
			       vshipno));
#else
			pr(fmt("%s #%d sunk!\n", mchr[vship.shp_type].m_name,
			       vshipno));
#endif	SHIPNAMES
		break ;
	}
	totaldefdam=defend(&fired,&defended,target,attacker,&vsect,&fsect,&vship,&fship,vdef,def,x,y,fx,fy,&ndefending);
	switch (target) {
	case targ_land:
		putsect(&vsect);
		break ;
	default:
		putship(vshipno, &vship);
		break ;
	}
#ifdef RETREAT
	if ((totaldefdam == 0) && (target == targ_ship))
		if ((vship.shp_rflags & RET_INJURED) == 0)
			retreat(&vship,'h');
#endif /* RETREAT */
	switch (attacker) {
	case targ_land:
		putsect(&fsect);
		break ;
	default:
#ifdef	MISSDEF
		if ((target == targ_ship) || (target == targ_sub))
		{
			if (fship.shp_effic > 20)
			{
				missdef(&fship,vict);
			};
		};
#endif	MISSDEF
		putship(fshipno, &fship);
		break ;
	}
}

	use_ammo(&defended);
	odds = ((double)ndefending)/((double)nfiring);
	do_defdam(&fired, odds);
	NAT_DELTA(nat_btu, cnum, -btused);
	return RET_OK;
}

defend(al,dl,target,attacker,vsect,fsect,vship,fship,vdef,fdef,vx,vy,fx,fy,nd)
	struct	qelem *al;
	struct	qelem *dl;
	enum	targ_type target, attacker;
	struct	sctstr *vsect, *fsect;
	struct	shpstr *vship, *fship;
	int	vdef,fdef,vx,vy,fx,fy,*nd;
{

	int	dam;
	char	buf[80];
	struct mchrstr *mcp;
	int	vict, nfiring=0;
	struct flist *fp;
	int	aown;

	if (attacker == targ_land)
		aown = fsect->sct_own;
	else
		aown = fship->shp_own;

	if (target == targ_land)
		vict = vsect->sct_own;
	else
		vict = vship->shp_own;

	if (dam = quiet_bigdef(dl, vict, aown, fdef, fx, fy, &nfiring)) {
		if (nfiring > *nd)
			*nd = nfiring;
		nreport(vict, N_FIRE_BACK, cnum, 1);
		fp = (struct flist *)malloc(sizeof(struct flist));
		bzero(fp,sizeof(struct flist));
		fp->defdam = dam;
		fp->victim = vict;
		switch (attacker) {
			case targ_land:
				fp->x = fsect->sct_x;
				fp->y = fsect->sct_y;
				break ;
			default:
				fp->isship = 1;
				fp->uid = fship->shp_uid;
				break ;
		}
		insque(&fp->queue,al);
	}

	return(dam);
}

do_defdam(list, odds)
	struct	qelem *list;
	double	odds;
{

	int	dam, vict,first=1;
	char	buf[80];
	struct	mchrstr *mcp;
	struct	flist *fp;
	struct	shpstr ship;
	struct	sctstr sect;
	struct	qelem *qp;

	for (qp = list->q_forw; qp != list; qp = qp->q_forw){
		if (first){
			pr("\nDefenders fire back!\07\n");
			first=0;
		}
		fp = (struct flist *)qp;
		dam = (odds * (double)fp->defdam);

		if (fp->isship){
			getship(fp->uid, &ship);
			vict = fp->victim;
#ifdef BETTERARMOR
			mcp = &mchr[ship.shp_type];
			dam = ((float)dam * (63.0/(float)mcp->m_armor));
#endif /* BETTERARMOR */
			pr(
#ifdef	SHIPNAMES
				fmt("Return fire does %d%% damage to %s %s(#%d)\n",
				dam, mchr[ship.shp_type].m_name, 
				ship.shp_name, ship.shp_uid));
#else
				fmt("Return fire does %d%% damage to %s #%d\n",
				dam, mchr[ship.shp_type].m_name, ship.shp_uid));
#endif	SHIPNAMES
			if (vict)
				wu(0, vict,
#ifdef	SHIPNAMES
					fmt("Return fire damaged %s %s(#%d) %d%%",
					mchr[ship.shp_type].m_name,
					ship.shp_name,
#else
					fmt("Return fire damaged %s #%d %d%%",
					mchr[ship.shp_type].m_name,
#endif	SHIPNAMES
					ship.shp_uid, dam));
			shipdamage(&ship, dam);
			putship(ship.shp_uid,&ship);
		}else{
			getsect(fp->x, fp->y, &sect);
			vict = fp->victim;
			sectdamage(&sect, dam);
			pr(fmt("Return fire damaged %s %d%%\n",
				xyas(fp->x, fp->y, cnum), dam));
			putsect(&sect);
			if (vict)
				wu(0, vict, fmt("Return fire damaged sector %s %d%%",
					xyas(fp->x, fp->y, vict), dam));
		}
		nreport(vict, N_FIRE_BACK, cnum, 1);
		remque(&fp->queue);
		free((char *)fp);
	}
}

int
quiet_bigdef(list, own, aown, def, ax, ay, nfiring)
	struct qelem *list;
	natid	own, aown;
	int	def;
	coord	ax;
	coord	ay;
	int	*nfiring;
{
	int	nshot;
	double	range;
	double	eff;
	struct	shpstr ship;
	struct	nstr_item ni;
	int	vec[I_MAX+1];
	int	dam;
	double	tech;
	struct	sctstr firing;
	struct	nstr_sect ns;
	coord	x,y;
	struct flist *fp;

	if (own == 0)
		return 0;
	dam = 0;
	snxtitem_dist(&ni, EF_SHIP, ax, ay, 8);
	while (nxtitem(&ni, (caddr_t)&ship)){
		if (ship.shp_own != own)
			continue;
		/* Don't shoot yourself */
		if (ship.shp_own == aown)
			continue;
		if (ship.shp_effic < 60)
			continue;
		range = techfact(ship.shp_tech, 
			mchr[ship.shp_type].m_frnge * ship.shp_effic / 200.0);
		range = roundrange(range);
		if (range < ni.curdist)
			continue;
		/* must have gun, shell, and milit to fire */
		if (getvec(VT_ITEM, vec, (caddr_t)&ship, EF_SHIP) < 3)
			continue;
		nshot = min(min(vec[I_GUN], vec[I_SHELL]), vec[I_MILIT]);
		nshot = min(nshot, mchr[ship.shp_type].m_glim);
		if (nshot == 0)
			continue;
		(*nfiring)++;
		fp = (struct flist *)malloc(sizeof(struct flist));
		bzero(fp,sizeof(struct flist));
		fp->isship = 1;
		fp->uid = ship.shp_uid;
		add_to_fired_queue(&fp->queue, list);
		dam += shelldam(seagun(ship.shp_type, ship.shp_effic, nshot),
			def);
		
	}

	/*
 	* Determine if any nearby gun-equipped sectors are within
 	* range and able to fire at an attacker.  Firing sectors
 	* need to have guns, shells, and military.  Sector being
 	* attacked is x,y -- attacker is at ax,ay.
 	*/

	tech = tfact(own, 1.0);
	snxtsct_dist(&ns, ax, ay, (int) (tech * 8.0));
	while (nxtsct(&ns, &firing)){
		if (firing.sct_own != own)
			continue;
		/* Don't shoot yourself */
		if (firing.sct_own == aown)
			continue;
		if (getvec(VT_ITEM, vec, (caddr_t)&firing, EF_SECTOR) < 0)
			continue;
		if (vec[I_GUN] == 0 || vec[I_MILIT] < 5 || vec[I_SHELL] == 0)
			continue;
		range = tech * min(vec[I_GUN], 7);
		range = roundrange(range);
		if (range < ns.curdist)
			continue;
		if (firing.sct_type != SCT_FORTR)
			continue;
		(*nfiring)++;
		fp = (struct flist *)malloc(sizeof(struct flist));
		bzero(fp,sizeof(struct flist));
		fp->x = firing.sct_x;
		fp->y = firing.sct_y;
		add_to_fired_queue(&fp->queue, list);
		dam += shelldam(landgun((int)firing.sct_effic), def);
	}
	return ((*nfiring) == 0 ? 0 : (dam/(*nfiring)));
}

use_ammo(list)
	struct qelem	*list;
{
	struct qelem	*qp;
	struct flist	*fp;
	struct shpstr	ship;
	struct sctstr	sect;
	int		shell, type;
	char		*ptr;

	/* use 1 shell from everyone */
	for (qp = list->q_forw; qp != list; qp = qp->q_forw){
		fp = (struct flist *)qp;
		if (fp->isship){
			getship(fp->uid,&ship);
			ptr = (char *)&ship;
			type = EF_SHIP;
		}else{
			getsect(fp->x,fp->y,&sect);
			ptr = (char *)&sect;
			type = EF_SECTOR;
		}
		shell = getvar(V_SHELL, ptr, type);
		putvar(V_SHELL, --shell, ptr, type);
		if (fp->isship)
			putship(ship.shp_uid,&ship);
		else
			putsect(&sect);

		remque(&fp->queue);
		free((char *)fp);
	}

}

add_to_fired_queue(elem, list)
	struct qelem	*elem, *list;
{
	struct qelem	*qp;
	struct flist	*fp, *ep;
	int		bad=0;

	ep = (struct flist *)elem;

	/* Don't put them on the list if they're already there */
	for (qp = list->q_forw; qp != list; qp = qp->q_forw){
		fp = (struct flist *)qp;
		if (fp->isship && (fp->uid == ep->uid))
			bad=1;
		if (!fp->isship && (fp->x == ep->x) && (fp->y == ep->y))
			bad=1;
	}

	if (!bad)
		insque(elem, list);
}
#endif /* MULTIFIRE */
