/* Main combat routines for Dungeon Master's Familiar
   by James Gary

Modification history:

   6-26-88  Fixed size determination error in damage calculation 
   7-1-88   Added saving throws
   7-1-88   Changed Attacker/Defender from temp records to cursors
            Previously editor changes not effective untill re-selection.
   7-9-88   Changed saving throws from gadgets to Inovatools 1 pop-up
            menus
   7-9-88   Added turning to pop-up menu
   7-12-88  Modified message handler for new roller functions
            (See roller.c for more detail)
*/

#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <intuition/intuition.h>
#include <clib/macros.h>
#include "character.h"
#include "dmf.h"

extern struct Character characters[];
int Attacker=0,Defender=0; /* cursors to att. & def. */
struct Window *wG; /* Main combat window */
struct MsgPort *MyPort; /* Use this because handling input from
                           multiple windows */

/* Cleric combat table */
int ClericTable[21][8] = {
    {25, 23, 21, 20, 20, 20, 19 },
    {24, 22, 20, 20, 20, 19, 18 },
    {23, 21, 20, 20, 20, 18, 17 },
    {22, 20, 20, 20, 19, 17, 16 },
    {21, 20, 20, 20, 18, 16, 15 },
    {20, 20, 20, 19, 17, 15, 14 },
    {20, 20, 20, 18, 16, 14, 13 },
    {20, 20, 19, 17, 15, 13, 12 },
    {20, 20, 18, 16, 14, 12, 11 },
    {20, 19, 17, 15, 13, 11, 10 },
    {20, 18, 16, 14, 12, 10,  9 },
    {19, 17, 15, 13, 11,  9,  8 },
    {18, 16, 14, 12, 10,  8,  7 },
    {17, 15, 13, 11,  9,  7,  6 },
    {16, 14, 12, 10,  8,  6,  5 },
    {15, 13, 11,  9,  7,  5,  4 },
    {14, 12, 10,  8,  6,  4,  3 },
    {13, 11,  9,  7,  5,  3,  2 },
    {12, 10,  8,  6,  4,  2,  1 },
    {11,  9,  7,  5,  3,  1,  0 },
    {10,  8,  6,  4,  2,  0, -1 } };

/* Fighter combat table */
int FighterTable[21][11] = {
    {26, 25, 23, 21, 20, 20, 20, 18, 16, 14 },
    {25, 24, 22, 20, 20, 20, 19, 17, 15, 13 },
    {24, 23, 21, 20, 20, 20, 18, 16, 14, 12 },
    {23, 22, 20, 20, 20, 19, 17, 15, 13, 11 },
    {22, 21, 20, 20, 20, 18, 16, 14, 12, 10 },
    {21, 20, 20, 20, 19, 17, 15, 13, 11,  9 },
    {20, 20, 20, 20, 18, 16, 14, 12, 10,  8 },
    {20, 20, 20, 19, 17, 15, 13, 11,  9,  7 },
    {20, 20, 20, 18, 16, 14, 12, 10,  8,  6 },
    {20, 20, 19, 17, 15, 13, 11,  9,  7,  5 },
    {20, 20, 18, 16, 14, 12, 10,  8,  6,  4 },
    {20, 19, 17, 15, 13, 11,  9,  7,  5,  3 },
    {19, 18, 16, 14, 12, 10,  8,  6,  4,  2 },
    {18, 17, 15, 13, 11,  9,  7,  5,  3,  1 },
    {17, 16, 14, 12, 10,  8,  6,  4,  2,  0 },
    {16, 15, 13, 11,  9,  7,  5,  3,  1, -1 },
    {15, 14, 12, 10,  8,  6,  4,  2,  0, -2 },
    {14, 13, 11,  9,  7,  5,  3,  2, -1, -3 },
    {13, 12, 10,  8,  6,  4,  2,  1, -2, -4 },
    {12, 11,  9,  7,  5,  3,  1,  0, -3, -5 },
    {11, 10,  8,  6,  4,  2,  0, -1, -4, -6 } };

