/****************************************************************************\
;                                                                            ;
;  lmpc.c  -  projekt LMP control center                                     ;
;  main source code                                                          ;
;                                                                            ;
;  The program lmpc is a LMP conversion utility                              ;
;                                                                            ;
;  Uwe Girlich                                                               ;
;  Erika-von-Brockdorff-Strasse 2                                            ;
;  04159 Leipzig                                                             ;
;  Deutschland / Germany                                                     ;
;  E-mail: girlich@aix520.informatik.uni-leipzig.de                          ;
;                                                                            ;
\****************************************************************************/


#ifndef VERSION
  the version must be defined
#endif
#ifndef DATE
  the date must be defined
#endif

#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ulmp.h"
#include "tools.h"

#define acInfo          0x01
#define acLMP2LS        0x02
#define acLS2LMP        0x04
#define acConvert       0x08

#define opVersion       0x01
#define opViewPlayer    0x02
#define opAddWT         0x04
#define opRemovePause   0x08
#define opSelectTics	0x10
#define opDefineGame	0x20

#define OLD_NEW         1
#define NEW_OLD         2

#define HEADER_START          0
#define HEADER                1
#define HEADER_END            2
#define DATA_START            3
#define DATA                  4
#define DATA_END              5

#define LMP_INIT              1
#define LMP_END               2

typedef struct {
		unsigned char	action;
		unsigned char	option;
		unsigned char	versionbyte;
		unsigned char	mainplayer;
		long		tic_start;
		long		tic_end;
		long		addwt;
		int		game;
	       } opt_t;


/* prototypes */
void syntax(void);
void version(void);
void ActionInfo(char *filename, opt_t *opt);
void ActionLMP2LS(char *lmpfilename, char *lsfilename, opt_t *opt);
void ActionLS2LMP(char *lsfilename, char *lmpfilename, int action);
void ActionConvert(char *infilename, char *outfilename, opt_t *opt);

int argf;
int argvf_base;

/******************************************************************************/
/** Main program **************************************************************/
/******************************************************************************/

