/* 
   Name: Wurmz!
   Author: Patrys (R) / MACROSoft RUSSiA / www.wurmz.prv.pl
   Description: an advanced Liero clone
   Copyright: (c) 2002-2003 MACROSoft RUSSiA. All rights reserved.
*/

//include <libnet.h>

#include "allegro.h"

#include "../wurmz_private.h"

char VERSION[16] = VER_STRING;

#ifdef _WIN32
#include "winalleg.h"
#include <winsock.h>
#else
#include <sys/socket.h>
#include <string.h>
#endif

#include "client.h"
#include "defs.h"
#include "console.h"
#include "ai.h"
#include "ai_waypt.h"
#include "engine.h"
#include "gfx.h"
#include "init.h"
#include "input.h"
#include "items.h"
#include "level.h"
#include "loadpng.h"
#include "maths.h"
#include "netio.h"
#include "network.h"
#include "packet.h"
#include "player.h"
#include "render.h"
#include "rules.h"
#include "server.h"
#include "sound.h"
#include "weapons.h"
#include "ai_lua.h"

#include "demos.h"

char fpsLimit;
int fullScreen;

#include "defs2.h"

int numViewports;
gameViewport *viewport;

int lineNumColors;
int lineColors[16];
int lineColor, lineHook, ropeSound, deathSndFirst, deathSndCount, hurtSndFirst, hurtSndCount, reloadSnd, talkSnd;

int bloodObject, fleshObject, fleshCount, dirtObject, dirtChance, shellObject, bloodCloudObject;

int respawnObject;
int respawnPoint;
int respawnNumPoints;
int respawnPoints[32];

int isServer, asyncServer, lanGame, drawMap;
int isDedicated, serverRestart;

char stretch=0, outlineFonts;
int stretchX, stretchY;
BITMAP *tmpb, *tmpd;

interact *itemList = NULL;

playerData players[GP_MAX_PLAYERS];

int GP_NUM_PLAYERS, GP_DIG_DELAY, GP_WORM_HEALTH, GP_WORM_RESPAWN, GP_DIG_DIST;
int IMG_DIG_HOLE;
long GP_JUMPER_FORCE,
     GP_TERM_VELOCITY, GP_LINE_DIST, GP_LINE_DIST2,
     GP_LINE_FORCE, GP_LINE_FORCE2, GP_KICKER_FORCE, GP_FLASH_DIM, GP_ITEM_GRAVITY;

char auto_screenshot, instantReload;
long next_screenshot, deathcam_delay;

unsigned char bufor[2*GP_MAX_PLAYERS];

char buf[256];

char mapName[21];

//         QUICKTEXT     //
char quicktexts[10][51];
int quicktextActive=0;
///////////////////////////

char splitScreen, numPlayers;
unsigned char hudAlpha;

char serverAddr[256];

int weapCount, wobjCount, specCount;

worm worms[GP_MAX_PLAYERS];
weapon weapons[256];
weapon_obj weapon_objs[GP_MAX_OBJECTS];
special specials[256];
special_obj special_objs[GP_MAX_SPECIALS];
wobj wobjs[256];
wobj_obj wobj_objs[GP_MAX_WOBJECTS];

long int gameTime;

char keys[15];
char gameStart, gotKeys, gameOver, pScroll;

BITMAP *background = NULL, *level = NULL, *material = NULL, *lights = NULL, *parallax = NULL, *sniper = NULL, *imgLoading = NULL;
BITMAP *imgWorm = NULL, *crosshair = NULL, *imgWeap = NULL, *imgSpecial = NULL, *fire[9] = {NULL, }, *holes[255] = {NULL, };
BITMAP *buffer = NULL, *gameFont = NULL, *wormSkins;

long mapSizeX, mapSizeY;

long cosinus[33] = { 0, 98, 195, 290, 382, 471, 555, 634, 707, 773, 831, 881, 923, 956, 980, 995, 1000, 995, 980, 956, 923, 881, 831, 773, 707, 634, 555, 471, 382, 290, 195, 98, 0 };