/* Magic user combat table */
int MagicTable[21][6] = {
    {26, 24, 21, 20, 20 },
    {25, 23, 20, 20, 20 },
    {24, 22, 20, 20, 19 },
    {23, 21, 20, 20, 18 },
    {22, 20, 20, 19, 17 },
    {21, 20, 20, 18, 16 },
    {20, 20, 20, 17, 15 },
    {20, 20, 19, 16, 14 },
    {20, 20, 18, 15, 13 },
    {20, 20, 17, 14, 12 },
    {20, 19, 16, 13, 11 },
    {20, 18, 15, 12, 10 },
    {19, 17, 14, 11,  9 },
    {18, 16, 13, 10,  8 },
    {17, 15, 12,  9,  7 },
    {16, 14, 11,  8,  6 },
    {15, 13, 10,  7,  5 },
    {14, 12,  9,  6,  4 },
    {13, 11,  8,  5,  3 },
    {12, 10,  7,  4,  2 },
    {11,  9,  6,  3,  1 } };

/* Thief combat table */
int ThiefTable[21][7] = {
    {26, 24, 21, 20, 20, 20 },
    {25, 23, 20, 20, 20, 19 },
    {24, 22, 20, 20, 20, 18 },
    {23, 21, 20, 20, 19, 17 },
    {22, 20, 20, 20, 18, 16 },
    {21, 20, 20, 19, 17, 15 },
    {20, 20, 20, 18, 16, 14 },
    {20, 20, 19, 17, 15, 13 },
    {20, 20, 18, 16, 14, 12 },
    {20, 20, 17, 15, 13, 11 },
    {20, 19, 16, 14, 12, 10 },
    {20, 18, 15, 13, 11,  9 },
    {19, 17, 14, 12, 10,  8 },
    {18, 16, 13, 11,  9,  7 },
    {17, 15, 12, 10,  8,  6 },
    {16, 14, 11,  9,  7,  5 },
    {15, 13, 10,  8,  6,  4 },
    {14, 12,  9,  7,  5,  3 },
    {13, 11,  8,  6,  4,  2 },
    {12, 10,  7,  5,  3,  1 },
    {11,  9,  6,  4,  2,  0 } };

/* Monster combat table */
int MonsterTable[21][13] = {
    {26, 25, 24, 23, 21, 20, 20, 20, 20, 19, 18, 17 },
    {25, 24, 23, 22, 20, 20, 20, 20, 19, 18, 17, 16 },
    {24, 23, 22, 21, 20, 20, 20, 20, 18, 17, 16, 15 },
    {23, 22, 21, 20, 20, 20, 20, 19, 17, 16, 15, 14 },
    {22, 21, 20, 20, 20, 20, 19, 18, 16, 15, 14, 13 },
    {21, 20, 20, 20, 20, 20, 18, 17, 15, 14, 13, 12 },
    {20, 20, 20, 20, 20, 19, 17, 16, 14, 13, 12, 11 },
    {20, 20, 20, 20, 19, 18, 16, 15, 13, 12, 11, 10 },
    {20, 20, 20, 20, 18, 17, 15, 14, 12, 11, 10,  9 },
    {20, 20, 20, 19, 17, 16, 14, 13, 11, 10,  9,  8 },
    {20, 20, 19, 18, 16, 15, 13, 12, 10,  9,  8,  7 },
    {20, 19, 18, 17, 15, 14, 12, 11,  9,  8,  7,  6 },
    {19, 18, 17, 16, 14, 12, 11, 10,  8,  7,  6,  5 },
    {18, 17, 16, 15, 13, 11, 10,  9,  7,  6,  5,  4 },
    {17, 16, 15, 14, 12, 10,  9,  8,  6,  5,  4,  3 },
    {16, 15, 14, 13, 11,  9,  8,  7,  5,  4,  3,  2 },
    {15, 14, 13, 12, 10,  8,  7,  6,  4,  3,  2,  1 },
    {14, 13, 12, 11,  9,  7,  6,  5,  3,  2,  1,  0 },
    {13, 12, 11, 10,  8,  6,  5,  4,  2,  1,  0, -1 },
    {12, 11, 10,  9,  7,  5,  4,  3,  1,  0, -1, -2 },
    {11, 10,  9,  8,  6,  5,  3,  2,  0, -1, -2, -3 } };

