/*
 *	monster.c		Larn is copyrighted 1986 by Noah Morgan. 
 *
 *	This file contains the following functions:
 *	----------------------------------------------------------------------------
 *
 *	createmonster(monstno) 		Function to create a monster next to the player
 *		int monstno;
 *
 *	int cgood(x,y,itm,monst)	Function to check location for emptiness
 *		int x,y,itm,monst;
 *
 *	createitem(it,arg) 			Routine to place an item next to the player
 *		int it,arg;
 *
 *	cast() 				Subroutine called by parse to cast a spell for the user
 *
 *	speldamage(x) 		Function to perform spell functions cast by the player
 *		int x;
 *
 *	loseint()			Routine to decrement your int (intelligence) if > 3
 *
 *	isconfuse() 		Routine to check to see if player is confused
 *
 *	nospell(x,monst)	Routine to return 1 if a spell doesn't affect a monster
 *		int x,monst;
 *
 *	fullhit(xx)			Function to return full damage against a monst (aka web)
 *		int xx;
 *
 *	direct(spnum,dam,str,arg)	Routine to direct spell damage 1 square in 1 dir
 *		int spnum,dam,arg;
 *		char *str;
 *
 *	godirect(spnum,dam,str,delay,cshow)		Function to perform missile attacks
 *		int spnum,dam,delay;
 *		char *str,cshow;
 *
 *	ifblind(x,y)	Routine to put "monster" or the monster name into lastmosnt
 *		int x,y;
 *
 *	tdirect(spnum)			Routine to teleport away a monster
 *		int spnum;
 *
 *	omnidirect(sp,dam,str)  Routine to damage all monsters 1 square from player
 *		int sp,dam;
 *		char *str;
 *
 *	dirsub(x,y)			Routine to ask for direction, then modify x,y for it
 *		int *x,*y;
 *
 *	vxy(x,y)		  	Routine to verify/fix (*x,*y) for being within bounds
 *		int *x,*y;
 *
 *	dirpoly(spnum)		Routine to ask for a direction and polymorph a monst
 *		int spnum;
 *
 *	hitmonster(x,y) 	Function to hit a monster at the designated coordinates
 *		int x,y;
 *
 *	hitm(x,y,amt)		Function to just hit a monster at a given coordinates
 *		int x,y,amt;
 *
 *	hitplayer(x,y) 		Function for the monster to hit the player from (x,y)
 *		int x,y;
 *
 *	dropsomething(monst) 	Function to create an object when a monster dies
 *		int monst;
 *
 *	dropgold(amount) 		Function to drop some gold around player
 *		int amount;
 *
 *	something(level) 		Function to create a random item around player
 *		int level;
 *
 *	newobject(lev,i) 		Routine to return a randomly selected new object
 *		int lev,*i;
 *
 *  spattack(atckno,xx,yy) 	Function to process special attacks from monsters
 *  	int atckno,xx,yy;
 *
 *	checkloss(x) 	Routine to subtract hp from user and flag bottomline display
 *		int x;
 *
 *	annihilate()   Routine to annihilate monsters around player, playerx,playery
 *
 *	newsphere(x,y,dir,lifetime)  Function to create a new sphere of annihilation
 *		int x,y,dir,lifetime;
 *
 *	rmsphere(x,y)		Function to delete a sphere of annihilation from list
 *		int x,y;
 *
 *	sphboom(x,y)		Function to perform the effects of a sphere detonation
 *		int x,y;
 *
 *	genmonst()			Function to ask for monster and genocide from game
 *
 */
#include "header.h"

struct isave	/* used for altar reality */
	{
	char type;	/* 0=item,  1=monster */
	char id;	/* item number or monster number */
	short arg;	/* the type of item or hitpoints of monster */
	};

/*
 *	createmonster(monstno) 		Function to create a monster next to the player
 *		int monstno;
 *
 *	Enter with the monster number (1 to MAXMONST+8)
 *	Returns no value.
 */
