/*
 * redraw.c
 */
#include "copyright.h"

#include <stdio.h>
#include <signal.h>
#include <math.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdlib.h>
#include "defs.h"
#include "struct.h"
#include "data.h"
#include "packets.h"
#include "proto.h"

/* file scope prototypes */
static u_char newcourse(int x, int y);
static void auto_features(void);

extern int living;

void intrupt(void)
{
    if (testtime == -1) {
	struct reserved_spacket sp;

	/* Give a reasonable period of time to respond to query (and test code
	   if they need to) */
#define RSAREPLYTIMER 30

#ifdef RSA
	RSA_Client = 0;
#endif
	testtime = RSAREPLYTIMER * 10;	
	makeReservedPacket(&sp);
	MCOPY(sp.data, testdata, RESERVED_SIZE);
	sendClientPacket(&sp);
    } else if (testtime != 0) {
	testtime--;
	if (testtime==0) {
	  if (bypassed) {                     /* Deal with .bypass entries */
	    pmessage2(0, MALL | MJOIN, "GOD->ALL", me->p_no,
                    "%s %.16s is not using an RSA client",
                    ranks[me->p_stats.st_rank].name,
                      me->p_name);
	    ERROR(3,(".bypass person : %s - logged on\n",me->p_login));
	    me->p_stats.st_flags |= ST_CYBORG; /* mark for reference */
	    new_warning(UNDEF,"You are in the .bypass file, be good");
          } else {
	    /* User failed to respond to verification query.  Bye! */
	    if (!binconfirm)
		me->p_stats.st_flags |= ST_CYBORG; /* mark for reference 7/27/91 TC */
	    else {
		me->p_explode = 10;
		me->p_whydead = KQUIT;
		me->p_status = PEXPLODE;
		ERROR(3,("User binary failed to verify\n"));
#ifdef RSA
#ifdef SHOW_RSA
		pmessage2(0, MALL | MJOIN, "GOD->ALL", me->p_no,
			"%s %.16s failed to verify",
			ranks[me->p_stats.st_rank].name,
                	me->p_name);
#endif
		if (RSA_Client==1)
		    new_warning(UNDEF,"No customized binaries.  Please use a blessed one.");
		else if (RSA_Client==2)
                    new_warning(UNDEF,"Wrong Client Version Number!");
                else
		    new_warning(UNDEF,"You need a spiffy new RSA client for this server!");
#else
		new_warning(UNDEF,"No customized binaries. Please use a blessed one.");
#endif
	    }
	}
      }
    }
    if (me->p_status == PFREE) {
	ERROR(1,("intrupt: exitGame() on PFREE (pid %i)\n",getpid()));
	exitGame();
    }

#ifdef OBSERVERS
    if (!Observer || me->p_status == PEXPLODE)
#endif
    {
	if (me->p_status == PEXPLODE || me->p_status == PDEAD) {
	    FD_ZERO(&inputMask);
	}
	if (((me->p_status == PDEAD) || (me->p_status == POUTFIT))
	      && (me->p_ntorp <= 0) && (me->p_nplasmatorp <= 0)) {
	    death();
            return;
	}
       
    }

    if (clue) {
	    if (clueFuse > CLUEINTERVAL) { 
		clueVerified = 0;
		clueFuse = 0;
		clueCount = 0;
	    } else {
		if (queues[QU_PICKUP].count != 0) clueFuse++;
	    }
	    if (!clueVerified) clue_check();
    }
    auto_features();
    updateClient();
}

#ifdef OBSERVERS
    /* observer is usually status POBSERV which is sort of like PDEAD */
    /* may get set to PDEAD or PEXPLODE by daemon events. deal with */
    /* those cases  reasonably gracefully */