/* Undead turning table */
int TurnTable[13][10] = {
    {10, 7, 4, 0, 0,-1,-1,-2,-2,-2},  /* 0 => automatic turn */
    {13,10, 7, 0, 0,-1,-1,-2,-2,-2},  /*-1 => automatic death*/
    {16,13,10, 4, 0, 0,-1,-1,-1,-2},  /*-2 => auto death 7-12*/
    {19,16,13, 7, 4, 0, 0,-1,-1,-2},
    {20,19,16,10, 7, 4, 0, 0,-1,-1},
    {21,20,19,13,10, 7, 4, 0, 0,-1},
    {21,21,20,16,13,10, 7, 4, 0,-1},
    {21,21,21,20,16,13,10, 7, 4, 0},
    {21,21,21,21,20,16,13,10, 7, 0},
    {21,21,21,21,21,20,16,13,10, 4},
    {21,21,21,21,21,21,20,16,13, 7},
    {21,21,21,21,21,21,21,19,16,10},
    {21,21,21,21,21,21,21,20,19,13} };

extern long IntuitionBase;
extern long GfxBase;
long MathBase;
long LayersBase;

/* Do saving throw */
SaveRoll(num)
int num;
{
     int roll,needed,len;
     char buff[80];

     roll = random(20);
     needed = characters[Attacker].Saves[num];
     if (roll >= needed)
          len = sprintf(buff,"%15s needs %2d, rolls %2d, saved!                      ",
                characters[Attacker].Name,needed,roll);
     else
          len = sprintf(buff,"%15s needs %2d, rolls %2d, failed!                     ",
                characters[Attacker].Name,needed,roll);
     Move(wG->RPort,9,195);
     SetAPen(wG->RPort,1); /* Contrasting color for visibility*/
     Text(wG->RPort,buff,len);
     SetAPen(wG->RPort,3); /* Reset color */
     return(0);
}

/* Do undead turning */
TurnRoll(row)
int row;
{
     int roll,needed,len,level,col=0;
     char buff[80];

     level = characters[Attacker].Level;
     if (level < 9)
          col = level - 1;
     else if (level < 14) 
          col = 8;
     else if (level > 13)
          col = 9;
     roll = random(20);
     needed = TurnTable[row][col];
     if (needed == 21) 
         len = sprintf(buff,"%15s needs %2d, rolls %2d, failed!                      ",
               characters[Attacker].Name,needed,roll);
     else if (needed == -1)
         len = sprintf(buff,"%15s kills %2d!                                         ",
               characters[Attacker].Name,random(12));
     else if (needed == -2)
         len = sprintf(buff,"%15s kills %2d!                                         ",
               characters[Attacker].Name,random(6)+6);
     else if (roll < needed)
         len = sprintf(buff,"%15s needs %2d, rolls %2d, failed!                      ",
               characters[Attacker].Name,needed,roll);
     else
          if (row < 12)
                len = sprintf(buff,"%15s needs %2d, rolls %2d, turned %2d                   ", 
                      characters[Attacker].Name,needed,roll,random(12));
          else
                len = sprintf(buff,"%15s needs %2d, rolls %2d, turned %2d                   ",
                      characters[Attacker].Name,needed,roll,random(2));
 
     Move(wG->RPort,9,195);
     SetAPen(wG->RPort,1); /* Contrasting color for visibility*/
     Text(wG->RPort,buff,len);
     SetAPen(wG->RPort,3); /* Reset color */
     return(0);
}