createmonster(mon)
	int mon;
	{
	register int x,y,k,i;
	if (mon<1 || mon>MAXMONST+8)	/* check for monster number out of bounds */
		{
		beep(); lprintf("\ncan't createmonst(%d)\n",(long)mon); nap(3000); return;
		}
	while (monster[mon].genocided && mon<MAXMONST) mon++; /* genocided? */
	for (k=rnd(8), i= -8; i<0; i++,k++)	/* choose direction, then try all */
		{
		if (k>8) k=1;	/* wraparound the diroff arrays */
		x = playerx + diroffx[k];		y = playery + diroffy[k];
		if (cgood(x,y,0,1))	/* if we can create here */
			{
			mitem[x][y] = mon;
			hitp[x][y] = monster[mon].hitpoints;
# ifdef DGK
			stealth[x][y]=0;
			know[x][y] &= ~KNOWHERE;
# else
			stealth[x][y]=know[x][y]=0;
# endif
			switch(mon)
				{
				case ROTHE: case POLTERGEIST: case VAMPIRE: stealth[x][y]=1;
				};
			return;
			}
		}
	}

/*
 *	int cgood(x,y,itm,monst)	  Function to check location for emptiness
 *		int x,y,itm,monst;
 *
 *	Routine to return TRUE if a location does not have itm or monst there
 *	returns FALSE (0) otherwise
 *	Enter with itm or monst TRUE or FALSE if checking it
 *	Example:  if itm==TRUE check for no item at this location
 *			  if monst==TRUE check for no monster at this location
 *	This routine will return FALSE if at a wall or the dungeon exit on level 1
 */
int cgood(x,y,itm,monst)
	register int x,y;
	int itm,monst;
	{
	if ((y>=0) && (y<=MAXY-1) && (x>=0) && (x<=MAXX-1)) /* within bounds? */
	  if (item[x][y]!=OWALL)	/* can't make anything on walls */
		if (itm==0 || (item[x][y]==0))	/* is it free of items? */
		  if (monst==0 || (mitem[x][y]==0))	/* is it free of monsters? */
		    if ((level!=1) || (x!=33) || (y!=MAXY-1)) /* not exit to level 1 */
			  return(1);
	return(0);
	}

/*
 *	createitem(it,arg) 		Routine to place an item next to the player
 *		int it,arg;
 *
 *	Enter with the item number and its argument (iven[], ivenarg[])
 *	Returns no value, thus we don't know about createitem() failures.
 */
createitem(it,arg)
	int it,arg;
	{
	register int x,y,k,i;
	if (it >= MAXOBJ) return;	/* no such object */
	for (k=rnd(8), i= -8; i<0; i++,k++)	/* choose direction, then try all */
		{
		if (k>8) k=1;	/* wraparound the diroff arrays */
		x = playerx + diroffx[k];		y = playery + diroffy[k];
		if (cgood(x,y,1,0))	/* if we can create here */
			{
			item[x][y] = it;  know[x][y]=0;  iarg[x][y]=arg;  return;
			}
		}
	}

/*
 *	cast() 		Subroutine called by parse to cast a spell for the user
 *
 *	No arguments and no return value.
 */
static char eys[] = "\nEnter your spell: ";
cast()
	{
	register int i,j,a,b,d;
	cursors();
	if (c[SPELLS]<=0) {	lprcat("\nYou don't have any spells!");	return;	}
	lprcat(eys);		--c[SPELLS];
	while ((a=getchar())=='D')
		{ seemagic(-1); cursors();  lprcat(eys); }
	if (a=='\33') goto over; /*	to escape casting a spell	*/
	if ((b=getchar())=='\33') goto over; /*	to escape casting a spell	*/
	if ((d=getchar())=='\33')
		{ over: lprcat(aborted); c[SPELLS]++; return; } /*	to escape casting a spell	*/
#ifdef EXTRA
	c[SPELLSCAST]++;
#endif
	for (lprc('\n'),j= -1,i=0; i<SPNUM; i++) /*seq search for his spell, hash?*/
		if ((spelcode[i][0]==a) && (spelcode[i][1]==b) && (spelcode[i][2]==d))
			if (spelknow[i])
				{  speldamage(i);  j = 1;  i=SPNUM; }

	if (j == -1) lprcat("  Nothing Happened ");
	bottomline();
	}

