/*
    Amiga port by Oliver Gantert

    27.04.2000 - fixed some compiler warnings, but many errors left
    28.04.2000 - commented out some functions, no networking on Amiga yet
*/
/* 
 
ORBIT, a freeware space combat simulator 
Copyright (C) 1999  Steve Belczyk <steve1@genesis.nred.ma.us> 
 
This program is free software; you can redistribute it and/or 
modify it under the terms of the GNU General Public License 
as published by the Free Software Foundation; either version 2 
of the License, or (at your option) any later version. 
 
This program is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
GNU General Public License for more details. 
 
You should have received a copy of the GNU General Public License 
along with this program; if not, write to the Free Software 
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 
*/ 

#include "orbit.h"

#ifndef AMIGA
#ifndef WIN32
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#endif /* WIN32 */
#include <fcntl.h>
#endif /* AMIGA */

#include <errno.h>
#include <limits.h>
#include <stdarg.h>

/*
 *  All sorts of stuff for network communication
 */

int endian; /* Endian for transmitting network values */

void InitNetwork()
/*
 *  Initialize network data structures
 */
{
  int c;

  Log ("InitNetwork: Initializing network");

  am_server = 0;
  am_client = 0;

  recv_bytes = xmit_bytes = 0;

  for (c=0; c<NCLIENTS; c++)
  {
    client[c].active = 0;
    client[c].is_me = 0;
    client[c].ping = 0.0;
    client[c].target = (-1);
    client[c].frags = 0;
    client[c].ip[0] = 0;

    client[c].timer.idle = 0.0;
    client[c].timer.ping = 0.0;

    client[c].state = NETSTATE_MAGIC;
  }
}

int OpenNetwork()
/*
 *  Really only need this for Winsock
 */
{
  #ifdef WIN32
  WSADATA wsadata;
  #endif

  Log ("OpenNetwork: Opening network");

  #ifdef WIN32
  if (SOCKET_ERROR == WSAStartup (0x202, &wsadata))
  {
    Log ("OpenNetwork: WSAStartup failed with error %d",
    WSAGetLastError());
    WSACleanup();
    return 0;
  }
  #endif

  return 1;
}

void CloseNetwork()
/*
 *  Close Winsock
 */
{
  Log ("CloseNetwork: Closing network");

  #ifdef WIN32
  WSACleanup();
  #endif
}