/* Handle a pop-up menu event */
void HandleMenu(num)
USHORT num;
{
     switch (ITEMNUM(num)) {
         case 0 : TurnRoll(SUBNUM(num));
                  break;
         case 2 : SaveRoll(SUBNUM(num));
                  break;
         }
}
     
/* Determine fighter to-hit number */
fighter(level,ac)
int level; /* Level of fighter */
int ac; /* AC of defender */
{
    int row,col;

    if (level <= 0)           /* This mess determines proper row and column */
        col = 0;              /*  |                                         */
    else                      /*  |                                         */
        col = (level + 1)/2;  /*  | (Advances by twos)                      */
    if (col > 9)              /*  | (up to column nine)                     */
        col = 9;              /*  |                                         */
    row = 10 + ac;            /*  | (Ac 10 -> -10)                          */
    if (row < 0)              /*  | Handle AC's off table                   */
        row = 0;              /*  |                                         */
    if (row > 19)             /*  |                                         */
        row = 19;             /*  |_________________________________________*/
    return(FighterTable[row][col]);
}

/* Determine cleric to-hit number */
cleric(level,ac)
int level; /* Level of attacker */
int ac;    /* AC of defender */
{
    int row,col;

    col = (level + 2)/3 - 1;
    if (col < 0)
        col = 0;
    if (col > 6)
        col = 6;
    row = 10 + ac;
    if (row < 0)
        row = 0;
    if (row > 19)
        row = 19;
    return(ClericTable[row][col]);
}

/* Determine thief to-hit number */
thief(level,ac)
int level; /* Level of thief */
int ac;    /* AC of defender */
{
    int row,col;

    col = (level + 3)/4 -1;
    if (col < 0)
        col = 0;
    if (col > 5)
        col = 5;
    row = 10 + ac;
    if (row < 0)
        row = 0;
    if (row > 19)
        row = 19;
    return(ThiefTable[row][col]);
}

/* Determine magic user to-hit number */
magic(level,ac)
int level; /* Level of attacker */
int ac;    /* AC of defender */
{
    int row,col;

    col = (level + 4)/5 - 1;
    if (col < 0)
        col = 0;
    if (col > 4)
        col = 4;
    row = 10 + ac;
    if (row < 0)
        row = 0;
    if (row > 19)
        row = 19;
    return(MagicTable[row][col]);
}

/* Determine monster to-hit number */
monster(level,ac)
int level; /* Level of monster */
int ac;    /* AC of defender */
{
    int row,col;

    col = level - 1;
    if (col < 0)
        col = 0;
    if (col > 11)
        col = 11;
    row = 10 + ac;
    if (row < 0)
        row = 0;
    if (row > 19)
        row = 19;
    return(MonsterTable[row][col]);
}

/* User selected reverse gadget, so switch attacker and defender
   and call repeat */
Reverse(gad)
struct Gadget *gad;
{
        int temp;
        int Repeat();

        temp = Attacker;
        Attacker = Defender;
        Defender = temp;
        Repeat(NULL);
	return(0);
}

/* User selected repeat gadget or selected defender, so handle combat */
Repeat(gad)
struct Gadget *gad;
{
        int len,ToHit,Roll,NumDice,TypeDie,DamPlus,Damage,i;
        char buf[80]; /* to hold result string */

        switch(characters[Attacker].Job) { /* get proper to-hit number */
                case(FIGHTER) : ToHit = fighter(characters[Attacker].Level,characters[Defender].AC);
                                break;
                case(CLERIC) : ToHit = cleric(characters[Attacker].Level,characters[Defender].AC);
                               break;
                case(MAGICUSER) : ToHit = magic(characters[Attacker].Level,characters[Defender].AC);
                                  break;
                case(THIEF) : ToHit = thief(characters[Attacker].Level,characters[Defender].AC);
                              break;
                case(MONSTER) : ToHit = monster(characters[Attacker].Level,characters[Defender].AC);
                                break;
                }