/*
 *	speldamage(x) 		Function to perform spell functions cast by the player
 *		int x;
 *
 *	Enter with the spell number, returns no value.
 *	Please insure that there are 2 spaces before all messages here
 */
speldamage(x)
	int x;
	{
	register int i,j,clev;
	int xl,xh,yl,yh;
	register char *p,*kn,*pm;
	if (x>=SPNUM) return;	/* no such spell */
	if (c[TIMESTOP])  { lprcat("  It didn't seem to work"); return; }  /* not if time stopped */
	clev = c[LEVEL];
	if ((rnd(23)==7) || (rnd(18) > c[INTELLIGENCE]))
		{ lprcat("  It didn't work!");  return; }
	if (clev*3+2 < x) { lprcat("  Nothing happens.  You seem inexperienced at this"); return; }

	switch(x)
		{
/* ----- LEVEL 1 SPELLS ----- */

		case 0:	if (c[PROTECTIONTIME]==0)	c[MOREDEFENSES]+=2; /* protection field +2 */
				c[PROTECTIONTIME] += 250;   return;

		case 1: i = rnd(((clev+1)<<1)) + clev + 3;
				godirect(x,i,(clev>=2)?"  Your missiles hit the %s":"  Your missile hit the %s",100,'+'); /* magic missile */

				return;

		case 2:	if (c[DEXCOUNT]==0)	c[DEXTERITY]+=3; /*	dexterity	*/
				c[DEXCOUNT] += 400;  	return;

		case 3: i=rnd(3)+1;
				p="  While the %s slept, you smashed it %d times";
			ws:	direct(x,fullhit(i),p,i); /*	sleep	*/	return;

		case 4:	/*	charm monster	*/	c[CHARMCOUNT] += c[CHARISMA]<<1;	return;

		case 5:	godirect(x,rnd(10)+15+clev,"  The sound damages the %s",70,'@'); /*	sonic spear */
				return;

/* ----- LEVEL 2 SPELLS ----- */

		case 6: i=rnd(3)+2;	p="  While the %s is entangled, you hit %d times";
				goto ws; /* web */

		case 7:	if (c[STRCOUNT]==0) c[STREXTRA]+=3;	/*	strength	*/
				c[STRCOUNT] += 150+rnd(100);    return;

		case 8:	yl = playery-5;     /* enlightenment */
				yh = playery+6;   xl = playerx-15;   xh = playerx+16;
				vxy(&xl,&yl);   vxy(&xh,&yh); /* check bounds */
				for (i=yl; i<=yh; i++) /* enlightenment	*/
# ifdef DGK
					for (j=xl; j<=xh; j++)
						know[j][i]=KNOWALL;
# else
					for (j=xl; j<=xh; j++)	know[j][i]=1;
# endif
				draws(xl,xh+1,yl,yh+1);	return;

		case 9:	raisehp(20+(clev<<1));  return;  /* healing */

		case 10:	c[BLINDCOUNT]=0;	return;	/* cure blindness	*/

		case 11:	createmonster(makemonst(level+1)+8);  return;

		case 12:	if (rnd(11)+7 <= c[WISDOM]) direct(x,rnd(20)+20+clev,"  The %s believed!",0);
					else lprcat("  It didn't believe the illusions!");
					return;

		case 13:	/* if he has the amulet of invisibility then add more time */
					for (j=i=0; i<26; i++)
						if (iven[i]==OAMULET) j+= 1+ivenarg[i];
					c[INVISIBILITY] += (j<<7)+12;   return;

/* ----- LEVEL 3 SPELLS ----- */

		case 14:	godirect(x,rnd(25+clev)+25+clev,"  The fireball hits the %s",40,'*'); return; /*	fireball */

		case 15:	godirect(x,rnd(25)+20+clev,"  Your cone of cold strikes the %s",60,'O');	/*	cold */
					return;

		case 16:	dirpoly(x);  return;	/*	polymorph */

		case 17:	c[CANCELLATION]+= 5+clev;	return;	/*	cancellation	*/

		case 18:	c[HASTESELF]+= 7+clev;  return;  /*	haste self	*/

		case 19:	omnidirect(x,30+rnd(10),"  The %s gasps for air");	/* cloud kill */
					return;

		case 20:	xh = min(playerx+1,MAXX-2);		yh = min(playery+1,MAXY-2);
					for (i=max(playerx-1,1); i<=xh; i++) /* vaporize rock */
					  for (j=max(playery-1,1); j<=yh; j++)
						{
						kn = &know[i][j];    pm = &mitem[i][j];
						switch(*(p= &item[i][j]))
						  {
						  case OWALL: if (level < MAXLEVEL+MAXVLEVEL-1)
											*p = *kn = 0;
										break;

						  case OSTATUE: if (c[HARDGAME]<3)
											 {
											 *p=OBOOK; iarg[i][j]=level;  *kn=0;
											 }
										break;
	
						  case OTHRONE: *pm=GNOMEKING;  *kn=0;  *p= OTHRONE2;
										hitp[i][j]=monster[GNOMEKING].hitpoints; break;

						  case OALTAR:	*pm=DEMONPRINCE;  *kn=0;
										hitp[i][j]=monster[DEMONPRINCE].hitpoints; break;
						  };
						switch(*pm)
							{
							case XORN:	ifblind(i,j);  hitm(i,j,200); break; /* Xorn takes damage from vpr */
							}
						}
					return;

/* ----- LEVEL 4 SPELLS ----- */

		case 21:	direct(x,100+clev,"  The %s shrivels up",0); /* dehydration */
					return;

		case 22:	godirect(x,rnd(25)+20+(clev<<1),"  A lightning bolt hits the %s",1,'~');	/*	lightning */
					return;

		case 23:	i=min(c[HP]-1,c[HPMAX]/2);	/* drain life */
					direct(x,i+i,"",0);	c[HP] -= i;  	return;

		case 24:	if (c[GLOBE]==0) c[MOREDEFENSES] += 10;
					c[GLOBE] += 200;  loseint();  /* globe of invulnerability */
					return;

		case 25:	omnidirect(x,32+clev,"  The %s struggles for air in your flood!"); /* flood */
					return;

		case 26:	if (rnd(151)==63) { beep(); lprcat("\nYour heart stopped!\n"); nap(4000);  died(270); return; }
					if (c[WISDOM]>rnd(10)+10) direct(x,2000,"  The %s's heart stopped",0); /* finger of death */
					else lprcat("  It didn't work"); return;

/* ----- LEVEL 5 SPELLS ----- */

		case 27:	c[SCAREMONST] += rnd(10)+clev;  return;  /* scare monster */

		case 28:	c[HOLDMONST] += rnd(10)+clev;  return;  /* hold monster */

		case 29:	c[TIMESTOP] += rnd(20)+(clev<<1);  return;  /* time stop */

		case 30:	tdirect(x);  return;  /* teleport away */

		case 31:	omnidirect(x,35+rnd(10)+clev,"  The %s cringes from the flame"); /* magic fire */
					return;

/* ----- LEVEL 6 SPELLS ----- */

		case 32:	if ((rnd(23)==5) && (wizard==0)) /* sphere of annihilation */
						{
						beep(); lprcat("\nYou have been enveloped by the zone of nothingness!\n");
						nap(4000);  died(258); return;
						}
					xl=playerx; yl=playery;
					loseint();
					i=dirsub(&xl,&yl); /* get direction of sphere */
					newsphere(xl,yl,i,rnd(20)+11);	/* make a sphere */
					return;

		case 33:	genmonst();  spelknow[33]=0;  /* genocide */
					loseint();
					return;

		case 34:	/* summon demon */
					if (rnd(100) > 30) { direct(x,150,"  The demon strikes at the %s",0);  return; }
					if (rnd(100) > 15) { lprcat("  Nothing seems to have happened");  return; }
					lprcat("  The demon turned on you and vanished!"); beep();
					i=rnd(40)+30;  lastnum=277;
					losehp(i); /* must say killed by a demon */ return;

		case 35:	/* walk through walls */
					c[WTW] += rnd(10)+5;	return;

		case 36:	/* alter reality */
					{
					struct isave *save;	/* pointer to item save structure */
					int sc;	sc=0;	/* # items saved */
					save = (struct isave *)malloc(sizeof(struct isave)*MAXX*MAXY*2);
					for (j=0; j<MAXY; j++)
						for (i=0; i<MAXX; i++) /* save all items and monsters */
							{
							xl = item[i][j];
							if (xl && xl!=OWALL && xl!=OANNIHILATION) 
								{
								save[sc].type=0;  save[sc].id=item[i][j];
								save[sc++].arg=iarg[i][j];
								}
							if (mitem[i][j]) 
								{
								save[sc].type=1;  save[sc].id=mitem[i][j];
								save[sc++].arg=hitp[i][j];
								}
							item[i][j]=OWALL;   mitem[i][j]=0;
# ifdef DGK
						if (wizard)
							know[i][j]=KNOWALL;
						else
							know[i][j]=0;
# else
							if (wizard) know[i][j]=1; else know[i][j]=0;
# endif
							}
					eat(1,1);	if (level==1) item[33][MAXY-1]=0;
					for (j=rnd(MAXY-2), i=1; i<MAXX-1; i++) item[i][j]=0;
					while (sc>0) /* put objects back in level */
						{
						--sc;
						if (save[sc].type == 0)
							{
							int trys;
							for (trys=100, i=j=1; --trys>0 && item[i][j]; i=rnd(MAXX-1), j=rnd(MAXY-1));
							if (trys) { item[i][j]=save[sc].id; iarg[i][j]=save[sc].arg; }
							}
						else
							{ /* put monsters back in */
							int trys;
							for (trys=100, i=j=1; --trys>0 && (item[i][j]==OWALL || mitem[i][j]); i=rnd(MAXX-1), j=rnd(MAXY-1));
							if (trys) { mitem[i][j]=save[sc].id; hitp[i][j]=save[sc].arg; }
							}
						}
					loseint();
					draws(0,MAXX,0,MAXY);  if (wizard==0) spelknow[36]=0;
					free((char*)save);	 positionplayer();  return;
					}

		case 37:	/* permanence */ adjtime(-99999L);  spelknow[37]=0; /* forget */
					loseint();
					return;

		default:	lprintf("  spell %d not available!",(long)x); beep();  return;
		};
	}