int main(int argc, char **argv)
{
  int i;
  int c;
  char c_str[2]=" "; 
  char *pos_r, *pos_l;
  int a;
  int sep;
  opt_t opt;

  static struct option long_options[] = {
    {"help",           0, 0, 'h'},
    {"version",        0, 0, 'V'},
    {"info",           0, 0, 'i'},
    {"ls-to-lmp",      0, 0, 'l'},
    {"lmp-to-ls",      0, 0, 's'},
    {"change-version", 1, 0, 'v'},
    {"change-player",  1, 0, 'p'},
    {"add-WT",         1, 0, 'w'},
    {"remove-pause",   0, 0, 'r'}, 
    {"tic",            1, 0, 't'},
    {"game",           1, 0, 'g'},
    {0,                0, 0, 0}
  };
  int option_index = 0;

  opt.action    = 0;
  opt.option	= 0;
  opt.game      = UNKNOWN;
  opt.tic_start = 0;
  opt.tic_end   = 0;

  /* scan the command line */
  while (1) {
    c = getopt_long(argc, argv, "hVilsv:p:w:rt:g:", long_options, &option_index);

    if (c==-1) break;
    *c_str = (char) c; 
    switch (c) {
      case ':': 
        syserror(ARGMIS,c_str); 
      break;
      case 'h':
        syntax();
      break;
      case 'V':
        version(); 
      break;
      case 'i': 
        if (opt.action) syserror(MANYACT,c_str);
        opt.action |= acInfo; 
      break;
      case 'l':
        if (opt.action) syserror(MANYACT,c_str);
        opt.action = acLS2LMP;
      break;
      case 's':
        if (opt.action) syserror(MANYACT,c_str);
        opt.action = acLMP2LS;
      break;
      case 'v':
        if (opt.action&(~acConvert)) syserror(MANYACT,c_str);
        opt.action |= acConvert;
        opt.option |= opVersion;
        opt.versionbyte = strtoversionbyte(optarg); 
      break;
      case 'p':
        if (opt.action&(~acConvert)) syserror(MANYACT,c_str);
        opt.action |= acConvert;
        opt.option |= opViewPlayer;
        a = sscanf(optarg,"%i",&i);
        opt.mainplayer = i % MAXPLAYER;
        if (a!=1) syserror(INVOPT, optarg);
      break;
      case 'w':
        if (opt.action&(~acConvert)) syserror(MANYACT,c_str);
        opt.action |= acConvert;
        opt.option |= opAddWT;
        a = sscanf(optarg,"%li",&opt.addwt);
        if ((a!=1) || (opt.addwt<0)) syserror(INVOPT,optarg);
      break;
      case 'r':
        if (opt.action&(~acConvert)) syserror(MANYACT,c_str);
        opt.action |= acConvert;
        opt.option |= opRemovePause;
      break;
      case 't':
        if (opt.option&opSelectTics) syserror(MANYARGS,c_str);
        opt.option |= opSelectTics;
        sep = (int)',';
        pos_l = index(optarg,sep);
        pos_r = rindex(optarg,sep);
        if (pos_l == NULL) {
          sep = (int)':';
          pos_l=index(optarg,sep);
          pos_r = rindex(optarg,sep);
        }
        if ((pos_l == NULL) || (pos_r!=pos_l)) syserror(INVOPT,optarg);
        if (pos_l == optarg) {
          opt.tic_start = 0;
        } else {
          *pos_l = '\0';
          a = sscanf(optarg,"%li",&opt.tic_start);
          if ((a!=1) || (opt.tic_start<0)) syserror(INVOPT,optarg);
          *pos_l = (char)sep;
        }
        if (*(++pos_r) != '\0') {
          a = sscanf(pos_r, "%li", &opt.tic_end);
          if ((a!=1) || (opt.tic_end<opt.tic_start)) syserror(INVOPT,pos_r);
        } else {
          opt.tic_end = 0;
        }
      break;
      case 'g':
        if (opt.option&opDefineGame) syserror(MANYARGS,c_str);
        opt.option |= opDefineGame;
        for (a=0;a<strlen(optarg);a++) optarg[a]=toupper(optarg[a]);
        if ((!strcmp(optarg,"DOOM")) || (!strcmp(optarg,"D"))) {
          opt.game=(DOOM_old|DOOM_new);
        }
        else {
          if ((!strcmp(optarg,"DOOM2")) || (!strcmp(optarg,"2"))) {
            opt.game=DOOM2; 
          }
          else {
            if ((!strcmp(optarg,"HERETIC")) || (!strcmp(optarg,"H"))) {
              opt.game=HERETIC; 
            }
            else {
              syserror(INVOPT, optarg);
            }
          }
        }
      break;
      case '?':
        exit(ILLOPT);
      break;
      default:
        syserror(EINVAL, c_str);
      break; 
    }
  }; 

  /* how many files ? */
  argf = argc-optind;   /* number of files on the command line */
  argvf_base = optind;  /* this is the first file */

  /* check the options */
  /* without options means acInfo */
  if (!opt.action) {
    opt.action |= acInfo;
    if (!argf) syntax();  /* without any actions and files */
  }
  /* --tic only with LMP2LS */
  if ((opt.option&opSelectTics) && 
      (opt.action&(~acLMP2LS))) 
    syserror(ILLOPT,"--tic");
  /* --game only with info, LMP2LS and convert */
  if ((opt.option&opDefineGame) &&
      (opt.action&(~(acInfo|acLMP2LS|acConvert)))) 
    syserror(ILLOPT, "--game");

  /* convert MS-DOS filenames to lower case */
  #ifdef __MSDOS__
    for (i=0;i<argf;i++) { 
      char *p;
      for (p = argv[argvf_base+i]; *p; p++) *p = (char)tolower((int)*p);
    }
  #endif

  /* do the actions */
  switch (opt.action) {
    case acInfo:
      if (argf<1) syserror(MISIFN, "command line");
      for (i=0;i<argf;i++)
        ActionInfo(argv[argvf_base+i], &opt);
    break;
    case acLMP2LS:
      if (argf<1) syserror(MISIFN, "command line");
      if (argf==1) syserror(MISOFN, "command line");
      if (argf>2) syserror(MANYARGS, "command line");
      ActionLMP2LS(argv[argvf_base], argv[argvf_base+1], &opt);
    break;
    case acLS2LMP:
      if (argf<1) syserror(MISIFN, "command line");
      if (argf==1) syserror(MISOFN, "command line");
      /* argf >=2 */
      for (i=0;i<argf-1;i++) { /* count the LS files */
        /*
           the first call has to init the LMP file,
           the last one has to close it
        */
        ActionLS2LMP(argv[argvf_base+i], argv[argvf_base+argf-1],
                       (i==0?LMP_INIT:0)|(i==argf-2?LMP_END:0)); 
      }
    break;
    case acConvert:
      if (argf<1) syserror(MISIFN, "command line");
      if (argf>2) syserror(MANYARGS, "command line");
      if (argf==1) 
        ActionConvert(argv[argvf_base], argv[argvf_base], &opt);
      else
        ActionConvert(argv[argvf_base], argv[argvf_base+1], &opt);
    break;
  }

  return 0;
}


/******************************************************************************/
/** Utility functions *********************************************************/
/******************************************************************************/

void syntax(void)
{
  printf("LMP Control Center\n");
  printf("LMPC (c) U. Girlich, 1994, 1995, Release %s %s\n", VERSION, DATE);
  printf("lmpc [option ...] filename [filename ...]\n");
  printf("-h, --help                   display this help and exit.\n");
  printf("-V, --version                output version information and exit.\n");
  printf("-i, --info                   prints out informations about the LMP-files.\n");
  printf("-s, --lmp-to-ls              recompiles a LMP-file to a LS-file.\n");
  printf("-l, --ls-to-lmp              compiles one or more LS-files to a LMP-file.\n");
  printf("-v, --change-version VERSION changes the version of a LMP-file to VERSION\n");
  printf("                             (0 means old DOOM).\n");
  printf("-p, --change-player PLAYER   changes the recording player of a LMP-file to\n");
  printf("                             PLAYER (0..3).\n");
  printf("-w, --add-WT SECONDS         adds SECONDS WT game tics to the LMP file\n");
  printf("                             (SECONDS must be integer).\n");
  printf("-r, --remove-pause           remove the game tics between PS and PE.\n");
  printf("-t, --tic FROM(,|:)TO        converts only a part of the LMP-file\n");
  printf("                             (in connection with -s only).\n");
  printf("-g, --game GAME              forces input game (GAME=DOOM, DOOM2 or HERETIC)\n");
  printf("                             short: D,2,H (in connection with -(i|s|v|p|w|r)).\n");
  exit(1);
}