        if (characters[Defender].Size == SMALL) { /* Adjust vars for small defender */
                ToHit += characters[Attacker].SmHitPlus;
                NumDice = characters[Attacker].SmNumDie;
                TypeDie = characters[Attacker].SmTypeDie;
                DamPlus = characters[Attacker].SmDamPlus;
        }
        else { /* Adjust vars for large defender */
                ToHit += characters[Attacker].LgHitPlus;
                NumDice = characters[Attacker].LgNumDie;
                TypeDie = characters[Attacker].LgTypeDie;
                DamPlus = characters[Attacker].LgDamPlus;
        }

        Roll = random(20); /* Roll dem bones! */
        Damage = 0;
        if (Roll >= ToHit) { /* Contact! */
                for (i=0;i<NumDice;i++)
                        Damage += random(TypeDie);
                Damage += DamPlus;
                }

        len = sprintf(buf,"%15s vs. %15s needs %2d, rolls %2d, for %3d   ",
              characters[Attacker].Name,characters[Defender].Name,ToHit,Roll,Damage);
        Move(wG->RPort,9,195);
        SetAPen(wG->RPort,1); /* Contrasting color for visibility*/
        Text(wG->RPort,buf,len);
        SetAPen(wG->RPort,3); /* Reset color */
	return(0);
}

/* Display character names on main window */
PrintChars()
{
     int len,row,col,i;
     char buf[20];

     i = 0;
     for (row=0; row < 15; row++) {
          for (col = 0; col < 4; col++) {
               len = sprintf(buf,"%15s",characters[i++].Name);
               Move(wG->RPort,3+160*col,10*row+18);
               Text(wG->RPort,buf,len);
          }
     }
     return(0);
}
void PrintOne(i,fp,bp)
int i;
UBYTE fp,bp;
{
     int row,col,len;
     char buf[20];

     row = i/4;
     col = i%4;
     len = sprintf(buf,"%15s",characters[i].Name);
     SetAPen(wG->RPort,fp);
     SetBPen(wG->RPort,bp);
     Move(wG->RPort,3+160*col,10*row+18);
     Text(wG->RPort,buf,len);
     SetAPen(wG->RPort,3);
     SetBPen(wG->RPort,0);
}
USHORT quit_flag = FALSE;

/* This is for the event handler */
void quit(object)
APTR object;
{
        quit_flag = TRUE;
	return(0);
}


struct Window *OpenWindow();
struct Screen *OpenScreen();

/* User selected attacker with Left Mouse button, determine which */
SetAttacker(x,y)
int x,y;
{
        int row,col,charnum;

        PrintOne(Attacker,3,0);
        row = (y-12)/10;
        col = (x/160);
        charnum = row*4+col;
        charnum = MAX(charnum,0);           /* Make sure that */
        charnum = MIN(charnum,MAXCHARS-1);  /* we are in range*/
        Attacker = charnum;
	PrintOne(Attacker,1,3);
        return(0);
}

/* User selected defender with Right Mouse button, determine which */
SetDefender(x,y)
int x,y;
{
        int row,col,charnum;

        row = (y-12)/10;
        col = (x/160);
        charnum = row*4+col;
        charnum = MAX(charnum,0);          /* Make sure that  */
        charnum = MIN(charnum,MAXCHARS-1); /* we are in range */
        Defender = charnum;
	return(0);
}

/* The main program was adapted from PW's example program and contains
   some unused leftovers from that code. Sorry. */