/*
 *	loseint()		Routine to subtract 1 from your int (intelligence) if > 3
 *
 *	No arguments and no return value
 */
loseint()
	{
	if (--c[INTELLIGENCE]<3)  c[INTELLIGENCE]=3;
	}

/*
 *	isconfuse() 		Routine to check to see if player is confused
 *
 *	This routine prints out a message saying "You can't aim your magic!"
 *	returns 0 if not confused, non-zero (time remaining confused) if confused
 */
isconfuse()
	{
	if (c[CONFUSE]) { lprcat(" You can't aim your magic!"); beep(); }
	return(c[CONFUSE]);
	}

/*
 *	nospell(x,monst)	Routine to return 1 if a spell doesn't affect a monster
 *		int x,monst;
 *
 *	Subroutine to return 1 if the spell can't affect the monster
 *	  otherwise returns 0
 *	Enter with the spell number in x, and the monster number in monst.
 */
nospell(x,monst)
	int x,monst;
	{
	register int tmp;
	if (x>=SPNUM || monst>=MAXMONST+8 || monst<0 || x<0) return(0);	/* bad spell or monst */
	if ((tmp=spelweird[monst-1][x])==0) return(0);
	cursors();  lprc('\n');  lprintf(spelmes[tmp],monster[monst].name);  return(1);
	}