static void check_observs(void)
{
    struct player *pl;
    struct planet *pln;

    if (!Observer) return;
    if (me->p_status != POBSERV) {
	if (me->p_status == PDEAD || me->p_status == PEXPLODE) {
            death ();
            return;
        }else{
	    if (me->p_status == PFREE){
		ERROR(1,("check_observs:  changing a PFREE!\n"));
	    }
            me->p_status = POBSERV;
	}
    }
    if (FD_ISSET (CP_SPEED, &inputMask)) {
        /* only let observer do a limited set of actions. */
        FD_ZERO (&inputMask);
        FD_SET (CP_MESSAGE, &inputMask);
        FD_SET (CP_QUIT, &inputMask);
        FD_SET (CP_LOGIN, &inputMask);
        FD_SET (CP_OUTFIT, &inputMask);
        FD_SET (CP_PLANLOCK, &inputMask);
        FD_SET (CP_PLAYLOCK, &inputMask);
        FD_SET (CP_SOCKET, &inputMask);
        FD_SET (CP_OPTIONS, &inputMask);
        FD_SET (CP_BYE, &inputMask);
        FD_SET (CP_UPDATES, &inputMask);
        FD_SET (CP_RESERVED, &inputMask);
        FD_SET (CP_UDP_REQ, &inputMask);
        FD_SET (CP_SEQUENCE, &inputMask);
#ifdef RSA
        FD_SET (CP_RSA_KEY, &inputMask);
#endif
#ifdef PING
        FD_SET (CP_PING_RESPONSE, &inputMask);
#endif
        FD_SET (CP_S_REQ, &inputMask);
        FD_SET (CP_S_THRS, &inputMask);
        FD_SET (CP_S_MESSAGE, &inputMask);
        FD_SET (CP_S_RESERVED, &inputMask);
        FD_SET (MAX_CP_PACKETS, &inputMask);
    }
    /* handle quitting  -- it just happens for the observer */
    if (me->p_flags & PFSELFDEST) {
        me->p_status = POUTFIT;
        me->p_whydead = KQUIT;
        death ();
        return;
    }

    me->p_flags |= PFOBSERV;  /* future clients may understand this */
    /* Check if I am locked onto a player */
    if (me->p_flags & PFPLOCK) {
        if ( (me->p_playerl < 0) || (me->p_playerl >= MAXPLAYER) ){
	    /* I am not locked onto a valid player */
            me->p_playerl = 0;         /* ensure a safe value */
	    me->p_flags &= ~(PFPLOCK); /* turn off player lock */
            me->p_x = -100000;         /* place me off the tactical */
            me->p_y = -100000;
	    return;
	}

	/* I am locked onto a valid player */
	pl = &players[me->p_playerl];
	if ( (pl->p_status != PALIVE) && (pl->p_status != PEXPLODE) && 
	     (pl->p_status != PDEAD) ) {
	  me->p_x = -100000;
	  me->p_y = -100000;
	  new_warning (99,"Player lock lost while player rejoins.");
	}
	else if ( (pl->p_team != me->p_team) && 
                  (pl->p_team != 0) && 
                  (me->p_team != 0)) {
	  /* allow locking on independent players (robots) */
	  new_warning (100,"Can only lock on own team.");
	  me->p_x = -100000;
	  me->p_y = -100000;
	  me->p_flags &= ~(PFPLOCK); /* turn off player lock */
	} else {
	  /* follow a particular player */
	  me->p_ship.s_type = pl->p_ship.s_type;
	  me->p_x = pl->p_x;
	  me->p_y = pl->p_y;
	  me->p_shield = pl->p_shield;
	  me->p_damage = pl->p_damage;
	  me->p_armies = pl->p_armies;
	  me->p_fuel = pl->p_fuel;
	  me->p_wtemp = pl->p_wtemp;
	  me->p_etemp = pl->p_etemp;
	  /* These flags shouldn't be propagated to observers */
#define NOOBSMASK	(PFSELFDEST|PFPLOCK|PFPLLOCK|PFOBSERV)
	  me->p_flags = (pl->p_flags & ~NOOBSMASK) | (me->p_flags & NOOBSMASK);
	  
	  /* always cloaked, state of person watched will be obvious */
	  /* me->p_flags |= PFCLOAK; */
	  me->p_dir = pl->p_dir;
	  me->p_tractor = pl->p_tractor;
	  me->p_docked = pl->p_docked;
	  me->p_planet = pl->p_planet;
	  me->p_speed = pl->p_speed;
	  /* me->p_kills = pl->p_kills;  Very bad: will confuse enemies */
	}
	return;
    }  /* end if I am locked onto a player */
    
    /* Check if I am locked onto a planet */
    if (me->p_flags & PFPLLOCK) {
        if ( (me->p_planet < 0) || (me->p_planet >= MAXPLANETS) ){
	    /* I am not locked onto a valid planet */
  	    me->p_planet = 0;            /* ensure a safe value */
	    me->p_flags &= ~(PFPLLOCK);  /* turn off planet lock */
	    me->p_x = -100000;           /* place me off the tactical */
	    me->p_y = -100000;
	    return;
        }

	/* I am locked onto a valid planet */
	pln = &planets[me->p_planet];
	if ((pln->pl_owner != me->p_team) && (me->p_team != 0)) {
	    if ( (me->p_x == pln->pl_x) && (me->p_y == pln->pl_y) )
	      /* we are sitting on the planet already, but it is not ours */
	      /* [some day we should fall back to nearest team ship] */
  	      new_warning(102,"Planet lock lost on change of ownership.");
	    else
	      new_warning(101,"You can only warp to your own team's planets!");
	    me->p_x = -100000;
	    me->p_y = -100000;
	    me->p_flags &= ~(PFPLLOCK); /* turn off planet lock */
	} else {
	    me->p_x = pln->pl_x;
	    me->p_y = pln->pl_y;
	}
	return;
    }   /* end if I am locked onto a planet */

    /* We are not locked onto anything */
    me->p_x = -100000;
    me->p_y = -100000;

    return;
}
#endif /* OBSERVERS */