main()
{
        UWORD code;
        ULONG class;
        APTR object;
        int x,y;    /* for mouse button message */

        struct Gadget *gad;
        extern struct Window *MyWindow; /* Roller window */
        struct Window *win;             /* Main combat window */
        struct RastPort *rpG;
        struct IntuiMessage *message;   /* the message the IDCMP sends us */
        USHORT num;
        extern int Total;
        struct StringInfo *sinfo;
        long last;

        IntuitionBase = OpenLibrary("intuition.library", 0);
        if (IntuitionBase == NULL)
        {
                printf("intuition is not here.  where are we?\n");
                goto cleanup1;
        }
        LayersBase = OpenLibrary("layers.library",0);
        GfxBase = OpenLibrary("graphics.library", 0);
        MathBase = OpenLibrary("mathffp.library",0);
   
           /* Must set up own port to handle input from multiple windows*/
        MyPort = (struct MsgPort *) CreatePort("CombatPort",0); 
        wG = OpenWindow(&NewWindowStructure1);  /* open the window */
        if ( wG == NULL )
        {
                printf ("open window failed\n");
                goto cleanup1;
        }
        wG->UserPort = MyPort; /* Attach to port*/

          /* To facilitate user port, no IDCMP were specified in NewWindow*/
          /* so add the ones we want now. */
        ModifyIDCMP(wG,MOUSEBUTTONS|GADGETDOWN|GADGETUP|CLOSEWINDOW);
        
        rpG = wG->RPort;        /* get a rastport pointer for the window */
         
        InitChars(); /* Initialize character array*/

        PrintChars(); /* Display character names in main window */

        PrintOne(Attacker,1,0); /*Highlight Attacker*/
#ifdef IntuiTextList1
        PrintIText(rpG,&IntuiTextList1,0,0);    /* Print the text if there is
                                                any */
#endif

#ifdef BorderList1
        DrawBorder(rpG,&BorderList1,0,0);       /* Draw the borders if there are
                                                any */
#endif

#ifdef ImageList1
        DrawImage(rpG,&ImageList1,0,0); /* Draw the images if there are any */
#endif

  do {
     WaitPort(wG->UserPort);
     while( (message = (struct IntuiMessage *) GetMsg(wG->UserPort)) != NULL)
         {
         code = message->Code;  /* MENUNUM */
         object = message->IAddress;  /* Gadget */
         class = message->Class;
         x = message->MouseX;
         y = message->MouseY;
         win = message->IDCMPWindow;
         ReplyMsg(message); /* All good stuff is recorded, make Intuition :) */
         
         /* User did something, no figure out what */
         if (win == wG) {  /* Main window event!*/
            if ( class == CLOSEWINDOW ) (quit_flag = TRUE);
            if (( class == GADGETDOWN ) ||
                  (class == GADGETUP)) {
                        if (object == (APTR) &ExtraGad) {
                             num = PopUpMenu(wG,&Menu1);
                             HandleMenu(num);
                        }
                        else 
                             HandleEvent(object);
                 }   
            if ( class == MOUSEBUTTONS ){
               if (code == SELECTUP)
                  SetAttacker(x,y); /* Left button means attacker */
               if (code == MENUUP) {
                  SetDefender(x,y); /* Right button means defender */
                  Repeat(); /* Resolve combat */
               }
            }
         }
         else {  /* Must be roller window!*/
            switch(class) {
               case CLOSEWINDOW: cleanup();
                                 break;
               case GADGETDOWN:
               case GADGETUP:  gad = (struct Gadget *) object;
                        switch((int)gad->GadgetID) {
/* Gadget ID's : */         case 25:
/* 25: UserNum   */                  sinfo = (struct StringInfo *)gad->SpecialInfo;
/* 50: Again     */                  last = sinfo->LongInt;
/* 75: ClearTotal*/                  display(last);
                                     break;
                            case 50: display(last);
                                     break;
                            case 75: Total = 0;
                                     DisplayTotal();
                                     break;
                            default: display((int)gad->GadgetID); /* Roll 'em */
                                     break;
                        }
                        break;
               default:  cleanup();
                         break;
            }
         }
      }
   } while (quit_flag == FALSE);

cleanup3:

cleanup2:
        wG->UserPort = NULL;
        CloseWindow(wG);
	cleanup();
        DeletePort(MyPort);
cleanup1:
        if (MathBase != NULL) CloseLibrary(MathBase);
        if (GfxBase != NULL) CloseLibrary(GfxBase);
        if (LayersBase != NULL) CloseLibrary(LayersBase);
        if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
        return(0);

}