/*
 *	fullhit(xx)		Function to return full damage against a monster (aka web)
 *		int xx;
 *
 *	Function to return hp damage to monster due to a number of full hits
 *	Enter with the number of full hits being done
 */
fullhit(xx)
	int xx;
	{
	register int i;
	if (xx<0 || xx>20) return(0);	/* fullhits are out of range */
	if (c[LANCEDEATH]) return(10000);	/* lance of death */
	i = xx * ((c[WCLASS]>>1)+c[STRENGTH]+c[STREXTRA]-c[HARDGAME]-12+c[MOREDAM]); 
	return( (i>=1) ? i : xx );
	}

/*
 *	direct(spnum,dam,str,arg)	Routine to direct spell damage 1 square in 1 dir
 *		int spnum,dam,arg;
 *		char *str;
 *
 *	Routine to ask for a direction to a spell and then hit the monster
 *	Enter with the spell number in spnum, the damage to be done in dam,
 *	  lprintf format string in str, and lprintf's argument in arg.
 *	Returns no value.
 */
direct(spnum,dam,str,arg)
	int spnum,dam,arg;
	char *str;
	{
	int x,y;
	register int m;
	if (spnum<0 || spnum>=SPNUM || str==0) return; /* bad arguments */
	if (isconfuse()) return;
	dirsub(&x,&y);
	m = mitem[x][y];
	if (item[x][y]==OMIRROR)
		{
		if (spnum==3) /* sleep */
			{
			lprcat("You fall asleep! "); beep();
		fool:
			arg += 2;
			while (arg-- > 0) { parse2(); nap(1000); }
			return;
			}
		else if (spnum==6) /* web */
			{
			lprcat("You get stuck in your own web! "); beep();
			goto fool;
			}
		else
			{
			lastnum=278; 
			lprintf(str,"spell caster (thats you)",(long)arg);
			beep(); losehp(dam); return;
			}
		}
	if (m==0)
		{	lprcat("  There wasn't anything there!");	return;  }
	ifblind(x,y);
	if (nospell(spnum,m)) { lasthx=x;  lasthy=y; return; }
	lprintf(str,lastmonst,(long)arg);       hitm(x,y,dam);
	}