int BecomeServer()
/*
 *  Become an ORBIT server
 */
{
  #ifndef AMIGA
  struct sockaddr_in address;
  SOCKET listening_socket;
  int one = 1;
  int b, t, c;

  Cprint ("Becoming ORBIT server...");
  Log ("BecomeServer: Becoming ORBIT server...");

  /* Determine how to send floats */
  if (!FindEndian()) return 0;

  /* Open Winsock */
  if (!OpenNetwork()) return 0;

  /* Set address and port to listen on */
  memset ((char *) &address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons (server.port);
  address.sin_addr.s_addr = htonl (INADDR_ANY);

  /* Create the socket.  This shouldn't cause any network activity;
    it's just creating a kernel data structure */
  Log ("BecomeServer: creating listening socket");
  listening_socket = socket (AF_INET, SOCK_STREAM, 0);
  if (listening_socket < 0)
  {
    Log ("BecomeServer: socket returned %d", listening_socket);
    CloseNetwork();
    return 0;
  }

  /* Remember listening socket so we can close it later */
  server.listening_socket = listening_socket;

  /* Set the listening socket option SO_REUSEADDR. */
  setsockopt (listening_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
  sizeof(one));

  /* Bind the listening socket to the address information */
  b = bind (listening_socket, (struct sockaddr *) &address, sizeof(address));
  if (b < 0)
  {
    Log ("BecomeServer: bind() returned %d", b);
    #ifndef WIN32
    close (listening_socket);
    #else
    closesocket (listening_socket);
    #endif
    CloseNetwork();
    return 0;
  }

  /* Set number of connections to queue */
  listen (listening_socket, 3);

  /* Put the socket in non-blocking mode so we can poll it
    (This should really be done by getting the flags and setting
    the one bit) */
  #ifdef WIN32
  ioctlsocket (listening_socket, FIONBIO, &one);
  #else
  fcntl (listening_socket, F_SETFL, O_NONBLOCK);
  #endif

  /* Remember I'm a server */
  am_server = 1; 
  
  /* Save IP address? */ 
  strcpy (server.ip, inet_ntoa(address.sin_addr));

  Cprint ("Now an ORBIT server, listening on port %d", server.port);
  Log ("BecomeServer: Successfully became server");

  /* Reset targets, events, weapons and planets, etc; */
  InitTargets();
  ResetEvents();
  InitWeapons();
  ResetPlanets();
  InitWaypoints();
  ResetModels();
  lock.target = (-1); 
  compression = 1.0;

  /* Network players are always vulnerable */
  vulnerable = 1;

  /* Set up a client and target for me */
  c = FindClient();
  client[c].active = 1;
  client[c].is_me = 1;
  server.client = c;
  t = client[c].target = InitClientTarget();
  strcpy (target[t].name, player.name);
  target[t].hidden = target[t].invisible = 1;

  return 1;
  #else
  return 0;
  #endif /* AMIGA */
}

int BecomeClient (char *s)
/*
 *  Try to become ORBIT client
 */
{
  #ifndef AMIGA
  /* struct in_addr *addr; */
  SOCKET sock;
  int c, port;
  struct sockaddr_in address;
  char ip[128];

  #ifdef WIN32
  unsigned long one = 1;
  #else
  int one = 1;
  #endif

  Log ("BecomeClient: Trying to become client");

  /* Determine how to send floats */
  if (!FindEndian()) return 0;

  /* Parse address as ip[ port] */
  port = (-1);
  sscanf (s, "%s %d", ip, &port);

  /* Init Winsock */
  if (!OpenNetwork()) return 0;

  /* Set up address, port of server */
  address.sin_family = AF_INET;
  address.sin_port = htons ((port < 0) ? ORBIT_PORT : port);
  address.sin_addr.s_addr = inet_addr (ip);

  /* Create the socket */
  sock = socket (AF_INET, SOCK_STREAM, 0);
  if (sock < 0)
  {
    Log ("BecomeClient: socket error: %d", errno);
    CloseNetwork();
    return 0;
  }

  /* Connect to the server */
  Log ("BecomeClient: Connecting to server %s on port %d...",
  ip, (port < 0 ? ORBIT_PORT : port));
  Cprint ("Connecting to server %s on port %d...",
  ip, (port < 0 ? ORBIT_PORT : port));
  c = connect (sock, (struct sockaddr *) &address, sizeof(address));

  /* Did it work? */
  if (c < 0)
  {
    /* No */
    Mprint ("Could not connect to server");
    Log ("BecomeClient: connect() error: %d", errno);
    CloseNetwork();
    return 0;
  }

  /* Yes!  We are connected */
  Mprint ("Connected to server");
  Log ("BecomeClient: Connected to server");

  /* Declare I am a client */
  am_client = 1;

  /* Remember this socket */
  clientme.socket = sock;

  /* Make the socket non-blocking */
  #ifdef WIN32
  ioctlsocket (clientme.socket, FIONBIO, &one);
  #else
  fcntl (clientme.socket, F_SETFL, O_NONBLOCK);
  #endif

  /* Disable the Nagle algorithm */
  setsockopt (clientme.socket, IPPROTO_TCP, TCP_NODELAY, (char *) &one,
  sizeof(one));

  /* Init incoming packet buffer */
  clientme.pkt[0] = 0;
  clientme.ptr = 0;

  /* Init timers */
  clientme.timer.pos = 0.0;
  clientme.timer.server = 0.0;

  /* Init packet state */
  clientme.state = NETSTATE_MAGIC;

  /* Send our name and model */
  SendASCIIPacket (clientme.socket, "NAME %s\n", player.name);
  SendASCIIPacket (clientme.socket, "MODL %s\n", player.model);
  SendASCIIPacket (clientme.socket, "VERS %s\n", VERSION);

  /* Report our position */
  ReportPosition();

  /* Network players are always vulnerable */
  vulnerable = 1;

  /* Reset targets, events, weapons and planets, etc; */
  InitTargets();
  ResetEvents();
  InitWeapons();
  ResetPlanets();
  InitWaypoints();
  ResetModels();
  lock.target = (-1); 
  compression = 1.0;

  return 1;
  #else
  return 0;
  #endif /* AMIGA */
}

void DoNetwork()
/*
 *  Process network activity
 */
{
  /* Are we client or server (or neither?) */
  if (am_server) DoServer();
  if (am_client) DoClient();
}

void SendASCIIPacket (SOCKET socket, char *fmt, ...)
/*
 *  Write an ASCII packet to given network socket
 */
{
  #ifndef AMIGA
  va_list ap;
  char buf[1024];
  int len;
  #ifndef BINARYPACKETS
  int e;
  #endif

  /* Start up varargs stuff */
  va_start (ap, fmt);

  /* Get packet into string */
  vsprintf (buf, fmt, ap);

  /* Wrap up varargs */
  va_end (ap);

  /* Add trailing null */
  buf[strlen(buf)] = 0;

  /* Send it */
  len = 1 + strlen (buf);
  #ifdef BINARYPACKETS
  XmitBinaryPacket (socket, buf, len);
  #else
  e = send (socket, buf, len, 0);
  #endif
  xmit_bytes += len;
  #endif /* AMIGA */
}

void XmitBinaryPacket (SOCKET socket, char *buf, int len)
{
  #ifndef AMIGA
  char pkt[512];
  int i;

  /* Length check */
  if (len > 255)
  {
    Cprint ("PANIC: Packet too large: %d", len);
    return;
  }

  /* Put in magic byte */
  pkt[0] = NET_MAGIC;

  /* Put in size */
  pkt[1] = len;

  /* Put in data */
  for (i=0; i<len; i++) pkt[2+i] = buf[i];

  /* Send it! */
  send (socket, pkt, len+2, 0);
  #endif /* AMIGA */
}

void ShutdownClient()
/*
 *  Undo being a client
 */
{
  #ifndef AMIGA
  if (!am_client) return;

  Log ("ShutdownClient: Shutting down network client");

  /* Close the socket to the server */
  #ifndef WIN32
  close (clientme.socket);
  #else
  closesocket (clientme.socket);
  #endif

  /* Shut down Winsock */
  CloseNetwork();

  /* Show no longer client */
  am_client = 0;

  /* Tell Sparky */
  Mprint ("Disconnected from server");

  /* Reset network data structures */
  InitNetwork();
  InitTargets();
  #endif /* AMIGA */
}

void ShutdownServer()
/*
 *  Stop being a server
 */
{
  #ifndef AMIGA
  int c;

  if (!am_server) return;

  Log ("ShutdownServer: Shutting down network server");

  /* Close each active client connection */
  for (c=0; c<NCLIENTS; c++)
  {
    if (client[c].active && (c != server.client))
    {
      Log ("ShutdownServer: Closing client %d", c);
      DropClient (c);
    }
  }

  /* Stop listening for clients */
  #ifndef WIN32
  close (server.listening_socket);
  #else
  closesocket (server.listening_socket);
  #endif

  /* Shut down Winsock */
  CloseNetwork();

  /* No longer server */
  am_server = 0;

  /* Hey Sparky */
  Mprint ("No longer a server");

  /* Reset network data structures */
  InitNetwork();
  #endif /* AMIGA */
}

void ShutdownNetwork()
/*
 *  Shut down all network connections and services
 */
{
  Log ("ShutdownNetwork: Shutting down network");

  if (am_server) ShutdownServer();
  if (am_client) ShutdownClient();
}

void DropClient (int c)
/*
 *  Hang up on client c
 */
{
  #ifndef AMIGA
  int cc;
  #ifndef WIN32
  close (client[c].socket);
  #else
  closesocket (client[c].socket);
  #endif

  client[c].active = 0;

  /* Notify us */
  Cprint ("%s is gone", target[client[c].target].name);

  /* Notify everyone */
  for (cc=0; cc<NCLIENTS; cc++)
  {
    if (client[cc].active && (cc != server.client) && (c != cc) )
    {
      SendASCIIPacket (client[cc].socket, "GBYE %d %s",
      c, target[client[c].target].name);
    }
  }

  /* Destroy associated target */
  DestroyTarget (client[c].target);
  #endif /* AMIGA */
}

void QueuePositionReport()
/*
 *  Force our position to be sent
 */
{
  int c;

  if (am_client)
  {
    clientme.timer.pos = CLIENTPOSINTERVAL;
  }
  else if (am_server)
  {
    for (c=0; c<NCLIENTS; c++)
    {
      client[c].timer.posn[server.client] = client[c].posninterval;
    }
  }
}

int InitClientTarget()
/*
 *  Set up a target for a client
 */
{
  int m, t;

  t = FindTarget();
  target[t].age = 0.1;
  strcpy (target[t].name, "Sparky");
  target[t].invisible = 0;
  target[t].hidden = 0;
  target[t].model = m = LoadModel ("light2.tri");
  target[t].list = model[m].list;
  target[t].shieldregen = SHIELD_REGEN;

  return t;
}

void NetHitTarget (int t, int m)
/*
 *  Target t was hit by missile m
 */
{
  int c;

  if (!am_server) return;

  /* Find the client that belongs to this target */
  c = FindClientByTarget (t);

  /* Don't bother if it's me */
  if (c == server.client) return;

  /* Send them the packet */
  if (client[c].active)
  {
    SendASCIIPacket (client[c].socket,
    "MHIT %lf", weapon[msl[m].weapon].yield);
 }
}

void NetDestroyTarget (int t, int m)
/*
 *  Target t was destroyed by missile m
 */
{
 int cvictim, ckiller;

 if (!am_server) return;

 /* Find the client that belongs to the victim */
 cvictim = FindClientByTarget (t);

 /* Find the client that belongs to the killer */
 ckiller = FindClientByTarget (msl[m].owner);

 /* Tell everyone */
 NetDestroyClient (cvictim, ckiller);
}

void NetDestroyClient (int cv, int ck)
/*
 *  Client cv was killed by client ck
 */
{
 int c;

 /* Adjust frags */
 client[ck].frags++;

 /* Tell everyone */
 for (c=0; c<NCLIENTS; c++)
 {
  if (client[c].active && (c != server.client) )
  {
   SendASCIIPacket (client[c].socket, "MDIE %d %d", cv, ck);
  }
 }

 /* Tell me */
 Cprint ("%s was killed by %s",
  target[client[cv].target].name,
  target[client[ck].target].name);

 /* Make client's target hidden */
 if (cv != server.client) target[client[cv].target].hidden = 1;

 CheckLock();
}

void NetTargetCratered (int t, int p)
/*
 *  Target t cratered on planet p
 */
{
 int c, cc;

 /* Get client */
 cc = FindClientByTarget (t);

 /* Adjust frags */
 client[cc].frags--;

 /* Tell everyone */
 for (c=0; c<NCLIENTS; c++)
 {
  if (client[c].active && (c != server.client) )
  {
   SendASCIIPacket (client[c].socket, "CRAT %d %d", cc, p);
  }
 }

 /* Tell me */
 Cprint ("%s cratered on %s", target[t].name, planet[p].name);

 /* Hide target */
 if (cc != server.client) target[t].hidden = 1;

 CheckLock();
} 

int FindClientByTarget (int t)
/*
 *  Figure out what client belongs to this target
 */
{
 int c;

 /* Is it me? */
 if (t == (-1)) return server.client;

 /* Loop through clients */
 for (c=0; c<NCLIENTS; c++)
 {
  if (client[c].active && (t==client[c].target) ) return c;
 }

 /* When in doubt, make it me */
 return server.client;
}

void NetClientFires (int clnt, int wep)
/*
 *  Client clnt has fired weapon wep
 */
{
 double v[3], d;
 int c, t1, t2;

 if (!am_server) return;

 t1 = client[clnt].target;

 /* Loop through clients */
 for (c=0; c<NCLIENTS; c++)
 {
  if (client[c].active && (c != clnt) && (c != server.client))
  {
   t2 = client[c].target;

   /* Find distance between clients */
   Vsub (v, target[t1].pos, target[t2].pos);
   d = Mag2 (v);

   /* Don't bother if too far */
   if (d > TARG_MAXRANGE2) continue;

   /* Send packet */
   SendASCIIPacket (client[c].socket,
    "FIRE %d %d", clnt, wep);
  }
 }
}

void NetPlayerDies()
/*
 *  We died in a network game
 */
{
 double v[3];
 int i;

 /* Change game state */
 state = STATE_DEAD1;

 /* Set up timer */
 player.dead_timer = DEAD_TIME;

 /* Stop Sparky */
 player.vel[0] = player.vel[1] = player.vel[2] = 0.0;

 /* Assign new position */
 do
 {
  for (i=0; i<3; i++) v[i] = player.pos[i] +
   rnd(1000000.0/KM_TO_UNITS1) - 500000.0/KM_TO_UNITS1;
 } while (!RespawnOkay(v));
 Vset (player.pos, v);

 /* Give Zorkian message */
 Mprint ("*** You have died ***");
} 
 
int RespawnOkay (double *v)
/* 
 *  Check if respawn location is okay 
 */ 
{ 
 int p; 
 double v1[3], d; 
 
 for (p=0; p<NPLANETS; p++) 
 { 
  if (!planet[p].hidden) 
  { 
   /* Compute range to planet */ 
   Vsub (v1, planet[p].pos, v); 
   d = Mag (v1); 
 
   /* Too close? */ 
   if (d < 5.0 * planet[p].radius) return 0; 
  } 
 } 
 
 /* Okay if we got here */ 
 return 1; 
}

int FindEndian()
/*
 *  Try to figure out the endianness of this machine, especially with
 *  regard to floats.  Kinda kludgy but it's been working great.
 */
{
 union
 {
  unsigned char c[4];
  float f;
 } u;

 /* Assume one way */
 endian = 0;

 /* Better have 4-byte floats or we're sunk */
 if (4 != sizeof(float))
 {
  Mprint ("PANIC! This machine doesn't use 4-byte floats");
  Log ("FindEndian: PANIC! This machine doesn't use 4-byte floats");
  return 0;
 }

 /* Set a float and pick apart the bytes to see if we
    recognize the order */
 u.f = 3141.59;

 /* Test one way */
 if ( (u.c[0] == 0x45) && (u.c[1] == 0x44) &&
      (u.c[2] == 0x59) && (u.c[3] == 0x71) )
 {
  /* Yay! It's MIPS, SPARC, RS/6000, HP or something
     like that */
  endian = 0;
  return 1;
 }

 /* Hmm, test other way */
 if ( (u.c[0] == 0x71) && (u.c[1] == 0x59) &&
      (u.c[2] == 0x44) && (u.c[3] == 0x45) )
 {
  /* Yay! Intel or DEC or something like that */
  endian = 1;
  return 1;
 }

 /* Bad news, Sparky! */
 Mprint ("Unrecognized floating point format! No network for you!");
 Log ("FindEndian: Unrecognized floating point format");
 
 return 0;
}

void EncFloat (double d, unsigned char *c)
/*
 *  Encode a double
 */
{
 union
 {
  unsigned char c[4];
  float f;
 } u;

 /* Convert to float */
 u.f = (float) d;

 /* Write bytes */
 if (endian)
 {
  c[0] = u.c[0];
  c[1] = u.c[1];
  c[2] = u.c[2];
  c[3] = u.c[3];
 }
 else
 {
  c[0] = u.c[3];
  c[1] = u.c[2];
  c[2] = u.c[1];
  c[3] = u.c[0];
 }
}

double DecFloat (unsigned char *c)
/*
 *  Decode a float
 */
{
 union
 {
  unsigned char c[4];
  float f;
 } u;

 /* Move into union */
 if (endian)
 {
  u.c[0] = c[0];
  u.c[1] = c[1];
  u.c[2] = c[2];
  u.c[3] = c[3];
 }
 else
 {
  u.c[3] = c[0];
  u.c[2] = c[1];
  u.c[1] = c[2];
  u.c[0] = c[3];
 }

 return (double) u.f;
}

void EncVector (double *v, char *c)
/*
 *  Encode a whole vector
 */
{
 EncFloat (v[0], &c[0]);
 EncFloat (v[1], &c[4]);
 EncFloat (v[2], &c[8]);
}

void EncUnitVector (double *v, char *c)
/*
 *  Encode vector of unit floats
 */
{
 c[0] = EncUnitFloat (v[0]);
 c[1] = EncUnitFloat (v[1]);
 c[2] = EncUnitFloat (v[2]);
}

void DecVector (double *v, unsigned char *c)
/*
 *  Decode a whole vector
 */
{
 v[0] = DecFloat (&c[0]);
 v[1] = DecFloat (&c[4]);
 v[2] = DecFloat (&c[8]);
}

int EncUnitFloat (double d)
/*
 *  Convert double on [-1,1] to integer on [0,255]
 */
{
 int i;

 if (d < -1.0) d = -1.0;
 if (d >  1.0) d =  1.0;

 i = (int) ((d + 1.0) * 127.5);

 return i;
}

double DecUnitFloat (unsigned char c)
/*
 *  Convert integer on [0,255] to double on [-1,1]
 */
{
 double d;
 int i;

 i = 0xff & c;
 d = (((double) i) / 127.5) - 1.0;

 return d;
}

void DecUnitVector (double *v, unsigned char *c)
{
 v[0] = DecUnitFloat (c[0]);
 v[1] = DecUnitFloat (c[1]);
 v[2] = DecUnitFloat (c[2]);
}

void SendBinaryPacket (SOCKET socket, char *fmt, ...)
/*
 *  Send a binary packet
 */
{
 int i, p, len;
 unsigned char buf[256];
 va_list ap;

 /* Start up varargs */
 va_start (ap, fmt);

 p = 0;
 len = strlen (fmt);

 /* Loop through format */
 for (i=0; i<len; i++)
 {
  switch (fmt[i])
  {
  case 'c': /* Single character */
   buf[p++] = va_arg (ap, int);
   break;

  case 'F': /* Normal float */
   EncFloat (va_arg (ap, double), &buf[p]);
   p += 4;
   break;

  case 'f': /* Unit float */
   buf[p++] = EncUnitFloat (va_arg (ap, double));
   break;

  case 'V': /* Normal vector */
   EncVector (va_arg (ap, double *), &buf[p]);
   p += 12;
   break;

  case 'v': /* Unit vector */
   EncUnitVector (va_arg (ap, double *), &buf[p]);
   p += 3;
   break;

  default:
   Log ("SendBinaryPacket: Bad format character: %c", fmt[i]);
   break;
  }
 }

 va_end (ap);

 /* Send it off! */
 XmitBinaryPacket (socket, buf, p);
 xmit_bytes += 2 + p;
}

void DecodeBinaryPacket (char *pkt, char *fmt, ...)
/*
 *  Decode a binary packet
 */
{
 int i, p, len;
 va_list ap;
 double *d;

 /* Start up varargs */
 va_start (ap, fmt);

 len = strlen (fmt);
 p = 0;

 /* Loop through format */
 for (i=0; i<len; i++)
 {
  switch (fmt[i])
  {
  case 'c': /* Single character */
   *(va_arg (ap, int *)) = pkt[p];
   p++;
   break;

  case 'F': /* Normal float */
   *(va_arg (ap, double *)) = DecFloat (&pkt[p]);
   p += 4;
   break;

  case 'f': /* Unit float */
   *(va_arg (ap, double *)) = DecUnitFloat (pkt[p]);
   p++;
   break;

  case 'V': /* Normal vector */
   d = va_arg (ap, double *);
   DecVector (d, &pkt[p]);
   p += 12;
   break;

  case 'v': /* Unit vector */
   d = va_arg (ap, double *);
   DecUnitVector (d, &pkt[p]);
   p += 3;
   break;

  default:
   Log ("DecBinaryPacket: Bad format character: %c", fmt[i]);
   break;
  }
 }

 va_end (ap);
}