long sinus[33] = { 1000, 995, 980, 956, 923, 881, 831, 773, 707, 634, 555, 471, 382, 290, 195, 98, 0,-98,-195,-290,-382,-471,-555,-634,-707,-773,-831,-881,-923,-956,-980,-995,-1000 };

//long wormColors[10] = { 240, 0, 116, 56, 28, 0, 0, 0, 0, 0};
long wormColors[10] = { 6842616, 14680064, 0, 0, 0, 0, 0, 0, 0, 0};

void calcPanes() {
   worm *local;
   int spotX, spotY, targX, targY, shakX, shakY;
   interact *item;

   for (int i=0; i<numViewports; i++)
   {
       local = &worms[viewport[i].player];
        
       shakX = RANDOM()%3-1;
       shakY = RANDOM()%3-1;
       
       if (local->active)
       {
          if (local->health > 0)
          {
             spotX = local->coords.x;
             if (spotX < (viewport[i].paneSizeX+1)/2)
                spotX = (viewport[i].paneSizeX+1)/2;
             if (spotX + (viewport[i].paneSizeX+1)/2 > mapSizeX)
                spotX = mapSizeX - (viewport[i].paneSizeX+1)/2;
             if (local->shakeEnd > gameTime)
               spotX += shakX;
             spotY = local->coords.y+(Engine.wormSizeYMax - Engine.wormSizeYMin)/2;
             if (spotY < (viewport[i].paneSizeY+1)/2)
                spotY = (viewport[i].paneSizeY+1)/2;
             if (spotY + (viewport[i].paneSizeY+1)/2 > mapSizeY)
                spotY = mapSizeY - (viewport[i].paneSizeY+1)/2;
             if (local->shakeEnd > gameTime)
               spotY += shakY;
             if (spotX < (viewport[i].paneSizeX+1)/2)
             {
                viewport[i].CurX = 0;
             }
             else if (spotX + viewport[i].paneSizeX/2 > mapSizeX)
             {
                viewport[i].CurX = mapSizeX - viewport[i].paneSizeX;
             }
             else {
                viewport[i].CurX = spotX - (viewport[i].paneSizeX+1)/2;
             }
    
             if (spotY < viewport[i].paneSizeY/2)
             {
                viewport[i].CurY = 0;
             }
             else if (spotY + viewport[i].paneSizeY/2 > mapSizeY)
             {
                viewport[i].CurY = mapSizeY - viewport[i].paneSizeY;
             }
             else {
                viewport[i].CurY = spotY - (viewport[i].paneSizeY+1)/2;
             }
          }
          else
          {
             item = itemList;
             if (gameType == GAME_CTF)
             while (item)
             {
                if (item->type == ITEM_FLAG)
                if (item->amount == local->team%2)
                   break;
                item = item->next;
             }
             if ((!item) && (gameType == GAME_CTF))
                return;
                
             getSpawnPoint(local, &spotX, &spotY, 0);
    
             if (gameType == GAME_CTF)
                spotX = item->spawnX;
             if (spotX < viewport[i].paneSizeX/2)
                spotX = viewport[i].paneSizeX/2;
             if (spotX + viewport[i].paneSizeX/2 > mapSizeX)
                spotX = mapSizeX - viewport[i].paneSizeX/2;
             if (local->shakeEnd > gameTime)
                spotX += shakX;
             if (gameType == GAME_CTF)
                spotY = item->spawnY;
             if (spotY < viewport[i].paneSizeY/2)
                spotY = viewport[i].paneSizeY/2;
             if (spotY + viewport[i].paneSizeY/2 > mapSizeY)
                spotY = mapSizeY - viewport[i].paneSizeY/2;
             if (local->shakeEnd > gameTime)
               spotY += shakY;
             if (spotX < viewport[i].paneSizeX/2)
             {
                targX = 0;
             }
             else if (spotX + viewport[i].paneSizeX/2 > mapSizeX)
             {
                targX = mapSizeX - viewport[i].paneSizeX;
             }
             else {
                targX = spotX - viewport[i].paneSizeX/2;
             }
    
             if (spotY < viewport[i].paneSizeY/2)
             {
                targY = 0;
             }
             else if (spotY + viewport[i].paneSizeY/2 > mapSizeY)
             {
                targY = mapSizeY - viewport[i].paneSizeY;
             }
             else {
                targY = spotY - viewport[i].paneSizeY/2;
             }
             if (targY < viewport[i].CurY)
                viewport[i].CurY--;
             if (targY > viewport[i].CurY)
                viewport[i].CurY++;
             if (targX < viewport[i].CurX)
                viewport[i].CurX--;
             if (targX > viewport[i].CurX)
                viewport[i].CurX++;
          }
       }
   }
}