/* These are routines that need to be done on interrupts but
   don't belong in the redraw routine and particularly don't
   belong in the daemon. */

static void auto_features(void)
{
    struct player *pl;
    struct planet *pln;
    u_char course;
    int troop_capacity=0;

#ifdef OBSERVERS
    check_observs();
    if (!living) return;
#endif

    if (me->p_flags & PFSELFDEST) {
	if ((me->p_updates >= selfdest) ||
	    ((me->p_flags & PFGREEN) && (me->p_damage == 0)
		&& (me->p_shield == me->p_ship.s_maxshield))) {
	    me->p_flags &= ~PFSELFDEST;
	    me->p_explode = 10;
	    me->p_whydead = KQUIT;
	    me->p_status = PEXPLODE;
	} else {
	    switch ((selfdest - me->p_updates)/10) {
	    case 5: case 4:
		new_warning(UNDEF,"You notice everyone on the bridge is staring at you.");
		break;
	    default:
		new_warning(UNDEF, "Stand By ... Self Destruct in %d seconds",
			(selfdest - me->p_updates) / 10);
		break;
	    }
	}
    }

    /* provide a refit countdown 4/6/92 TC */

    if (me->p_flags & PFREFITTING) {
	static int lastRefitValue = 0; /* for smooth display */
	if (lastRefitValue != (rdelay - me->p_updates)/10) {
	    lastRefitValue = (rdelay - me->p_updates)/10; /* CSE to the rescue? */
	    switch (lastRefitValue) {
	      case 3:
	      case 2:
		new_warning(UNDEF, "Engineering:  Energizing transporters in %d seconds", lastRefitValue);
		break;
	      case 1:
		new_warning(UNDEF, "Engineering:  Energize. [ SFX: chimes ]");
		break;
	      case 0:
		switch (random()%5) {
		  case 0:
		    new_warning(UNDEF,"Wait, you forgot your toothbrush!");
		    break;
		  case 1:
		    new_warning(UNDEF,"Nothing like turning in a used ship for a new one.");
		    break;
		  case 2:
		    new_warning(UNDEF,"First officer:  Oh no, not you again... we're doomed!");
		    break;
		  case 3:
		    new_warning(UNDEF,"First officer:  Uh, I'd better run diagnostics on the escape pods.");
		    break;
		  case 4:
		    new_warning(UNDEF,"Shipyard controller:  This time, *please* be more careful, okay?");
		    break;
		}
		break;
	    }
	}
    }

    /* provide a war declaration countdown 4/6/92 TC */

    if (me->p_flags & PFWAR) {
	static int lastWarValue = 0;
	if (lastWarValue != (delay - me->p_updates)/10) {
	    lastWarValue = (delay - me->p_updates)/10; /* CSE to the rescue? */
	    switch (lastWarValue) {
	      case 9:
		new_warning(UNDEF,"Weapons officer:  Not again!  This is absurd...");
		break;
	      case 8:
		break;
	      case 7:
		new_warning(UNDEF,"Weapons officer:  ... the whole ship's computer is down?");
		break;
	      case 6:
		break;
	      case 5:
		new_warning(UNDEF,"Weapons officer:  Just to twiddle a few bits of the ship's memory?");
		break;
	      case 4:
		break;
	      case 3:
		new_warning(UNDEF,"Weapons officer:  Bah! [ bangs fist on inoperative console ]");
		break;
	      case 2:
		break;
	      case 1:
		new_warning(UNDEF,"First Officer:  Easy, big guy... it's just one of those mysterious");
		break;
	      case 0:
		switch (random()%5) {
		  case 0:
		    new_warning(UNDEF,"First Officer:  laws of the universe, like 'tires on the ether'.");
		    break;
		  case 1:
		    new_warning(UNDEF,"First Officer:  laws of the universe, like 'Klingon bitmaps are ugly'.");
		    break;
		  case 2:
		    new_warning(UNDEF,"First Officer:  laws of the universe, like 'all admirals have scummed'.");
		    break;
		  case 3:
		    new_warning(UNDEF,"First Officer:  laws of the universe, like 'Mucus Pig exists'.");
		    break;
		  case 4:
		    new_warning(UNDEF,"First Officer:  laws of the universe, like 'guests advance 5x faster'.");
		    break;
		}
		break;
	    }
	}
    }

    /* give certain information about bombing or beaming */
    if (me->p_flags & PFBOMB) {
	if (planets[me->p_planet].pl_armies < 5) {
	    new_warning(UNDEF, "Weapons Officer: Bombarding %s... Ineffective, %d armies left.",
			planets[me->p_planet].pl_name,    /* Planet name for stats 10/20/96 [007] */
		    planets[me->p_planet].pl_armies); /* nifty info feature 2/14/92 TMC */
	    me->p_flags &= ~PFBOMB;
	}
	else {
	    new_warning(UNDEF, "Weapons Officer: Bombarding %s...  Sensors read %d armies left.",
		planets[me->p_planet].pl_name,
		planets[me->p_planet].pl_armies);
	}
    }

#ifdef OBSERVERS
    /* Use person observed for kills if we are an observer */
    if(Observer && (me->p_flags&PFPLOCK)) pl = &players[me->p_playerl];
    else
#endif
    pl = me;	/* Not observer, just use my kills */

    troop_capacity = pl->p_kills * (myship->s_type == ASSAULT?3:2);
    if (myship->s_type == STARBASE || troop_capacity > myship->s_maxarmies)
    	troop_capacity = myship->s_maxarmies;

    if (me->p_flags & PFBEAMUP) {
        /* Messages rewritten for stats 10/20/96 [007] */
        char *txt;
	if (me->p_flags & PFORBIT) {
	  if (planets[me->p_planet].pl_armies < 5) {
	    txt = "Too few armies to beam up";
	    me->p_flags &= ~PFBEAMUP;
	  } else if (me->p_armies == troop_capacity) {
	    txt = "No more room on board for armies";
	    me->p_flags &= ~PFBEAMUP;
	  } else {
	    txt = "Beaming up";
	  }
	  new_warning(UNDEF, "%s: (%d/%d) %s has %d armies left",
		      txt,
		      me->p_armies,
		      troop_capacity,
		      planets[me->p_planet].pl_name,	
		      planets[me->p_planet].pl_armies);
	    
	} else if (me->p_flags & PFDOCK) {
	    if (players[me->p_docked].p_armies == 0) {
		txt = "Too few armies to beam up";
		me->p_flags &= ~PFBEAMUP;
	    } else if (me->p_armies == troop_capacity) {
		txt = "No more room on board for armies";
		me->p_flags &= ~PFBEAMUP;
	    } else {
	        txt = "Beaming up";
	    }
	    new_warning(UNDEF, "%s: (%d/%d) Starbase %s has %d armies left",
			txt,
			me->p_armies,
			troop_capacity,
			players[me->p_docked].p_name,
			players[me->p_docked].p_armies);
	}
    }

    if (me->p_flags & PFBEAMDOWN) {
        /* Messages rewritten for stats 10/20/96 [007] */
        char *txt;
	if (me->p_flags & PFORBIT) {
	  if (me->p_armies == 0) {
	    txt = "No more armies to beam down";
	    me->p_flags &= ~PFBEAMDOWN;
	  } else {
	    txt = "Beaming down";
	  }
	  new_warning(UNDEF, "%s: (%d/%d) %s has %d armies left",
		      txt,
		      me->p_armies,
		      troop_capacity,
		      planets[me->p_planet].pl_name,	
		      planets[me->p_planet].pl_armies);

	} else if (me->p_flags & PFDOCK) {
	    if (me->p_armies == 0) {
		txt = "No more armies to beam down";
		me->p_flags &= ~PFBEAMDOWN;
	    } else if (players[me->p_docked].p_armies ==
		players[me->p_docked].p_ship.s_maxarmies) {
	      txt = "All troop bunkers are full";
	      me->p_flags &= ~PFBEAMDOWN;
	    } else {
	        txt = "Transfering ground units";
	    }
	    new_warning(UNDEF, "%s: (%d/%d) Starbase %s has %d armies left",
			txt,
			me->p_armies,
			troop_capacity,
			players[me->p_docked].p_name,
			players[me->p_docked].p_armies);
	}
    }

    if (me->p_flags & PFREPAIR) {
	if ((me->p_damage == 0) && (me->p_shield == me->p_ship.s_maxshield))
	    me->p_flags &= ~PFREPAIR;
    }
    if ((me->p_flags & PFPLOCK) 
#ifdef OBSERVERS
		&& (!Observer)
#endif
	) {/* set course to player x */
	int dist;

	pl = &players[me->p_playerl];
	if (pl->p_status != PALIVE)
	    me->p_flags &= ~PFPLOCK;
#ifdef SB_TRANSWARP
	if (me->p_flags & PFTWARP){
	  if (pl->p_status != PALIVE){
	     new_warning(UNDEF, "Starbase is dead; Transwarp aborted!", -1);
	     me->p_flags &= ~PFTWARP;
	     me->p_speed = 3;
	     set_speed(0);
	  }
	  if (pl->p_ship.s_type != STARBASE){
	     new_warning(UNDEF, "Starbase has been turned in; Transwarp aborted!", -1);
	     me->p_flags &= ~PFTWARP;
	     me->p_speed = 3;
	     set_speed(0);
	  }
	  if(me->p_etemp > me->p_ship.s_maxegntemp - 10){
	     new_warning(UNDEF,"Warp drive reaching critical temperature!", -1);
	     me->p_flags &= ~PFTWARP;
	     me->p_speed = 3;
	     set_speed(0);
	  }
 	  else
	  if (me->p_fuel < (int) (me->p_ship.s_maxfuel/8)){
	     new_warning(UNDEF, "Not enough fuel to continue transwarp!", -1);
	     me->p_flags &= ~PFTWARP;
	     me->p_speed = 3;
	     set_speed(0);
	  }
	  else if(!(me->p_flags & PFPLOCK)){
	     new_warning(UNDEF, "Lost player lock!", -1);
	     me->p_flags &= ~PFTWARP;
	     me->p_speed = 3;
	     set_speed(0);
	  }
	}
#endif
	if (pl->p_ship.s_type == STARBASE) {
	    dist = hypot((double) (me->p_x - pl->p_x),
		(double) (me->p_y - pl->p_y));

#if defined(SB_TRANSWARP) && defined(SB_CALVINWARP)
            if (!(me->p_flags & PFTWARP))
#endif
	    if (dist-(DOCKDIST/2) < (11500 * me->p_speed * me->p_speed) /
		    me->p_ship.s_decint) {
		if (me->p_desspeed > 2) {
		    set_speed(me->p_desspeed-1);
		}
	    }
#ifdef SB_TRANSWARP
	if ((dist < 2*DOCKDIST) && (me->p_flags & PFTWARP)){
	    me->p_x = pl->p_x; me->p_y = pl->p_y;
	    me->p_flags &= ~(PFPLOCK);
	    me->p_flags &= ~(PFTWARP);
#ifdef SB_CALVINWARP
            if (!(pl->p_flags & PFDOCKOK) ||
                ((pl->p_flags & PFPRESS) &&
                 (pl->p_tractor == me->p_no))) ;
            else {
#endif

	    me->p_speed=2;
	    orbit();
#ifdef SB_CALVINWARP
            }
#endif
	} else
#endif
	    if ((dist < DOCKDIST) && (me->p_speed <= 2))  {
		me->p_flags &= ~PFPLOCK;
		orbit();
	    }
	}
	if (me->p_flags & PFPLOCK) {
	    course = newcourse(pl->p_x, pl->p_y);
#ifdef SB_TRANSWARP
	    if (me->p_flags & PFTWARP)
		me->p_dir = course;
	    else
#endif
	    set_course(course);
	}
    }
    if ((me->p_flags & PFPLLOCK) 
#ifdef OBSERVERS
		&& (!Observer)
#endif
	) {/* set course to planet x */
	int dist;

	pln = &planets[me->p_planet];
	dist = hypot((double) (me->p_x - pln->pl_x),
	    (double) (me->p_y - pln->pl_y));

	/* This is magic.  It should be based on defines, but it slows
	   the ship down to warp two an appropriate distance from the
	   planet for orbit */

	if (dist-(ORBDIST/2) < (11500 * me->p_speed * me->p_speed) /
	        me->p_ship.s_decint) {
	    if (me->p_desspeed > 2) {
		set_speed(2);
	    }
	}

	if ((dist < ENTORBDIST) && (me->p_speed <= 2))  {
	    me->p_flags &= ~PFPLLOCK;
	    orbit();
	}
	else {
	    int ax, ay, ad, missing;
	    extern int nowobble;

	    /* calculate course to planet from current coordinates */
	    course = newcourse(pln->pl_x, pln->pl_y);

            /* avoid superfluous midflight wobble */
	    if (nowobble) {
	      /* test case; at 6 o'clock on earth, lock on altair, warp 8 */
	      /* calculate arrival point at current course */
	      ax = (double) (me->p_x + Cos[me->p_desdir] * dist);
	      ay = (double) (me->p_y + Sin[me->p_desdir] * dist);
	      ad = hypot((double) (ax - pln->pl_x),
			 (double) (ay - pln->pl_y));
	      
	      /* change to the corrected course if the expected error
		 exceeds the remaining distance divided by the nowobble
		 factor (25 works well) */
	      missing = (ad > dist / nowobble);
	      if (missing)
		set_course(course);
	    } else {
	      /* classical behaviour */
	      if ( (ABS(course-me->p_desdir) > 2) || (dist < ENTORBDIST*10) )
		set_course(course);
	    }
	}
    }
}