void version(void)
{
  printf("lmpc %s, %s\n", VERSION, DATE);
  exit(0);
}


/******************************************************************************/
/** Actions *******************************************************************/
/******************************************************************************/

void ActionInfo(char *filename, opt_t *opt)
{
  LMP_t l;
  char buffer[10];

  LMP_init(&l,filename,"rb");
  l.game = (opt->option&opDefineGame?opt->game:UNKNOWN);
  LMP_readheader(&l); 
  printf("%s: ",l.filename);
  printf("%s %s ", l.gs, l.ns);
  if (l.game==DOOM2)
    printf("Map%02d", l.map); 
  else
    printf("E%dM%d", l.episode, l.map);
  printf(" skill %d, ", l.skill+1);
  if (l.playernum==1)
    printf("single");
  else {
    printf("%d ", l.playernum);
    if (l.game&(DOOM_new||DOOM2))
      switch (l.multirule) {
	case ruleCOOP:  printf("cooperative "); break;
	case ruleDEATH: printf("deathmatch ");  break;
	case ruleALT:   printf("altdeath ");    break;
	default:   printf("unknown rule "); break;
      }
    printf("players"); 
  }    
  if (l.respawn) printf(", respawn ");
  if (l.fast)    printf(", fast ");
  if (l.nomonsters) printf(", nomonsters "); 
  printf("\n");
  if (l.playernum>1) printf("rec. by player %d, ", l.mainplayer);
  printf("%li game tics, ", l.tics);
  printf("%s.\n", Time2String(l.time,buffer));
  LMP_done(&l);
}