extern int randomPos;

volatile char allowFrame;

void wurmzTimer()
{
   allowFrame = 1;
}
END_OF_FUNCTION(wurmzTimer);

int joyInit;

int main(int argc, char *argv[]) {         
   int i, j;
   int mode;

   TRACE("start\n");
   if (argc < 2)
   {
      isServer = 1;
   }
   else
   {
      if (!strcmp(argv[1], "-dedicated"))
      {
         isServer = 1;
         isDedicated = 1;
      }
#ifdef DEMOS
      else if (!strcmp(argv[1], "-play"))
      {
         isServer = 0;
         isDedicated = 0;
         isPlaybackMode = 1;
      }
      else if (!strcmp(argv[1], "-render"))
      {
         isServer = 0;
         isDedicated = 0;
         isPlaybackMode = 1;
         Engine.renderMovie = 1;
      }
      else if (!strcmp(argv[1], "-rec"))
      {
         isDemoMode = 1;
         isServer = 1;
         isDedicated = 0;
      }
#endif
      else
      {
         isServer = 0;
         sprintf(serverAddr, argv[1]);
      }
   }
   TRACE("init\n");

   if (!isDedicated)
   {
      allegro_init();

      register_png_file_type();
      install_joystick(JOY_TYPE_AUTODETECT);

   }
   else
   {
      allegro_init();

      set_color_conversion (COLORCONV_NONE);
      set_color_depth (16);
      set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
   }

   Console.Start();

   install_keyboard(); 
   install_timer();

   LOCK_VARIABLE(allowFrame);
   LOCK_FUNCTION(wurmzTimer);

   sprintf(buf, "Wurmz! -- %s", VERSION);
   
   set_window_title(buf);

   set_config_file("Wurmz!.cfg");

   if (!isDedicated)
      joyInit = load_joystick_data(NULL);

   asyncServer = get_config_int("wurmz!", "AsyncServer", 0);
   loadData();

   TRACE("timer\n");

   install_int_ex(wurmzTimer, BPS_TO_TIMER(fpsLimit));

//   set_config_file("Wurmz!.cfg");

   if (!isDedicated)
   {

      if (initRenderer(fullScreen) != 0)
      {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error setting video mode\n");
         return 1;
      }

      initPhysics();
   

      if (initLoading(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading the loading screen\n");
         return 1;
      }

//      reserve_voices(16, -1);
//      install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL);

      TRACE("sound start\n");

      Sound.Start();

      initStuff();

      fadeIn();

      Console.WriteF("Loading worm graphics...");
      if (initWorms(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading worms\n");
         return 1;
      }

      Console.WriteF("Loading crosshair graphics...");
      if (initCrosshair(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading crosshair\n");
         return 1;
      }

      Console.WriteF("Loading weapon graphics...");
      if (initWeaps(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading weapons\n");
         return 1;
      }

      Console.WriteF("Loading special graphics...");
      if (initSpecials(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading special objects\n");
         return 1;
      }

      Console.WriteF("Loading fire graphics...");
      if (initFire(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading fire\n");
         return 1;
      }

      Console.WriteF("Loading hole graphics...");
      if (initHoles(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading fire\n");
         return 1;
      }

      Console.WriteF("Loading font...");
      if (initFont(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading font\n");
         return 1;
      }

      Console.WriteF("Loading sniping graphics...");
      if (initSniper(argv[0]) != 0) {
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading sniping view\n");
         return 1;
      }

      initPlayer();

//   set_config_file("data/Wurmz!.cfg");
      fadeOut();

      Console.WriteF("%d joystick(s) detected...", num_joysticks);
      if (joyInit)
      {
         Console.WriteF("Joystick calibration required...");
         for (int i = 0; i < num_joysticks; i++) {
            while (joy[i].flags & JOYFLAG_CALIBRATE) {
               clear_bitmap(buffer);
               char *msg = (char *)calibrate_joystick_name(i);
               drawTextCenter(buffer, msg, 160, 70);
               drawTextCenter(buffer, "and press SPACE", 160, 80);
               acquire_screen();
               render();
               release_screen();
               while (!key[KEY_SPACE]);
               while (key[KEY_SPACE]);
               calibrate_joystick(i);
            }
         }
         save_joystick_data(NULL);
      }
   }
   else
   {
      set_display_switch_mode(SWITCH_BACKGROUND);
   }

   if (gameType == GAME_PRD) teamplay = 0;
   if ((gameType == GAME_CTF) || (gameType == GAME_DOM)) teamplay = 1;


//   for (i=0; i < GP_MAX_PLAYERS; i++) worms[i].aim = 16;

   while (!key[KEY_ESC])
   {

#ifdef DEMOS
   if (isPlaybackMode)
   {
   }
   else
#endif
   if (isServer)
   {
      if (ServerInit())
      {
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error spawning serv0r\n");
         return 1;
      }
   }
   else
   {
      if (Client.Init(serverAddr, Engine.network.serverPort))
      {
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error connecting to serv0r\n");
         return 1;
      }
   }

   gameStart = 0;
   gameOver = 0;
   gameTime = 0;
   if (key[KEY_ESC]) gameOver = 1;
   predator = &worms[0];
   if (isServer)
   {
      serverRestart = 0;
      for (i=0; i<GP_MAX_PLAYERS; i++)
      {
         if (ClientConn[i] != -2)
         if (ClientConn[i] != -1)
            closesocket(ClientConn[i]);
         if (worms[i].active)
            removePlayer(i);
      }
      GP_NUM_PLAYERS = 0;
   }
   randomPos = 0;
   
   if ((isServer) && (!isDedicated))
   {
      GP_NUM_PLAYERS = numPlayers;
      for (int num=0; num < GP_NUM_PLAYERS; num++)
      {
         ClientConn[num] = -2;
         sprintf(worms[num].name, players[num].playerName);
         worms[num].team = players[num].localTeam;
         worms[num].skin = players[num].localSkin;
         addPlayer(num);
         renderSkin(num, worms[num].skin, worms[num].team);
         worms[num].active = 2;
         for (i=0; i<5; i++)
            worms[num].weaps[i] = players[num].localWeaps[i];
         worms[num].sentKeys=1;
      }
   }

   initAI();
   if (isServer)
   if (!lanGame)
   {
      drawTextCenter(buffer,"Registering game server at the lobby...",160,150);
      render();
      serverRegister();
   }

#ifdef DEMOS
   if (isPlaybackMode)
      StartPlayback("demo1.dem");
#endif

#ifdef DEMOS
   if (isDemoMode)
   {
      StartRecording("demo1.dem");
      demoJoinPackets();
   }
#endif


   while ((!gameStart) && (!gameOver))
   {
      if (isServer)
      {
         networkServerListen();
         networkServerProcess();
      }
      else
      {
         networkProcess();
#ifdef DEMOS
         if (!isPlaybackMode)
#endif
         if (key[KEY_F1])
         {
            Packet(CLIENT_BEGIN);
            Client.SendPacket(packetsend, CLIENT_BEGIN_SIZE, 1);
         }
      }

      if (!isDedicated)
      {
         clear_bitmap(buffer);
         hud.drawStatus();
      }

      while (!allowFrame) { rest(1); }
      allowFrame = 0;

      if (!isDedicated)
      {
         acquire_screen();
         render();
         release_screen();
      }
      
      if (key[KEY_ESC]) gameOver = 1;
   }

   if (gameOver)
   {

      clear_keybuf();
#ifdef DEMOS
      if ((isDemoMode) || (isPlaybackMode))
      {
         FreeDemo();
      }

      if (isPlaybackMode)
      {
      }
      else
#endif
      if (isServer)
      {
         ServerStopListening();
         ServerExit();
      }
      else
      {
         Packet(CLIENT_QUIT);
         Client.SendPacket(packetsend, CLIENT_QUIT_SIZE, 1);

         Client.Exit();
      }
      freeAI();
      freeAllStuff();
      //exit here
      return 0;
   }

   if ((!isServer) && (gameStart))
   {
      Packet(CLIENT_MOVE);
      PacketAddByte(0);
      Client.SendPacket(packetsend, CLIENT_MOVE_SIZE, 1);
   }

   TRACE("init sounds\n");

   if (!isDedicated)
      Sound.initSounds();

   Console.Write("Game spawned...");

   if (!isDedicated)
   {
      fadeIn();
      loadWeapons();
      loadWobjects();
      loadSpecial();
      initLuaAI();

      if (initLevel(mapName) != 0) {
         Sound.freeSounds();
         Sound.Free();
         freeAllStuff();
         set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
         allegro_message("Error loading level\n");
         return 1;
      }
      initWaypoints(mapName);
   
      TRACE("map loaded\n");
   
      mapSizeX = material->w;
      mapSizeY = material->h;
   
      for (i=0; i<GP_NUM_PLAYERS; i++)
      {
         if (worms[i].active)
         {
            //fixme
            respawnPlayer(i);
            worms[i].lives = lifeLimit;
            for (j=0; j<5; j++)
               worms[i].ammo[j] = weapons[worms[i].weaps[j]].maxAmmo;
         }
      }
      fadeOut();
   }

   if (isServer)
   {
      ServerStopListening();
      TRACE("server stopped\n");
   }

   next_screenshot = -1;

   if (!isDedicated)
      clear_bitmap(screen);

   if (gameStart)
   while ((!key[KEY_ESC]) && (!serverRestart)) {

      TRACE("-- game frame %d\n", gameTime);

      processInput();

      if (!isDedicated)
      {
         TRACE("  timing\n");
         engineWeapTiming();
         engineShrapTiming();
         engineSpecialTiming();
         playerTiming();

         TRACE("  physics\n");
         enginePhysics();
         Sound.preFrame();
         engineWeapPhysics();
         engineShrapPhysics();

         TRACE("  items\n");
         ItemPhysics();
         ItemsThink();

         TRACE("  panes\n");
         calcPanes();

         hud.drawViewports();
      }

      while (!allowFrame) { rest(0); }
      allowFrame = 0;

      if (!isDedicated)
      {
         if ((key[KEY_F12]) || ((auto_screenshot) && (next_screenshot == gameTime)) || (Engine.renderMovie))
         {
            key[KEY_F12]=0;
            sprintf(buf, "screenshots/screen-%09d.bmp", gameTime);
            save_bitmap(buf, buffer, backpal);
            if (!Engine.renderMovie)
               Console.Write("Screenshot taken...");
         }
#ifdef DEMOS
         if (!isPlaybackMode)
         {
#endif
            
            if ((key[players[0].keys[GK_CHAT]]) && (!chatActive))
            {
               chatActive=1;
               while (keypressed()) readkey();
            }
            processChat();
//          QUICKTEXT            //
            processQuickText();
////////////////////////////////////            
#ifdef DEMOS
         }
#endif
         TRACE("  postframe\n");
         Sound.postFrame();
         acquire_screen();
         render();
         release_screen();
      }

      gameTime++;
   }
   }

#ifdef DEMOS
   if ((isDemoMode) || (isPlaybackMode))
      FreeDemo();
   
   if (isPlaybackMode)
   {
   }
   else
#endif
   if (isServer)
   {
      ServerExit();
   }
   else
   {
      Packet(CLIENT_QUIT);
      Client.SendPacket(packetsend, CLIENT_QUIT_SIZE, 1);

      Client.Exit();
   }

   if (!isDedicated)
   {
      clear_keybuf();

      Sound.freeSounds();
      Sound.Free();
    
      freeAllStuff();
      freeAI();
   }


   set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
   return 0;     
}     
END_OF_MAIN();