static u_char newcourse(int x, int y)
{
	return((u_char) nint(atan2((double) (x - me->p_x),
	    (double) (me->p_y - y)) / 3.14159 * 128.));
}

/*

However, I have added a tail wag fix to continuum.  Send 'nowobble 25' to
yourself to enable it.  Send 'nowobble 0' to disable it.  The change only
affects your heading while you are locked on a planet.

What it does is avoid turning your ship to the exact heading if it can be
proven that you'll get into orbit anyway at the heading you are on.  It
still does some correction, but it doesn't oscillate back and forth
between the two suboptimal courses.

The test case, if you want to see the tail wag, is to stop your orbit at
the six o'clock position at Earth, lock on Altair, and then set a speed.

The cause of the problem is trigonometry on integer math.  The coordinate
system of Netrek is integer based, for efficiency of calculation and
information transmission.  Ships have an (x,y) coordinate, and a course.
The course is in 256ths of a full circle, about 1.4 degrees between each
possible course.

From one end of the galactic to another, 1.4 degrees of error is about
one and a half planet widths.  So we must be able to turn your ship as
you get closer.

Often, the best course is between two of the integer courses.  No matter
what course is chosen, a correction is needed by swapping to the other
course.

What the change does is (a) calculate the arrival point at the current
integer course you have, (b) calculate the distance from the arrival
point to the planet's centre, (c) changes ship heading only if the
expected arrival error exceeds the remaining distance divided by a
'nowobble factor'.

Through trial and error, I've found 25 works well.  Set it to a
pathalogically low value like 1 and you take a circuitous route to the
planet, which is quite funny.  Set it to 10,000,000 and you wobble like
you always did.  Set it to 0 to turn it off.

*/