/*
 *	godirect(spnum,dam,str,delay,cshow)		Function to perform missile attacks
 *		int spnum,dam,delay;
 *		char *str,cshow;
 *
 *	Function to hit in a direction from a missile weapon and have it keep
 *	on going in that direction until its power is exhausted
 *	Enter with the spell number in spnum, the power of the weapon in hp,
 *	  lprintf format string in str, the # of milliseconds to delay between 
 *	  locations in delay, and the character to represent the weapon in cshow.
 *	Returns no value.
 */
godirect(spnum,dam,str,delay,cshow)
	int spnum,dam,delay;
	char *str,cshow;
	{
	register char *p;
	register int x,y,m;
	int dx,dy;
	if (spnum<0 || spnum>=SPNUM || str==0 || delay<0) return; /* bad args */
	if (isconfuse()) return;
	dirsub(&dx,&dy);	x=dx;	y=dy;
	dx = x-playerx;		dy = y-playery;		x = playerx;	y = playery;
	while (dam>0)
		{
		x += dx;    y += dy;
	    if ((x > MAXX-1) || (y > MAXY-1) || (x < 0) || (y < 0))
			{
			dam=0;	break;  /* out of bounds */
			}
		if ((x==playerx) && (y==playery)) /* if energy hits player */
			{
			cursors(); lprcat("\nYou are hit my your own magic!"); beep();
			lastnum=278;  losehp(dam);  return;
			}
		if (c[BLINDCOUNT]==0) /* if not blind show effect */
			{
			cursor(x+1,y+1); lprc(cshow); nap(delay); show1cell(x,y);
			}
		if ((m=mitem[x][y]))	/* is there a monster there? */
			{
			ifblind(x,y);
			if (nospell(spnum,m)) { lasthx=x;  lasthy=y; return; }
			cursors(); lprc('\n');
			lprintf(str,lastmonst);		dam -= hitm(x,y,dam);
			show1cell(x,y);  nap(1000);		x -= dx