void ActionLMP2LS(char *lmpfilename, char *lsfilename, opt_t *opt)
{
  LMP_t l;
  LS_t s;
  TIC_t t;
  STAT_t GF[MAXPLAYER], GB[MAXPLAYER], 
	 SL[MAXPLAYER], SR[MAXPLAYER], 
	 TL[MAXPLAYER], TR[MAXPLAYER];
  unsigned long wait_sum[MAXPLAYER];
  char buffer[10];
  unsigned char p;
  int pause, empty_t, empty_c;
  double time;
  char ts[200];
  char cs[200];
  unsigned long tic;
  unsigned char b;
  int wb;
  int KeyOld, KeyNew; 
  int m;
  
  LMP_init(&l,lmpfilename,"rb");
  l.game = (opt->option&opDefineGame?opt->game:UNKNOWN);
  LMP_readheader(&l); 
  LS_init(&s,lsfilename,"wb");
  printf("%s -> %s\n", l.filename, s.filename);
  wb=0;
  switch (l.game) {
    case HERETIC  : wb=16; break;
    case DOOM_new : 
    case DOOM_old : wb=0; break;
    case DOOM2    : wb=8; break;
    case DOOM_new|DOOM2 : wb=8; break;
  }
  fprintf(s.file,"# %s %.*s %s\n", l.gs, (int)(24-strlen(l.gs)), "LMP file:                ", l.filename);
  fprintf(s.file,"# Number of players:        %d\n", l.playernum);
  fprintf(s.file,"# Total play time:          %s\n", Time2String(l.time,buffer));
  fprintf(s.file,"# Number of game tics:      %ld\n", l.tics);
  fprintf(s.file,"\n");
  fprintf(s.file,"HeaderStart\n");
  fprintf(s.file," Game:         %s\n", l.gs);
  if (l.game!=HERETIC) fprintf(s.file," Version:      %s\n", l.ns); 
  fprintf(s.file," Skill:        %d\n", l.skill+1);
  fprintf(s.file," Episode:      %d\n", l.episode);
  fprintf(s.file," Map:          %d\n", l.map);
  if (l.playernum>1) {
    for (p=0;p<l.playernum;p++) {
      fprintf(s.file," Player%d:      %d        # %s player\n",p,l.num[p],PlayerColor[l.num[p]]);
    }
  }
  if (l.game&DOOM_new || l.game&DOOM2) {
    if (l.playernum>1) {
      fprintf(s.file," MultiRule:    ");
      switch (l.multirule) {
	case ruleCOOP : fprintf(s.file,"Cooperative")   ; break;
	case ruleDEATH: fprintf(s.file,"DeathMeatch")   ; break;
	case ruleALT  : fprintf(s.file,"AltDeath")      ; break;
	default       : fprintf(s.file,"%d",l.multirule); break;
      }
      fprintf(s.file,"\n");
    }
    if (l.respawn   !=0) fprintf(s.file," Respawn:      %d\n", l.respawn);
    if (l.fast      !=0) fprintf(s.file," Fast:         %d\n", l.fast);
    if (l.nomonsters!=0) fprintf(s.file," NoMonsters:   %d\n", l.nomonsters);
    if (l.playernum>1 || (l.playernum==1 && l.mainplayer!=0)) 
			 fprintf(s.file, "ViewOfPlayer: %d\n", l.mainplayer); 
  }
  fprintf(s.file,"HeaderEnd\n");
  fprintf(s.file,"\n");
  fprintf(s.file,"DataStart\n");
  
  for (p=0;p<l.playernum;p++) {
    wait_sum[p] = 0;
    STAT_init(&GF[p],"GF", 2);
    STAT_init(&GB[p],"GB", 2);
    STAT_init(&SL[p],"SL", 2);
    STAT_init(&SR[p],"SR", 2);
    STAT_init(&TL[p],"TL", 3);
    STAT_init(&TR[p],"TR", 3);
  }
  
  if (opt->tic_start==0) opt->tic_start=1;
  if (opt->tic_end==0) opt->tic_end=l.tics;
  pause=FALSE;
  for (tic=1;tic<=l.tics;tic++) {
    time=tic*TICTIME;
    for (p=0;p<l.playernum;p++) {
      LMP_getgametic(&l,&t);
      strcpy(ts,"");
      strcpy(cs,"");
      empty_t=TRUE;
      empty_c=TRUE;
      if (t.go!=0) {
	if (!empty_t) strcat(ts," ");
	if (t.go<=0x7F) {
	  sprintf(ts+strlen(ts),"GF%d",t.go);
	  GF[p].hist[t.go]++;
	} 
	if (0x81<=t.go) {
	  sprintf(ts+strlen(ts),"GB%d",256-t.go);
	  GB[p].hist[256-t.go]++;
	} 
	if (t.go==0x80) {
	  sprintf(ts+strlen(ts),"G0x%02x",t.go);
	  if (!empty_c) strcat(cs," ");
	  strcat(cs,"Unknown Go");
	  empty_c=FALSE;
	}
	empty_t=FALSE; 
      }
      if (t.strafe!=0) {
	if (!empty_t) strcat(ts," ");
	if (t.strafe<=0x7F) {
	  sprintf(ts+strlen(ts),"SR%d",t.strafe);
	  SR[p].hist[t.strafe]++;
	}
	if (0x81<=t.strafe) { 
	  sprintf(ts+strlen(ts),"SL%d",256-t.strafe);
	  SL[p].hist[256-t.strafe]++;
	}
	if (t.strafe==0x80) {
	  sprintf(ts+strlen(ts),"S0x%02x",t.strafe);
	  if (!empty_c) strcat(cs," ");
	  strcat(cs,"Unknown Strafe");
	  empty_c=FALSE;
	}
	empty_t=FALSE;
      }
      if (t.turn!=0) {
	if (!empty_t) strcat(ts," ");
	if (t.turn<=0x7F) {
	  sprintf(ts+strlen(ts),"TL%d",t.turn);
	  TL[p].hist[t.turn]++;
	}
	if (0x81<=t.turn) {
	  sprintf(ts+strlen(ts),"TR%d",256-t.turn);
	  TR[p].hist[256-t.turn]++;
	} 
	if (t.turn==0x80) {
	  sprintf(ts+strlen(ts),"T0x%02x",t.turn);
	  if (!empty_c) strcat(cs," ");
	  strcat(cs,"Unknown Turn");
	  empty_c=FALSE;
	}
	empty_t=FALSE;
      }
      if (t.use!=0) {
	if (!empty_t) strcat(ts," ");
	if (t.use & 0x80) {
	  t.use ^= 0x80;
	  if (t.use==0x01) {
	    if (pause) {
	      strcat(ts,"PE");
	      pause=FALSE;
	    }
	    else {
	      strcat(ts,"PS");
	      pause=TRUE;
	    }
	  } /* t.use==0x01 */
	  else if ((t.use & 0x03) == 0x02) {
	    sprintf(ts+strlen(ts),"SG%d",(t.use>>2) & 0x07);
	  }
	  else {
	    sprintf(ts+strlen(ts),"U0x%02x",t.use | 0x80);
	    if (!empty_c) strcat(cs," ");
	    strcat(cs,"Unknown Use");
	    empty_c=FALSE;
	  }
	} /* t.use & 0x80 */
	else {
	  switch (t.use & 0x03) {
	    case 0x01: strcat(ts,"FW ");    break;
	    case 0x02: strcat(ts,"UT ");    break;
	    case 0x03: strcat(ts,"FW UT "); break;
	  }
	  if (t.use & 0x04) {
	    b = t.use >> 3;
	    if (0x07<b) {
	      sprintf(ts+strlen(ts),"U0x%02x",t.use);
	      if (!empty_c) strcat(cs," ");
	      strcat(cs,"Unknown Use");
	      empty_c=FALSE;
	    }
	    else {  
	      sprintf(ts+strlen(ts),"NW%d",b+1);
	      if (!empty_c) strcat(cs," ");
	      strcat(cs,WeaponName[wb+b]);
	      empty_c=FALSE;
	    }
	  }
	  else {
	    if ((t.use & (~0x03)) !=0) {
	      sprintf(ts+strlen(ts),"U0x%02x",t.use);
	      if (!empty_c) strcat(cs," ");
	      strcat(cs,"Unknown Use");
	      empty_c=FALSE;
	    }
	  }
	}
	empty_t=FALSE;
      }
      if (l.game==HERETIC && t.fl!=0) {
	b=t.fl & 0x0F;
	if (b!=0) {
	  if (!empty_t) strcat(ts," ");
	  if (b<=0x07) {
	    sprintf(ts+strlen(ts),"LU%d",b);
	  }
	  if (b>=0x09) {
	    sprintf(ts+strlen(ts),"LD%d",16-b);
	  }
	  if (b==0x08) {
	    strcat(ts,"LC");
	  }
	  empty_t=FALSE;
	} 
	b=t.fl >> 4;
	if (b!=0) {
	  if (!empty_t) strcat(ts," ");
	  if (b<=0x07) {
	    sprintf(ts+strlen(ts),"FU%d",b);
	  }
	  if (b>=0x09) {
	    sprintf(ts+strlen(ts),"FD%d",16-b);
	  }
	  if (b==0x08) {
	    strcat(ts,"FC");
	  }
	  empty_t=FALSE;
	} 
      }
      if (l.game==HERETIC && t.art!=0) {
	if (!empty_t) strcat(ts," ");
	if (t.art<=10) {
	  sprintf(ts+strlen(ts),"AT%c", t.art-1+'a');
	  if (!empty_c) strcat(cs," ");
	  sprintf(cs+strlen(cs),"%s",ArtifactName[t.art-1]);
	  empty_c=FALSE; 
	}
	if (11<=t.art) {
	  sprintf(ts+strlen(ts),"A0x%02x",t.art); 
	  if (!empty_c) strcat(cs," ");
	  strcat(cs,"Unknown Artifact");
	  empty_c=FALSE; 
	} 
	empty_t=FALSE;
      }
      if (empty_t) {
	strcat(ts,"WT");
	wait_sum[p]++;
      }
      if ((tic>=opt->tic_start) && (tic<=opt->tic_end)) {
        if (p==0) {
 	  fprintf(s.file,"%s%.*s# %5ld (%s) %s\n", ts, (int)(24-strlen(ts)), "                        ", tic, Time2String(time,buffer), cs);
        } 
        else {
          fprintf(s.file,"%s%.*s# %s\n", ts, (int)(24-strlen(ts)), "                        ", cs);
        }
      }
    }
  }
  fprintf(s.file,"DataEnd\n");
  if (l.tics>0) {
    m=0;
    for(p=0;p<l.playernum;p++) {
      STAT_calc(&GF[p]);
      STAT_calc(&GB[p]);
      STAT_calc(&SL[p]);
      STAT_calc(&SR[p]);
      STAT_calc(&TL[p]);
      STAT_calc(&TR[p]);
      fprintf(s.file,"\n# Statistics for player %d\n", p);
      if (TL[p].control==C_KEY && TR[p].control==C_KEY) {
	KeyOld=STAT_turncheck(&TL[p],1,2,5) && STAT_turncheck(&TR[p],2,3,5);
	KeyNew=STAT_turncheck(&TL[p],1,3,5) && STAT_turncheck(&TR[p],1,2,5);
	/* the following code isn't totally save. What happens, if 
	   KeyOld and KeyNew are both true ? */
	if (!KeyOld && !KeyNew) 
	  TL[p].control=C_MOUSE;
	else 
	  if (KeyOld)
	    TL[p].control=C_KEY_OLD;
	  else
	   TL[p].control=C_KEY_NEW;
	TR[p].control=TL[p].control;
      }
      STAT_writehist(&GF[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      STAT_writehist(&GB[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      STAT_writehist(&SL[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      STAT_writehist(&SR[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      STAT_writehist(&TL[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      STAT_writehist(&TR[p],cs); if (strcmp(cs,"")!=0) fprintf(s.file,"# %s\n",cs);
      if (    GF[p].control==C_KEY && GB[p].control==C_KEY
	   && SL[p].control==C_KEY && SR[p].control==C_KEY
	   && (TL[p].control & C_KEY)
	   && (TR[p].control & C_KEY)
	 ) {
	fprintf(s.file,"# This player plays with keyboard/joystick only. Give him a mouse.\n");
      } 
      if (SR[p].sum+SL[p].sum==0) {
	fprintf(s.file,"# This player plays without STRAFE. Show him the README.\n");
      }
      fprintf(s.file,"# %lu * WT in %lu tics (%.1f%%)\n",wait_sum[p],l.tics,100.0*wait_sum[p]/l.tics);
      if (TL[p].control==C_KEY_OLD && (l.game&DOOM_new || l.game&DOOM2)) m=OLD_NEW;
      if (TL[p].control==C_KEY_NEW && l.game==DOOM_old) m=NEW_OLD; 
    }
    if (m==NEW_OLD) fprintf(s.file,"# This LMP was originally recorded with an old version (<1.4)\n");
    if (m==OLD_NEW) fprintf(s.file,"# This LMP was originally recorded with a new version (>=1.4)\n");
  }
  LMP_done(&l);
  LS_done(&s);
}


#define MAXLINELENGTH 200

void ActionLS2LMP(char *lsfilename, char *lmpfilename, int action)
{
  static LMP_t l;
  LS_t s;
  TIC_t t;
  int state;
  unsigned long linenumber;
  char lsline[MAXLINELENGTH];
  char *gt, *temp, *res, *token, *value;
  int i, pause;
  int notdecoded, numberused;
  long number;
  unsigned long repeatcounter;

  LS_init(&s,lsfilename,"rb");
  if (action&LMP_INIT) LMP_init(&l,lmpfilename,"wb");
  printf("%s ", s.filename);

  state=HEADER_START;
  linenumber=0;
  pause=FALSE;
  while (fgets(lsline,MAXLINELENGTH,s.file)==lsline) {
    linenumber++;
    if (lsline[strlen(lsline)-1]!='\n') {
      syntaxerror(linenumber,"line too long");
    }
    gt=lsline;
    temp=strchr(gt,'#');
    if (temp!=NULL) *temp='\0';
    for (i=0;i<strlen(gt);i++) gt[i]=toupper(gt[i]);
    gt=delspaces(gt);
    if (strlen(gt)>0) switch (state) {
      case HEADER_START:
	l.game=UNKNOWN;
	l.versionbyte=0;
	l.skill=0;
	l.episode=0;
	l.map=0;
	l.multirule=0;
	l.respawn=0;
	l.fast=0;
	l.nomonsters=0;
	l.playernum=1;
	l.mainplayer=0;
	for (i=0;i<MAXPLAYER;i++) l.num[i]=0; 
	if (strcmp(gt,"HEADERSTART")) syntaxerror(linenumber,"HeaderStart expected");
	state=HEADER;
      break; /* end HEADER_START */
      case HEADER:
	if (strcmp(gt,"HEADEREND")==0)
	  state=HEADER_END;
	else {
	  temp=strchr(gt,':');
	  if (temp==NULL) syntaxerror(linenumber,"a colon separates token and value in the header");
	  if (temp==gt) syntaxerror(linenumber,"token expected");
	  if (temp==gt+strlen(gt)-1) syntaxerror(linenumber,"value expected");
	  *temp='\0';
	  token=gt; 
	  token=delspaces(token);
	  value=temp+1;
	  value=delspaces(value);
	  notdecoded=TRUE;
	  numberused=FALSE;
	  number=strtol(value,&res,0);
	  if (notdecoded && strcmp(token,"GAME")==0) {
	    if (notdecoded && strcmp(value,"HERETIC")==0) {
	      l.game=HERETIC;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    } 
	    if (notdecoded && strcmp(value,"DOOM")==0) {
	      l.game=DOOM_old|DOOM_new;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    } 
	    if (notdecoded && strcmp(value,"DOOM ][")==0) {
	      l.game=DOOM2;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    } 
	    if (notdecoded && strcmp(value,"DOOM OR DOOM ][")==0) {
	      l.game=DOOM_new|DOOM2;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    } 
	  }
	  if (notdecoded && strcmp(token,"VERSION")==0) {
            l.versionbyte=strtoversionbyte(value);
	    numberused=FALSE;
	    notdecoded=FALSE;
	  } 
	  if (notdecoded && strcmp(token,"SKILL")==0) {
	    l.skill=number-1;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"EPISODE")==0) {
	    l.episode=number;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"MAP")==0) {
	    l.map=number;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"PLAYER0")==0) {
	    l.num[0]=number;
	    if (l.playernum<1) l.playernum=1;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"PLAYER1")==0) {
	    l.num[1]=number;
	    if (l.playernum<2) l.playernum=2;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"PLAYER2")==0) {
	    l.num[2]=number;
	    if (l.playernum<3) l.playernum=3;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"PLAYER3")==0) {
	    l.num[3]=number;
	    if (l.playernum<4) l.playernum=4;
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"MULTIRULE")==0) {
	    if (notdecoded && strcmp(value,"COOPERATIVE")==0) {
	      l.multirule=ruleCOOP;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    }
	    if (notdecoded && strcmp(value,"DEATHMATCH")==0) {
	      l.multirule=ruleDEATH;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    }
	    if (notdecoded && strcmp(value,"ALTDEATH")==0) {
	      l.multirule=ruleALT;
	      numberused=FALSE;
	      notdecoded=FALSE;
	    }
	    if (notdecoded) { 
	      l.multirule=number;
	      numberused=TRUE;
	      notdecoded=FALSE;
	    }
	  }
	  if (notdecoded && strcmp(token,"RESPAWN")==0) {
	    l.respawn=number; 
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"FAST")==0) {
	    l.fast=number; 
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"NOMONSTERS")==0) {
	    l.nomonsters=number; 
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded && strcmp(token,"VIEWOFPLAYER")==0) {
	    l.mainplayer=number; 
	    numberused=TRUE;
	    notdecoded=FALSE;
	  }
	  if (notdecoded) syntaxerror(linenumber,"token expected");
	  if (numberused && res[0]!='\0') syntaxerror(linenumber,"number expected");
	}
      break; /* end HEADER */
      case HEADER_END:
      case DATA_START:
	if (l.game==UNKNOWN) syntaxerror(linenumber,"Game is not defined");
	if (l.game==(DOOM_old|DOOM_new)) {
	  if (l.versionbyte==0) 
	    l.game=DOOM_old;
	  else
	    l.game=DOOM_new;
	}
	if (action&LMP_INIT) LMP_writeheader(&l);
	l.ticsize=(l.game==HERETIC?LONG_TIC:SHORT_TIC);
	state=DATA_START;
	if (strcmp(gt,"DATASTART")) syntaxerror(linenumber,"DataStart expected");
	state=DATA;
      break;
      case DATA:
	if (strcmp(gt,"DATAEND")==0) {
	  state=DATA_END;
	  if (action&LMP_END) LMP_writequitbyte(&l);
	  state++;
	}
	else {
	  t.go=0;
	  t.strafe=0;
	  t.turn=0;
	  t.use=0;
	  if (l.game==HERETIC) {
	    t.fl=0;
	    t.art=0;
	  }
	  repeatcounter=1;
	  token=strtok(gt," ");
	  while (token!=NULL) {
	    notdecoded=TRUE;
	    if (notdecoded && strncmp(token,"*",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"*number expected");
	      if (0<number) 
		repeatcounter=number; 
	      else
		syntaxerror(linenumber,"*number out of range");
	      notdecoded=FALSE;
	    }
	    if (notdecoded && strncmp(token,"GF",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"GFnumber expected");
	      if (0<number && number<128)
		t.go=number; 
	      else
		syntaxerror(linenumber,"GFnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"GB",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"GBnumber expected");
	      if (0<number && number<128)
		t.go=256-number;
	      else
		syntaxerror(linenumber,"GBnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"SR",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"SRnumber expected");
	      if (0<number && number<128)
		t.strafe=number; 
	      else
		syntaxerror(linenumber,"SRnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"SL",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"SLnumber expected");
	      if (0<number && number<128)
		t.strafe=256-number;
	      else
		syntaxerror(linenumber,"SLnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"TR",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"TRnumber expected");
	      if (0<number && number<128) 
		t.turn=256-number; 
	      else
		syntaxerror(linenumber,"TRnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"TL",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"TLnumber expected");
	      if (0<number && number<128)
		t.turn=number;
	      else
		syntaxerror(linenumber,"TLnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"PS",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"PS expected");
	      if (pause) syntaxwarning(linenumber,"Pause is activ already");
              pause=TRUE;
	      if (t.use!=0 && t.use!=0x81) syntaxerror(linenumber,"PS can''t be used with other use things");
	      t.use=0x81; 
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"PE",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"PE expected");
	      if (!pause) syntaxwarning(linenumber,"Pause is off already");
              pause=FALSE;
	      if (t.use!=0 && t.use!=0x81) syntaxerror(linenumber,"PE can't be used with other use things");
	      t.use=0x81; 
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"SG",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"SGnumber expected");
	      if (0<=number && number<=7) {
		if (t.use!=0) syntaxerror(linenumber,"SG can't be used with other use things");
		t.use=0x82 | (number<<2);
	      }
	      else
		syntaxerror(linenumber,"SGnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"FW",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"FW expected");
	      if (t.use & 0x80) syntaxerror(linenumber,"FW can't be used with PS, PE and SG");
	      t.use |= 0x01;
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"UT",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"UT expected");
	      if (t.use & 0x80) syntaxerror(linenumber,"UT can't be used with PS, PE and SG");
	      t.use |= 0x02;
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"NW",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"NWnumber expected");
	      if (1<=number && number<=8) {
		if (t.use & 0x80) syntaxerror(linenumber,"NW can't be used with PS, PE and SG"); 
		t.use = (t.use & 0x03) | 0x04 | ((number-1)<<3);
	      }
	      else
		syntaxerror(linenumber,"NWnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"WT",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"WT expected");
	      notdecoded=FALSE;
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"LU",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"LUnumber expected");
	      if (0<number && number<16)
		t.fl=(t.fl & 0xF0) | number; 
	      else
		syntaxerror(linenumber,"LUnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"LD",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"LDnumber expected");
	      if (0<number && number<16)
		t.fl=(t.fl & 0xF0) | (16-number);
	      else
		syntaxerror(linenumber,"LDnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"LC",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"LC expected");
	      t.fl=(t.fl & 0xF0) | 0x08; 
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"FU",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"FUnumber expected");
	      if (0<number && number<16)
		t.fl=(t.fl & 0x0F) | (number<<4); 
	      else
		syntaxerror(linenumber,"FUnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"FD",2)==0) {
	      number=strtol(token+2,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"FDnumber expected");
	      if (0<number && number<16)
		t.fl=(t.fl & 0x0F) | ((16-number)<<4);
	      else
		syntaxerror(linenumber,"FDnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"FC",2)==0) {
	      if (strlen(token)>2) syntaxerror(linenumber,"FC expected");
	      t.fl=(t.fl & 0x0F) | 0x80; 
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"AT",2)==0) {
	      if (strlen(token)!=3) syntaxerror(linenumber,"ATchar expected");
	      number=token[2]-'A'+1; 
	      if (1<=number && number<=10) 
		t.art=number;
	      else
		syntaxerror(linenumber,"ATchar out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"G",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Gnumber expected");
	      if (0<number && number<256) {
		if (t.go!=0) syntaxerror(linenumber,"Gnumber overwrites go-byte");
		t.go=number;
	      }
	      else
		syntaxerror(linenumber,"Gnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"S",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Snumber expected");
	      if (0<number && number<256) {
		if (t.strafe!=0) syntaxerror(linenumber,"Snumber overwrites strafe-byte");
		t.strafe=number;
	      }
	      else
		syntaxerror(linenumber,"Snumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"T",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Tnumber expected");
	      if (0<number && number<256) {
		if (t.turn!=0) syntaxerror(linenumber,"Tnumber overwrites turn-byte");
		t.turn=number;
	      }
	      else
		syntaxerror(linenumber,"Tnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded && strncmp(token,"U",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Unumber expected");
	      if (0<number && number<256) {
		if (t.use!=0) syntaxerror(linenumber,"Unumber overwrites use-byte");
		t.use=number;
	      }
	      else
		syntaxerror(linenumber,"Unumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"F",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Fnumber expected");
	      if (0<number && number<256) {
		if (t.fl!=0) syntaxerror(linenumber,"Fnumber overwrites flight/look-byte");
		t.fl=number;
	      }
	      else
		syntaxerror(linenumber,"Fnumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (l.game==HERETIC && notdecoded && strncmp(token,"A",1)==0) {
	      number=strtol(token+1,&res,0);
	      if (res[0]!='\0') syntaxerror(linenumber,"Anumber expected");
	      if (0<number && number<256) {
		if (t.art!=0) syntaxerror(linenumber,"Anumber overwrites use artifact-byte");
		t.art=number;
	      }
	      else
		syntaxerror(linenumber,"Anumber out of range");
	      notdecoded=FALSE; 
	    }
	    if (notdecoded) syntaxerror(linenumber,"Data token expected");       
	    token=strtok(NULL," ");
	  }
	  for (i=0;i<repeatcounter;i++) LMP_putgametic(&l, &t);
	}
      break;
      case DATA_END:
        /* ignore all the rest */
      break;
    } /* end switch (state) */
  } /* end while not end of file */
  LS_done(&s);
  if (action&LMP_END) {
    printf("-> %s\n", l.filename);
    LMP_done(&l); 
  }
}


void ActionConvert(char *infilename, char *outfilename, opt_t *opt)
{
  LMP_t li, lo;
  int NewFile, NewTics, NewHeader; 
  int o_game;
  char *gs, *o_version;
  long wttics=0, tic, ps=0;
  int p;
  TIC_t t[MAXPLAYER];
  TIC_t WT = {0,0,0,0,0,0};
  int pause=FALSE, allowput;
  #define TMP_FILE "lmpclmpc.tmp"

  LMP_init(&li, infilename, "rb");
  li.game = (opt->option&opDefineGame?opt->game:UNKNOWN);
  LMP_readheader(&li);
  printf("%s ", li.filename);
  NewFile=FALSE;
  NewTics=FALSE;
  NewHeader=FALSE;
  o_game=li.game;
  if (strcmp(infilename,outfilename)) NewFile=TRUE;
  if (opt->option&opVersion) {
    if (li.versionbyte==opt->versionbyte) {
      opt->option^=opVersion; /* nothing to do */
    }
    else {
      NewHeader=TRUE;
    }
    if (li.game==HERETIC) syserror(IMPVCH,li.filename);
    if (opt->versionbyte==0) {
      if (li.game==DOOM2) syserror(IMPVCH,li.filename);
      if (li.game&DOOM_new) {
        o_game=DOOM_old; 
        NewFile=TRUE;
      }
    }
    else {
      if (li.game==DOOM_old) {
        o_game=DOOM_new;
        NewFile=TRUE;
      }
    } 
    getversion(opt->versionbyte,&o_game,outfilename,&gs,&o_version); 
    printf("version %s -> %s ", li.ns, o_version);
  }
  if (opt->option&opViewPlayer) {
    if (li.game==HERETIC || li.game==DOOM_old) syserror(IMPVCH,li.filename);
    if (li.mainplayer==opt->mainplayer) {
      opt->option^=opViewPlayer; /* nothing to do */
    }
    else {
      NewHeader=TRUE;
    }
    printf("player %i -> %i ", li.mainplayer, opt->mainplayer);
  }
  if (opt->option&opAddWT) {
    if (opt->addwt==0) {
      opt->option^=opAddWT; /* nothing to do */
    }
    wttics = opt->addwt * 35 * li.playernum;   
    printf("+%lisec ", opt->addwt);
  }
  if (opt->option&opRemovePause) {
    NewFile=TRUE;
    NewTics=TRUE;
    pause = FALSE;
    ps = 0;
    printf("-PS -PE ");
  }
  printf("%s\n", outfilename);
  if (NewFile) {
    lo=li;
    LMP_init(&lo, TMP_FILE, "wb");
  }
  else {
    if (fclose(li.file)!=0) syserror(errno,li.filename);
    if ((li.file=fopen(li.filename, "r+b"))==NULL) syserror(errno,li.filename);
    lo=li;
  }
  lo.game=o_game;
  if (opt->option&opVersion) {
    lo.versionbyte=opt->versionbyte;
  }
  if (opt->option&opViewPlayer) {
    lo.mainplayer=opt->mainplayer;
  }

  /* header */
  if (NewHeader || NewFile) LMP_writeheader(&lo);

  /* game tics */
  if (NewFile) {
    if (NewTics) {
      allowput = TRUE;
      for (tic=1;tic<=li.tics;tic++) {
        for (p=0;p<li.playernum;p++) {
          LMP_getgametic(&li,&(t[p]));
        }
        if (opt->option&opRemovePause) {
          allowput = !pause;
          for (p=0;p<li.playernum;p++) {
            if (t[p].use==0x81) {
              if (pause) {
                pause=FALSE; /* PE */
                /* the PE game tic will not be stored */
                printf(", PE at %lu (%lu tics removed)\n",tic, tic-ps);
              }
              else {
                pause=TRUE; /* PS */
                t[p].use=0;
                /* the PS game will be stored (use=empty) */
                printf("PS at %lu",tic);
                ps=tic;
              }
              p=li.playernum; /* only one pause state change per tic */
            }
          }
        }
        if (allowput) for (p=0;p<li.playernum;p++) LMP_putgametic(&lo,&(t[p])); 
      }
    }
    else {
      copyopenfiles(li.file,lo.file);
      fseek(lo.file, -1, SEEK_END);  /* seek after the last tic */
    }
  }
  else {
    fseek(lo.file, -1, SEEK_END);    /* seek after the last tic */
  }

  if (opt->option&opAddWT) {
    for (tic=0;tic<wttics;tic++) LMP_putgametic(&lo, &WT);
  } 
  
  if (NewFile || (opt->option&opAddWT)) LMP_writequitbyte(&lo);
  LMP_done(&lo);
  if (NewFile) {
    LMP_done(&li);
    if (rename(TMP_FILE, outfilename)==-1) syserror(errno, outfilename);
  }
}

/*- file end lmpc.c ----------------------------------------------------------*/
