#include "stdafx.h"

#include <stdio.h>

#include "UI.h"
#include "Objects.h"
#include "Dispatch.h"
#include "CSB.h"
#include "Data.h"
void WarnIllegalTimer(i32 srcX, i32 srcY, i32 dstX, i32 dstY);
void SelectOverlay(i32, i32, i32, i32, i32);
void info(char *, unsigned int);


ui32 DSAparameters[100];

static char *colNames[12] =
{
  "S0","C0","T0","S1","C1","T1","S2","C2","T2","S3","C3","T3"
};

struct EXECUTIONPACKET 
{
  i32         m_nextState;      //The prefix '5' in "5MS"
  ui16       *m_PC;             //Updated
  i32         m_wordsRemaining; //Updated
  DB3        *m_pDBMaster;      //Unchanged
  DSA        *m_pMaster;        //Unchanged
  i32         m_curState;       //Unchanged
  i32         m_column;         //Unchanged
  LOCATIONREL m_locrMaster;     //Unchanged
  LOCATIONREL m_locrSlave;      //Unchanged
  i32         m_transferState;  //-1 unless Jump
  i32         m_transferCol;    //-1 unless Jump
  i32         m_subroutineLevel;//Unchanged
  RN          m_RNmaster;       //Unchanged
};


struct LOCATIONABS
{
  i32 l;
  i32 x;
  i32 y;
  i32 p;
public:
  LOCATIONABS& operator =(LOCATIONREL& locr);
};

//**********  RUNTIME DEBUGGING HELP **********
EXECUTIONPACKET *pExPkt; //only for debugging purposes
static char *constructDebugMessage(const char *msg)
{
  static char debugMessage[300];
  sprintf(debugMessage,"%s\nDSA at %d(%d,%d)\nRow=%d;Col=%d",
          msg,
          pExPkt->m_locrMaster.l,
          pExPkt->m_locrMaster.x,
          pExPkt->m_locrMaster.y,
          pExPkt->m_curState,
          pExPkt->m_column);
  return debugMessage;
}

//**********  RUNTIME DEBUGGING HELP **********

ui16 LOCATIONREL::Integer(void)
{
  ASSERT(x < 32);
  ASSERT(y < 32);
  ASSERT(l < 16);
  ASSERT(p < 4);
  return (ui16)((p<<14)|(l<<10)|(x<<5)|y);
}

void LOCATIONREL::Integer(i32 t)
{
  p = (t >> 14) & 3;
  l = (t >> 10) & 15;
  x = (t >> 5) & 31;
  y = t & 31;
}

bool LOCATIONREL::IsValid(void)
{
  if (x > d.pLevelDescriptors[l].LastColumn()) return false;
  if (y > d.pLevelDescriptors[l].LastRow()) return false;
  return true;
}

LOCATIONABS& LOCATIONABS::operator =(LOCATIONREL& locr)
{
  l = locr.l;
  x = locr.x + d.pLevelDescriptors[l].offsetX;
  y = locr.y + d.pLevelDescriptors[l].offsetY;
  p = locr.p;
  return *this;
}

static void PrintTrace(char *text)
{
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"%s",text);
  };
}


class STACK
{
  i32 stack[100];
  ui32 stacklen;
public:
  i32 tempVar[100];
  STACK(void);
  ~STACK(void);
  void Clear(void){stacklen=0;};
  void push(LOCATIONREL locr);
  void push(i32 i);
  i32 pop(void);
  void pick(ui32 i);
  void poke(ui32 i);
  void roll(ui32 i);
  void minusRoll(ui32 i);
  char *underflowMsg(void);
  void dump(void);
};

STACK::STACK(void)
{
  stacklen = 0;
}

STACK::~STACK(void)
{
}

char *STACK::underflowMsg(void)
{
  return constructDebugMessage("DSA stack underflow");
}

void STACK::dump(void)
{
  i32 i, n;
  if (TimerTraceActive)
  {
    if (stacklen > 7) PrintTrace(" ... ");
    n = 7;
    if (stacklen < 7) n= stacklen;
    for (i=n; i>0; i--)
    {
      fprintf(GETFILE(TraceFile)," %d",stack[stacklen-i]);
    };
  };
}

void STACK::push(LOCATIONREL locr)
{
  push(locr.Integer());
}

void STACK::push(i32 i)
{
  if (stacklen > 99)
  {
    die(0xdcd6);
  };
  stack[stacklen++] = i;
}

i32 STACK::pop(void)
{
  if (stacklen < 1)
  {
    die(0xdcd7,underflowMsg());
  };
  return stack[--stacklen];
}

void STACK::pick(ui32 i)
{
  if (stacklen < i+1) die(0xdcdb,underflowMsg());
  push(stack[stacklen-1-i]);
}

void STACK::poke(ui32 i)
{
  ui32 v;
  v = pop();
  if (stacklen < i) die(0xdcdb,underflowMsg());
  stack[stacklen-i] = v;
}

void STACK::roll(ui32 i)
{
  ui32 temp;
  ui32 j;
  if (stacklen < i+1)
  {
    die(0xdcda,underflowMsg());
  };
  j = stacklen-i-1;
  temp = stack[j];
  for (; j<stacklen-1; j++) stack[j] = stack[j+1];
  stack[j] = temp;
}

void STACK::minusRoll(ui32 i)
{
  ui32 temp;
  ui32 j, k;
  if (stacklen < i+1)
  {
    die(0xdcda,underflowMsg());
  };
  j = stacklen-1;
  k = j-i;
  temp = stack[j];
  for (; j>k; j--) stack[j] = stack[j-1];
  stack[j] = temp;
}

static STACK stack;

static i32 Execute(RN  RNmaster,
                   DB3 *pDBMaster,
                   DSA *pMaster, 
                   DSA *pSlave, 
                   i32 curState, 
                   i32 msgType,
                   LOCATIONREL locrMaster,
                   LOCATIONREL locrSlave,
                   i32 subroutineLevel);

static i32 warningCount = 3;
static bool Override_P = false;
static i32 Override_Pos;

static void message(char *msg)
{
  warningCount--;
  if (warningCount < 0) return;
  UI_MessageBox(constructDebugMessage(msg), "Warning", MESSAGE_OK);
}

//*********************************************************
// All of this copied from QueueSwitchAction and CreateTimer
// Que timer as shown in table below
//       If target room is not stone position = 0
//       If target room is stone position = targetPos;
//  ---------------------------------------------------
//    target room    timerFunction
//     0=roomSTONE           6          set/clear bits in DB2 and DB3
//                                      If actuator type 14 is in the
//                                      room then an object is ejected
//                                      from the room as a missile.
//     1=roomOPEN            5          ????????
//     2=roomPIT             9          ????????
//     3=roomSTAIRS          0          ????????
//     4=roomDOOR           10          ????????
//     5=roomTELEPORTER      8          set/clear room-active  (0x08)
//     6=roomFALSEWALL       7          set/clear wall invisible (0x04)
//     7=unused              0          ????????
//*********************************************************
//   TAG012cb0
void QueueDSASwitchAction(
                   i32 delay,        //
                   i32 tmrAct,       //Action set/clear/toggle
                   i32 level,        //
                   i32 targetX,i32 targetY,
                   i32 targetPos)
{
  i32 actionTime;
  i32 numericFunction;
  TIMERTYPE tt;
  CELLFLAG cfD4;
  TIMER timer;
  ROOMTYPE targetRoomType;
  actionTime = d.Time + delay;
  cfD4 = d.pppdPointer10450[level][targetX][targetY]; //Cellflag
  targetRoomType = (ROOMTYPE)(cfD4>>5); // roomType
  if (   (targetRoomType != roomSTONE)
      && (targetRoomType != roomFALSEWALL))targetPos = 0;
  //CreateTimer((TIMERFUNCTION)d.Byte1596[targetRoomType],
  //            targetX,
  //            targetY,
  //            targetPos,
  //            tmrAct,   //tmrAct_SET, _CLEAR, or _TOGGLE
  //            actionTime);
//void CreateTimer(TIMERFUNCTION function,
//                 i32 b6 = targetx,
//                 i32 b7 = targety,
//                 i32 b8 = targetPos,
//                 i32 b9 = messageType,
//                 i32 time)
  timer.timerTime = (level << 24) | actionTime;
  numericFunction = d.Byte1596[targetRoomType];
  switch (numericFunction)
  {
  case 7: tt = TT_FALSEWALL; break;
  default: tt = (TIMERTYPE)numericFunction; break;
  };
  if (tt == 0) 
  {
    WarnIllegalTimer(-1, -1, targetX, targetY);
    return;
  };
  timer.Function(tt);
  timer.timerUByte5 = 0;
  timer.timerUByte6 = (ui8)targetX;
  timer.timerUByte7 = (ui8)targetY;
  timer.timerUByte8 = (ui8)targetPos;
  timer.timerUByte9 = (ui8)tmrAct;
  SetTimer(&timer);
}

static RN FindMaster(DSA *pSlave, RN objSlave, i32 x, i32 y,
                             i32 *masterX, i32 *masterY,
                             i32 *masterPos) 
{
  if (pSlave->IsMaster()) 
  {
    *masterX = x;
    *masterY = y;
    *masterPos = objSlave.pos();
    return objSlave;
  };
  message("Slave DSA Not Implemented");
  return objSlave;
}

static i32 GetState(DB3 *pDBMaster, DSA *pMaster)
{
  switch (pMaster->LocalState())
  {
  case 0: return pDBMaster->DSAstate();
  case 1: return pMaster->State();
  case 2: return pDBMaster->ParameterB();
  default:
    message("Illegal DSA LocalState");
    return 0;
  };
}

static void PutState(DB3 *pDBMaster, DSA *pMaster, i32 state)
{
  switch (pMaster->LocalState())
  {
  case 0: pDBMaster->DSAstate((ui8)state); break;
  case 1: pMaster->State(state); break;
  case 2: pDBMaster->ParameterB(state); break;
  default:
    message("Illegal DSA LocalState");
  };
}

static void EX_NOOP(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  DSAnoopCmd cmd;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->noopCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
}

LOCATIONREL GetTarget(TARGETTYPE targetType,
                      EXECUTIONPACKET& exPkt,
                      bool thirtyTwo)
{
  LOCATIONREL result;
  ui32 target;
  switch (targetType)
  {
  case TARGET_A:
    target = exPkt.m_pDBMaster->ParameterA();
    break;
  case TARGET_B:
    target = exPkt.m_pDBMaster->ParameterB();
    break;
  case TARGET_ABS:
    target = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
    if (thirtyTwo)
    {
      target |= (*(exPkt.m_PC++)) << 16;
      exPkt.m_wordsRemaining--;
    };
    break;
  case TARGET_GEAR:
    target = stack.pop();
//    target =  (ui16)((locrMaster.l & 0xf) << 10);
//    target |= (locrMaster.p & 3) << 14;
//    target |= (locrMaster.x &0x1f) <<5;
//    target |= locrMaster.y & 0x1f;
    break;
  default:
    message("Illegal Target Type in DSA");
    target = 0;
  };
  result.l = (target>>10) & 0xf;
  result.p = (target>>14) & 3;
  result.x = (target>>5)  & 0x1f;
  result.y = target & 0x1f;
  if (Override_P) result.p = Override_Pos;
  Override_P = false;
  return result;
}

static void EX_MESSAGE(EXECUTIONPACKET& exPkt, bool thirtyTwo)
{
  i32 relState;
  i32 action;
  LOCATIONREL locr;
  DSAmessageCmd cmd;
  i32 delay=0;  //To keep compiler happy.
  MESSAGETYPE messageType;
  TIMER timer;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->msgCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  messageType = cmd.MsgType();
  switch (cmd.Delay())
  {
  case 0: delay = 0; break;
  case 1: delay = exPkt.m_pDBMaster->ParameterA(); break;
  case 2: delay = exPkt.m_pDBMaster->ParameterB(); break;
  case 3: delay = *(exPkt.m_PC++); exPkt.m_wordsRemaining--; break;
  };
//  locr = GetTarget(cmd.Target(),
//                   PC,
//                   wordsRemaining,
//                   RNmaster,
//                   pDBMaster,
//                   pMaster,
//                   locrMaster);
  locr = GetTarget(cmd.Target(), exPkt, thirtyTwo);
  switch (messageType)
  {
  case MSG_NULL: 
    if (TimerTraceActive)
    {
      PrintTrace("No Message");
    };
    return;
  case MSG_SET: 
    if (TimerTraceActive)
    {
      PrintTrace("SET");
    };
    action = tmrAct_SET; 
    break;
  case MSG_CLEAR: 
    if (TimerTraceActive)
    {
      PrintTrace("CLEAR");
    };
    action = tmrAct_CLEAR; 
    break;
  case MSG_TOGGLE: 
    if (TimerTraceActive)
    {
      PrintTrace("TOGGLE");
    };
    action = tmrAct_TOGGLE; 
    break;
  default: 
    if (TimerTraceActive)
    {
      PrintTrace("UNKNOWN");
    };
    message("Unknown DSA messageType"); 
    return;
  };
  if (TimerTraceActive)
  {
    LOCATIONABS loca;
    loca = locr;
    fprintf(GETFILE(TraceFile)," delay=%d, %d(%d,%d)%d",
            delay,loca.l,loca.x,loca.y,loca.p);
  };
  QueueDSASwitchAction(
                   delay,        //
                   action,       //Action set/clear/toggle
                   locr.l,       //level
                   locr.x,
                   locr.y,
                   locr.p);

  return;
}

static void EX_COPYTELEPORTER(EXECUTIONPACKET& exPkt, bool thirtyTwo)
{
  RN RNsrc, RNdst;
  CELLFLAG *pCFsrc, *pCFdst;
  DB1 *pDB1Src, *pDB1Dst;
  i32 relState;
  LOCATIONREL locrSrc, locrDst;
  DSAcopyTeleporterCmd cmd;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->copyTeleporterCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  locrSrc = GetTarget(cmd.TargetFrom(), exPkt, thirtyTwo);
  locrDst = GetTarget(cmd.TargetTo(), exPkt, thirtyTwo);
  //swap locrsrc and locrdst
  RNsrc = FindFirstTeleporter(locrSrc.l,locrSrc.x,locrSrc.y);
  pCFsrc = GetCellFlagsAddress(locrSrc.l,locrSrc.x,locrSrc.y);
  RNdst = FindFirstTeleporter(locrDst.l,locrDst.x,locrDst.y);
  pCFdst = GetCellFlagsAddress(locrDst.l,locrDst.x,locrDst.y);
  if (RNsrc == RNeof) return;
  if (RNdst == RNeof) return;
  pDB1Src = GetRecordAddressDB1(RNsrc);
  pDB1Dst = GetRecordAddressDB1(RNdst);
  pDB1Dst->copyTeleporter(pDB1Src);
  *pCFdst = *pCFsrc;
  return;
}

static void EX_GOSUB(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 row, column;
  DSAgosubCmd cmd;
  TIMER timer;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->gosubCmd;
  exPkt.m_wordsRemaining--;
  row = cmd.Row();
  if (row == 63)
  {
    row = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  };
  column = cmd.Column();
  if (column != 0)
  {
    column = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  };
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"%d%s\n",row,colNames[column]);
  };
  Execute(exPkt.m_RNmaster,
          exPkt.m_pDBMaster,
          exPkt.m_pMaster,
          exPkt.m_pMaster,
          row,
          column,
          exPkt.m_locrMaster,
          exPkt.m_locrSlave,
          exPkt.m_subroutineLevel+1);
  return;
}

static void EX_JUMP(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 row, column;
  DSAjumpCmd cmd;
  //TIMER timer;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->jumpCmd;
  exPkt.m_wordsRemaining--;
  row = cmd.Row();
  if (row == 63)
  {
    row = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  };
  exPkt.m_transferState = row;
  column = cmd.Column();
  if (column != 0)
  {
    column = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  };
  exPkt.m_transferCol = column;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile)," Jump %d%s",
              exPkt.m_transferState, colNames[exPkt.m_transferCol]);
  };
}

static void EX_QUESTION(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  DSAquestionCmd cmd;
  TIMER timer;
  i32 cCmd, cCol, addr;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->questionCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  if (stack.pop())
  {
    if (TimerTraceActive)
    {
      PrintTrace("(true)");
    };
    cCmd = cmd.IfCmd();
    cCol = 0;
    addr = 0;
    if (cCmd != 0) 
    {
      addr = *(exPkt.m_PC++); 
      exPkt.m_wordsRemaining--;
    };
    if (cmd.IfColumn() != 0) 
    {
      cCol = *(exPkt.m_PC++);
      exPkt.m_wordsRemaining--;
    };
    if (cmd.ElseCmd() != 0) 
    {
      exPkt.m_PC++; 
      exPkt.m_wordsRemaining--;
    };
    if (cmd.ElseColumn() != 0)
    {
      exPkt.m_PC++; 
      exPkt.m_wordsRemaining--;
    };
  }
  else
  {
    if (TimerTraceActive)
    {
      PrintTrace("(false)");
    };
    if (cmd.IfCmd() != 0) 
    {
      exPkt.m_PC++; 
      exPkt.m_wordsRemaining--;
    };
    if (cmd.IfColumn() != 0)
    {
      exPkt.m_PC++; 
      exPkt.m_wordsRemaining--;
    };
    cCmd = cmd.ElseCmd();
    cCol = 0;
    addr = 0;
    if (cCmd != 0) 
    {
      addr = *(exPkt.m_PC++); 
      exPkt.m_wordsRemaining--;
    };
    if (cmd.ElseColumn() != 0) 
    {
      cCol = *(exPkt.m_PC++);
      exPkt.m_wordsRemaining--;
    };
  };
  switch (cCmd)
  {
  case 1:
    exPkt.m_transferState = addr;
    exPkt.m_transferCol = cCol;
    if (TimerTraceActive)
    {
      fprintf(GETFILE(TraceFile)," Jump %d%s",
                addr, colNames[cCol]);
    };
    return; 
  case 2:
    // Gosub
    if (TimerTraceActive)
    {
      fprintf(GETFILE(TraceFile)," GoSub %d,%d",
                addr, colNames[cCol]);
    };
    Execute(exPkt.m_RNmaster,
            exPkt.m_pDBMaster,
            exPkt.m_pMaster,
            exPkt.m_pMaster,
            addr,
            cCol,
            exPkt.m_locrMaster,
            exPkt.m_locrSlave,
            exPkt.m_subroutineLevel+1);
    break;
  default:
    if (TimerTraceActive)
    {
      PrintTrace(" Do Nothing");
    };
    break;
  };
  return;
}

static void EX_CASE(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  DSAcaseCmd cmd;
  TIMER timer;
  i32 col, addr;
  i32 value;
  ui32 numCase, n, m;
  i32 *pCase;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->caseCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  value = stack.pop();
  numCase = *(exPkt.m_PC++);
  exPkt.m_wordsRemaining--;
  //Search the cases for value
  pCase = (i32 *)exPkt.m_PC;
  for (n=numCase; n>0;)
  {
    m=n/2;
    if (pCase[2*m] == value)
    {
      addr = (pCase[2*m+1] >> 8) & 0xffff;
      col  = pCase[2*m+1] & 0xff;
      exPkt.m_transferState = addr;
      exPkt.m_transferCol = col;
      if (TimerTraceActive)
      {
        fprintf(GETFILE(TraceFile)," Jump %d%s",
                  addr, colNames[col]);
      };
      break;
    };
    if (pCase[2*m] > value)
    {
      n = m;
    }
    else
    {
      pCase += 2*(m+1);
      n -= m+1;
    };
  };
  exPkt.m_PC += 4*numCase;
  exPkt.m_wordsRemaining -= 4*numCase;
  return;
}

static void EX_OVERRIDE(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 what;
  DSAoverrideCmd cmd;
  TIMER timer;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->overrideCmd;
  exPkt.m_wordsRemaining--;
  what = cmd.What();
  switch(what)
  {
  case 1:
    Override_P = true;
    Override_Pos = cmd.Value();
    break;
  default:
    message("Illegal DSA override command");
    return;
  };
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  return;
}  
  
static void EX_LOAD(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 where;
  RN RNparameter;
  DB3 *pParameter;
  DSAloadCmd cmd;
  TIMER timer;
  LOCATIONREL locr;
  LOCATIONABS loca;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->loadCmd;
  exPkt.m_wordsRemaining--;
  where = cmd.Where();
  switch(where)
  {
  case LOAD_INTEGER:
    if (TimerTraceActive)
    {
      fprintf(GETFILE(TraceFile),"INTEGER %d ",*exPkt.m_PC);
    };
    stack.push(*(exPkt.m_PC++));
    exPkt.m_wordsRemaining--;
    break;
  case LOAD_ABS:
    locr.Integer(*exPkt.m_PC);
    loca = locr;
    if (TimerTraceActive)
    {
      fprintf(GETFILE(TraceFile),"ABSLOCATION %d(%d,%d)%d",
            loca.l, loca.x, loca.y,loca.p);
    };
    stack.push(*(exPkt.m_PC++));
    exPkt.m_wordsRemaining--;
    break;
  case LOAD_DOLLAR: //L$
    if (TimerTraceActive)
    {
      PrintTrace("DOLLAR ");
    };
    stack.push(exPkt.m_locrMaster);
    break;
  default:
    if (where > 25)
    {
      message("Illegal DSA load command");
      return;
    };
    RNparameter = exPkt.m_RNmaster;
    if (TimerTraceActive)
    {
      fprintf(GETFILE(TraceFile),"Parameter %c = ",where+'A');
    };
    while (where > 1)
    {
      RNparameter = GetDBRecordLink(RNparameter);
      if (RNparameter == RNeof) break;
      if (RNparameter.dbType() != dbACTUATOR) continue;
      pParameter = GetRecordAddressDB3(RNparameter);
      if (pParameter->actuatorType() != 47) continue;
      where -= 2;
    };
    if (RNparameter == RNeof) 
    {
      if (TimerTraceActive)
      {
        PrintTrace("Missing = 0");
      };
      stack.push(0);
    }
    else
    {
      pParameter = GetRecordAddressDB3(RNparameter);
      if (where == 0) 
      {
        stack.push(pParameter->ParameterA());
        if (TimerTraceActive)
        {
          fprintf(GETFILE(TraceFile),"%d",pParameter->ParameterA());
        };
      }
      else 
      {
        stack.push(pParameter->ParameterB());
        if (TimerTraceActive)
        {
          fprintf(GETFILE(TraceFile),"%d",pParameter->ParameterB());
        };
      };
    };
  };
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  return;
}  
  
static void EX_STORE(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 where;
  RN RNparameter;
  DB3 *pParameter;
  DSAstoreCmd cmd;
  TIMER timer;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->storeCmd;
  exPkt.m_wordsRemaining--;
  where = cmd.Where();
  if (where > 25)
  {
    message("Illegal DSA store command");
    return;
  };
  RNparameter = exPkt.m_RNmaster;
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"Parameter %c = ",where+'A');
  };
  while (where > 1)
  {
    RNparameter = GetDBRecordLink(RNparameter);
    if (RNparameter == RNeof) break;
    if (RNparameter.dbType() != dbACTUATOR) continue;
    pParameter = GetRecordAddressDB3(RNparameter);
    if (pParameter->actuatorType() != 47) continue;
    where -= 2;
  };
  if (RNparameter == RNeof) 
  {
    if (TimerTraceActive)
    {
      PrintTrace("Missing = 0");
    };
    stack.pop();
  }
  else
  {
    pParameter = GetRecordAddressDB3(RNparameter);
    if (where == 0) 
    {
      pParameter->ParameterA(stack.pop());
      if (TimerTraceActive)
      {
        fprintf(GETFILE(TraceFile),"%d",pParameter->ParameterA());
      };
    }
    else 
    {
      pParameter->ParameterB(stack.pop());
      if (TimerTraceActive)
      {
        fprintf(GETFILE(TraceFile),"%d",pParameter->ParameterB());
      };
    };
  };
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  return;
}  
  
static void EX_TYPE(void)
{
  i32 id, objID;
  RN thisObj, contents;
  DB0 *pDB0;
  DB1 *pDB1;
  DB2 *pDB2;
  DB3 *pDB3;
  DB4 *pDB4;
  DB5 *pDB5;
  DB6 *pDB6;
  DB7 *pDB7;
  DB8 *pDB8;
  DB9 *pDB9;
  DB10 *pDB10;
  thisObj.ConstructFromInteger(stack.pop());
  switch (thisObj.dbType())
  {
  case dbDOOR:
    pDB0 = GetRecordAddressDB0(thisObj);
    id = pDB0->doorSwitch();                 //bit 0
    if (pDB0->canOpenWithAxe()) id |= 2;     //bit 1
    if (pDB0->canOpenWithFireball()) id |= 4;//bit 2
    id |= pDB0->mode() << 3;                 //bit 3
    id |= pDB0->ornateness() << 4;           //bit 4-7
    id |= pDB0->doorType() << 8;             //bit 8
    break;
  case dbTELEPORTER:
    pDB1 = GetRecordAddressDB1(thisObj);
    id = pDB1->rotation();        //bit0-1
    id |= pDB1->facingMode() << 2;//bit 2
    id |= pDB1->what() << 3;      //bit 3-4
    id |= pDB1->audible() << 5;   //bit 5
    break;
  case dbTEXT:
    pDB2 = GetRecordAddressDB2(thisObj);
    id = pDB2->show(); //bit 0
    break;
  case dbACTUATOR:
    pDB3 = GetRecordAddressDB3(thisObj);
    id = pDB3->audible();       //bit 0
    id |= pDB3->onceOnly()<<1;  //bit 1
    id |= pDB3->actuatorType()<<2;//bit 2-8
    break;
  case dbMONSTER:
    pDB4 = GetRecordAddressDB4(thisObj);
    id = pDB4->monsterType();
    break;
  case dbWEAPON:
    pDB5 = GetRecordAddressDB5(thisObj);
    id = pDB5->weaponType();
    break;
  case dbCLOTHING:
    pDB6 = GetRecordAddressDB6(thisObj);
    id = pDB6->clothingType();
    break;
  case dbSCROLL:
    pDB7 = GetRecordAddressDB7(thisObj);
    id = 0;
    break;
  case dbPOTION:
    pDB8 = GetRecordAddressDB8(thisObj);
    id = pDB8->potionType() << 8;
    id |= pDB8->strength();
    break;
  case dbCHEST:
    pDB9 = GetRecordAddressDB9(thisObj);
    for (id=0,contents = pDB9->contents();
         contents != RNeof;
         contents = GetDBRecordLink(contents))
    {
      id++;
    };
    break;
  case dbMISC:
    pDB10 = GetRecordAddressDB10(thisObj);
    id = pDB10->miscType();
    break;
  case dbEXPOOL:
  case db12:
  case db13:
  case dbMISSILE:
  case dbCLOUD:
  default:
    id = 0;
    break;
  };
  objID = thisObj.dbType() *10000;
  objID += id & 0x0ffff;
  stack.push(objID);
}
  
static void EX_EQUAL(EXECUTIONPACKET& exPkt)
{
  i32 v1, v2;
  i32 relState;
  DSAequalCmd cmd;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->equalCmd;
  exPkt.m_wordsRemaining--;
  v1 = stack.pop();
  v2 = stack.pop();
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"(%d,%d) --> %d",
      v1, v2, v1==v2 ? 1 : 0);
  };
  stack.push(((v1==v2) ? 1 : 0));
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  return;
}  
  
static void EX_DEL(void)
{
  LOCATIONREL locr;
  ui32 location;
  i32 objID;
  i32 oldLevel;
  DBTYPE dbType;
  RN thisObj;
  location = stack.pop();
  objID = stack.pop();
  locr.Integer(location);
  if (!locr.IsValid())
  {
    message("Illegal location in DSA &DEL");
    return;
  };
  for (thisObj = FindFirstObject(locr.l,locr.x,locr.y);
       thisObj != RNeof;
       thisObj = GetDBRecordLink(thisObj))
  {
    if (thisObj.ConvertToInteger() != objID) continue;
    dbType = thisObj.dbType();
    switch (dbType)
    {
    case dbMISC:
    case dbPOTION:
    case dbWEAPON:
    case dbCLOTHING:
      oldLevel = d.LoadedLevel;
      LoadLevel(locr.l);
      MoveObject(thisObj, locr.x, locr.y, -1, 0);
      //db.GetCommonAddress(thisObj)->link(RNnul);
      DeleteDBEntry(db.GetCommonAddress(thisObj));
      LoadLevel(oldLevel);
      return;
    default:
      return;
    };
  };
}
  
static void EX_ADD(void)
{
  LOCATIONREL locr;
  RN object, newObj;
  ui32 objID;
  ui32 location;
  ui32 posMsk;
  i32 oldLevel;
  DBTYPE dbType;
  newObj = RNeof;
  objID = stack.pop();
  location = stack.pop();
  posMsk = stack.pop();
  locr.Integer(location);
  if (!object.checkIndirectIndex(objID)) 
  {
    if (TimerTraceActive)
    {
      PrintTrace(" illegal object in &ADD ");
    };
    return;
  };
  object.ConstructFromInteger(objID);
  dbType = object.dbType();
  switch (dbType)
  {
  case dbMISC:
    {
      DB10 *pDB10, *newPDB10;
      i32 i;
      pDB10 = GetRecordAddressDB10(object);
      newObj = FindEmptyDB10Entry(true);
      if (newObj == RNnul)
      {
        newObj = RNeof;
        break;
      };
      newPDB10 = GetRecordAddressDB10(newObj);
      for (i=2; i<sizeof(DB10); i++)
      {
        *((char *)newPDB10+i) = *((char *)pDB10+i);
      };
    };
    break;
  case dbWEAPON:
    {
      DB5 *pDB5, *newPDB5;
      i32 i;
      pDB5 = GetRecordAddressDB5(object);
      newObj = FindEmptyDB5Entry(true);
      if (newObj == RNnul)
      {
        newObj = RNeof;
        break;
      };
      newPDB5 = GetRecordAddressDB5(newObj);
      for (i=2; i<sizeof(DB5); i++)
      {
        *((char *)newPDB5+i) = *((char *)pDB5+i);
      };
    };
    break;
  case dbCLOTHING:
    {
      DB6 *pDB6, *newPDB6;
      i32 i;
      pDB6 = GetRecordAddressDB6(object);
      newObj = FindEmptyDB6Entry(true);
      if (newObj == RNnul)
      {
        newObj = RNeof;
        break;
      };
      newPDB6 = GetRecordAddressDB6(newObj);
      for (i=2; i<sizeof(DB6); i++)
      {
        *((char *)newPDB6+i) = *((char *)pDB6+i);
      };
    };
    break;
  case dbPOTION:
    {
      DB8 *pDB8, *newPDB8;
      i32 i;
      pDB8 = GetRecordAddressDB8(object);
      newObj = FindEmptyDB8Entry(true);
      if (newObj == RNnul)
      {
        newObj = RNeof;
        break;
      };
      newPDB8 = GetRecordAddressDB8(newObj);
      for (i=2; i<sizeof(DB8); i++)
      {
        *((char *)newPDB8+i) = *((char *)pDB8+i);
      };
    };
    break;
  default:
    if (TimerTraceActive)
    {
      PrintTrace(" &ADD of unimplemented type ");
    };
  };
  if (newObj != RNeof)
  {
    i32 m, n, p;
    m = posMsk;
    n = 0;
    for (p=0; p<4; p++)
    {
      if (m&1) n++;
      m >>= 1;
    };
    n = STRandom(n);
    m = posMsk;
    for (p=0; p<4; p++)
    {
      if ((n==0) && (m&1)) break;
      n--;
      m >>= 1;
    };
    oldLevel = d.LoadedLevel;
    LoadLevel(locr.l);
    MoveObject(newObj, -1, 0, locr.x, locr.y);
    LoadLevel(oldLevel);
    newObj.pos(p);
    return;
  };  
}

static i32 EX_AMPERSAND(EXECUTIONPACKET& exPkt)
{
  i32 relState;
  i32 forceState = -1;  //return value
  i32 v;
  STKOP subcode;
  DSAampersandCmd cmd;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->ampersandCmd;
  exPkt.m_wordsRemaining--;
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  subcode = cmd.SubCode();
  switch(subcode)
  {
  case STKOP_2Drop:
    if (TimerTraceActive)
    {
      PrintTrace("2DROP ");
    };
    stack.pop();
    stack.pop();
    break;
  case STKOP_1Plus:
    if (TimerTraceActive)
    {
      PrintTrace("1+ ");
    };
    stack.push(stack.pop() + 1);
    break;
  case STKOP_Swap:
    if (TimerTraceActive)
    {
      PrintTrace("SWAP ");
    };
    stack.roll(1);
    break;
  case STKOP_Neg:
    if (TimerTraceActive)
    {
      PrintTrace("NEG ");
    };
    stack.push(-stack.pop());
    break;
  case STKOP_Comp:
    if (TimerTraceActive)
    {
      PrintTrace("COMP ");
    };
    stack.push(~stack.pop());
    break;
  case STKOP_Dup:
    if (TimerTraceActive)
    {
      PrintTrace("DUP ");
    };
    v = stack.pop();
    stack.push(v);
    stack.push(v);
    break;
  case STKOP_Fetch:
    if (TimerTraceActive)
    {
      PrintTrace("Fetch ");
    };
    v = stack.pop();
    stack.push(stack.tempVar[v]);
    break;
  case STKOP_Store:
    if (TimerTraceActive)
    {
      PrintTrace("Store ");
    };
    v = stack.pop();
    stack.tempVar[v] = stack.pop();
    break;
  case STKOP_Del:
    if (TimerTraceActive)
    {
      PrintTrace("DEL ");
    };
    EX_DEL();
    break;
  case STKOP_Add:
    if (TimerTraceActive)
    {
      PrintTrace("ADD ");
    };
    EX_ADD();
    break;
  case STKOP_Plus:
    if (TimerTraceActive)
    {
      PrintTrace("+ ");
    };
    stack.push(stack.pop()+stack.pop());
    break;
  case STKOP_Gear:
    if (TimerTraceActive)
    {
      PrintTrace("* ");
    };
    stack.push(stack.pop()*stack.pop());
    break;
  case STKOP_1Minus:
    if (TimerTraceActive)
    {
      PrintTrace("1- ");
    };
    stack.push(stack.pop() - 1);
    break;
  case STKOP_Roll:
    if (TimerTraceActive)
    {
      PrintTrace("ROLL ");
    };
    v = stack.pop();
    stack.roll(v);
    break;
  case STKOP_Rot:
    if (TimerTraceActive)
    {
      PrintTrace("ROT ");
    };
    stack.roll(2);
    break;
  case STKOP_MinusRoll:
    if (TimerTraceActive)
    {
      PrintTrace("-ROLL ");
    };
    v = stack.pop();
    stack.minusRoll(v);
    break;
  case STKOP_MinusRot:
    if (TimerTraceActive)
    {
      PrintTrace("-ROT ");
    };
    stack.minusRoll(2);
    break;
  case STKOP_Pick:
    if (TimerTraceActive)
    {
      PrintTrace("PICK ");
    };
    stack.pick(stack.pop());
    break;
  case STKOP_2Pick:
    if (TimerTraceActive)
    {
      PrintTrace("2PICK ");
    };
    stack.pick(2);
    break;
  case STKOP_Over:
    if (TimerTraceActive)
    {
      PrintTrace("OVER ");
    };
    stack.pick(1);
    break;
  case STKOP_Poke:
    if (TimerTraceActive)
    {
      PrintTrace("POKE ");
    };
    stack.poke(stack.pop());
    break;
  case STKOP_Drop:
    if (TimerTraceActive)
    {
      PrintTrace("DROP ");
    };
    stack.pop();
    break;
  case STKOP_Equal:
    if (TimerTraceActive)
    {
      PrintTrace("= ");
    };
    stack.push((stack.pop()==stack.pop())?1:0);
    break;
  case STKOP_NotEqual:
    if (TimerTraceActive)
    {
      PrintTrace("!=");
    };
    stack.push((stack.pop()==stack.pop())?0:1);
    break;
  case STKOP_And:
    if (TimerTraceActive)
    {
      PrintTrace("AND ");
    };
    stack.push(stack.pop() & stack.pop());
    break;
  case STKOP_Or:
    if (TimerTraceActive)
    {
      PrintTrace("OR ");
    };
    stack.push(stack.pop() | stack.pop());
    break;
  case STKOP_Shift:
    if (TimerTraceActive)
    {
      PrintTrace("SHIFT ");
    };
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      stack.push( (v>=0)?(w<<v):(w>>(-v)));
    };
    break;
  case STKOP_RShift:
    if (TimerTraceActive)
    {
      PrintTrace("RSHIFT ");
    };
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      stack.push( (v>=0)?(w>>v):(w<<(-v)));
    };
    break;
  case STKOP_Less:
    if (TimerTraceActive)
    {
      PrintTrace("< ");
    };
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      stack.push( (w<v)?1:0 );
    };
    break;
  case STKOP_Slash:
    if (TimerTraceActive)
    {
      PrintTrace("/ ");
    };
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      stack.push(w/v);
    };
    break;
  case STKOP_Percent:
    if (TimerTraceActive)
    {
      PrintTrace("% ");
    };
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      stack.push(w%v);
    };
    break;
  case STKOP_Type:
    if (TimerTraceActive)
    {
      PrintTrace("TYPE ");
    };
    EX_TYPE();
    break;
  case STKOP_SetNewState:
    if (TimerTraceActive)
    {
      PrintTrace("SETNEWSTATE ");
    };
    forceState = stack.pop();
    break;
  case STKOP_FalsePit: //(target bool ...)
    {
      i32 v = stack.pop();
      i32 w = stack.pop();
      LOCATIONREL locr;
      CELLFLAG *pcf;
      if (TimerTraceActive)
      {
        PrintTrace("FalsePit ");
      };
      locr.Integer(w);
      if (!locr.IsValid()) break; // if bad location
      pcf = GetCellFlagsAddress(locr.l, locr.x, locr.y);
      if (RoomType(*pcf) != roomPIT) break;
      if (v != 0) *pcf |= 1;
      else *pcf &= ~1;
    };
    break;
  case STKOP_GeneratorDelay:
    {
      i32 v = stack.pop(); //delay
      i32 w = stack.pop(); //target
      LOCATIONREL locr;
      DBTYPE dbType;
      CELLFLAG *pcf;
      RN obj;
      i32 at;
      DB3 *pDB3, *pDisabled;
      locr.Integer(w);
      if (TimerTraceActive)
      {
        PrintTrace("GeneratorDelay ");
      };
      if (!locr.IsValid()) break; // if bad location
      pcf = GetCellFlagsAddress(locr.l, locr.x, locr.y);
      if (RoomType(*pcf) == roomSTONE) break; //bad location
      pDisabled = pDB3 = NULL;
      for (obj = FindFirstObject(locr.l,locr.x,locr.y);
           obj != RNeof;
           obj = GetDBRecordLink(obj))
      {
        dbType = obj.dbType();
        if (dbType != dbACTUATOR) continue;
        pDB3 = GetRecordAddressDB3(obj);
        at = pDB3->actuatorType();
        if (at == 6)
        {
          pDB3->disableTime(v);
          break;
        };
        if ((at == 0) && (pDisabled == NULL)) pDisabled = pDB3;
        pDB3 = NULL;
      };
      if ((pDB3 == NULL) && (pDisabled != NULL))
      {
        pDisabled->disableTime(v);
      };
    };
    break;
  case STKOP_Overlay:
    {
      ui32 p1, p2, p3, p4, onum;
      if (TimerTraceActive)
      {
        PrintTrace("Overlay ");
      };
      onum = stack.pop();
      p4 = stack.pop();
      p3 = stack.pop();
      p2 = stack.pop();
      p1 = stack.pop();
      SelectOverlay(onum, p1, p2, p3, p4);
    };
    break;
  case STKOP_DisableSaves:
    {
      ui32 d;
      if (TimerTraceActive)
      {
        PrintTrace("DisableSaves ");
      };
      d = stack.pop();
      disableSaves = d != 0;
    };
    break;
  case STKOP_ParamFetch:
    {
      i32 n, i;
      if (TimerTraceActive)
      {
        PrintTrace("PARAM@ ");
      };
      i = stack.pop();
      n = stack.pop();
      if (n > 100)
      {
        message("DSA &PARAM@ too many parameters");
      }
      else
      {
        memcpy(stack.tempVar+i, DSAparameters, 4*n);
      };
    };
    break;
  case STKOP_ParamStore:
    {
      i32 n, i;
      if (TimerTraceActive)
      {
        PrintTrace("PARAM! ");
      };
      i = stack.pop();
      n = stack.pop();
      if (n > 100)
      {
        message("DSA &PARAM! too many parameters");
      }
      else
      {
        memcpy(DSAparameters, stack.tempVar+i, 4*n);
      };
    };
    break;
  case STKOP_Message:
    {
      LOCATIONREL target;
      TIMER timer;
      ui32 delay;
      ui32 msgType;
      ui32 numParam;
      ui32 timerID;
      i32 actionTime;
      ui32 key;
      delay = stack.pop();
      numParam = stack.pop();
      msgType = stack.pop();
      target.Integer(stack.pop());
      if (Override_P)
      {
        target.p = Override_Pos;
        Override_P = false;
      };
      actionTime = d.Time + delay;
      timer.timerTime = (target.l << 24) | actionTime;
      timer.Function(TT_ParameterMessage);
      timer.timerUByte5 = 0;
      timer.timerUByte6 = (ui8)target.x;
      timer.timerUByte7 = (ui8)target.y;
      timer.timerUByte8 = (ui8)target.p;
      timer.timerUByte9 = (ui8)msgType;
      timerID = SetTimer(&timer);
      key = (EDT_MessageParameters<<24) + timerID;
      expool.Write(key, DSAparameters, numParam);
    };
    break;
  case STKOP_Cast:
    DSACastSpell();
    break;
  default:
    message("Illegal subcode in EX_AMPERSAND");
    return forceState;
  };
  return forceState;
}  

static void EX_FETCH(EXECUTIONPACKET& exPkt)
{
  i32 objMask, positionMask;
  i32 location, depth;
  i32 objID;
  i32 relState;
  LOCATIONREL locr;
  //LOCATIONABS loca;
  DSAfetchCmd cmd;
  RN thisObj, contents;
  cmd = ((DSAcommand *)(exPkt.m_PC++))->fetchCmd;
  exPkt.m_wordsRemaining--;
  objMask = stack.pop();
  positionMask = stack.pop();
  location = stack.pop();
  depth = stack.pop();
  locr.Integer(location);
  if (!locr.IsValid())
  {
    message("Illegal location in DSA FETCH");
    return;
  };
  if (positionMask == -1) positionMask = 1<<locr.p;
  //loca = locr;
  objID = -1;
  for (thisObj = FindFirstObject(locr.l,locr.x,locr.y);
       thisObj != RNeof;
       thisObj = GetDBRecordLink(thisObj))
  {
    if (((1<<thisObj.pos()) & positionMask) == 0) continue;
    if (((1<<thisObj.dbType()) & objMask) == 0) continue;
    if (depth == 0)
    {
      objID = thisObj.ConvertToInteger();//indirectIndex;
      break;
    }
    else
    {
      depth--;
    };
  };
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"%d",objID);
  };
  stack.push(objID);
  if (cmd.IsMaxState())
  {
    relState = *(exPkt.m_PC++);
    exPkt.m_wordsRemaining--;
  }
  else
  {
    relState = cmd.NextState();
  };
  exPkt.m_nextState = exPkt.m_curState + relState;
  return;
}

class SAVEPEXPKT
{
  EXECUTIONPACKET *save;
public:
  SAVEPEXPKT(EXECUTIONPACKET *p){save=pExPkt;pExPkt=p;};
  ~SAVEPEXPKT(void){pExPkt=save;};
};

static i32 Execute(RN  RNmaster,
                   DB3 *pDBMaster,
                   DSA *pMaster, 
                   DSA *pSlave, 
                   i32 curState, 
                   i32 msgType,
                   LOCATIONREL locrMaster,
                   LOCATIONREL locrSlave,
                   i32 subroutineLevel)
{
  DSAState *pState;
  DSAcommand *pCMD;
  LOCATIONABS locaSlave;
  i32 finalState = -1;
  i32 forceState = -1;
  i32 v;
  DSACOMMAND opcode;
  EXECUTIONPACKET exPkt;
  SAVEPEXPKT save(&exPkt);
  exPkt.m_locrMaster = locrMaster;
  exPkt.m_subroutineLevel = subroutineLevel;
  exPkt.m_RNmaster       = RNmaster;
  exPkt.m_pDBMaster      = pDBMaster;
  exPkt.m_pMaster        = pMaster; 
  exPkt.m_curState       = curState;
  exPkt.m_column         = msgType;
  exPkt.m_locrMaster     = locrMaster;
  exPkt.m_locrSlave      = locrSlave;
  if (TimerTraceActive)
  {
    locaSlave  = locrSlave;
    fprintf(GETFILE(TraceFile),
      "(%d)Execute DSA at %d(%d,%d) master at %d(%d,%d) state=%d,msg=%d\n",
      exPkt.m_subroutineLevel,
      locaSlave.l,locaSlave.x,locaSlave.y,
      exPkt.m_locrMaster.l,exPkt.m_locrMaster.x,exPkt.m_locrMaster.y,
      exPkt.m_curState,msgType);
  };
  do
  { 
    pState = pSlave->StatePointer(exPkt.m_curState);
    if (pState == NULL) return exPkt.m_curState;
    exPkt.m_PC = pState->Program(exPkt.m_column);
    exPkt.m_wordsRemaining = pState->ProgramSize(exPkt.m_column);
    exPkt.m_transferState  = -1;
    exPkt.m_transferCol    = -1;
    exPkt.m_nextState      = exPkt.m_curState;
    while (exPkt.m_wordsRemaining > 0)
    {
      pCMD = (DSAcommand *)exPkt.m_PC;
      opcode = (DSACOMMAND)pCMD->generalCmd.CmdCode();
      switch (opcode)
      {
      case DSACMD_QUESTION:
        if (TimerTraceActive)
        {
          PrintTrace("  QUESTION ");
        };
        EX_QUESTION(exPkt);
        break;
      case DSACMD_CASE:
        if (TimerTraceActive)
        {
          PrintTrace("  CASE ");
        };
        EX_CASE(exPkt);
        break;
      case DSACMD_EQUAL:
        if (TimerTraceActive)
        {
          PrintTrace("  EQUAL ");
        };
        EX_EQUAL(exPkt);
        break;
      case DSACMD_AMPERSAND:
        if (TimerTraceActive)
        {
          PrintTrace("  &");
        };
        v = EX_AMPERSAND(exPkt);
        if (v >= 0) forceState = v;
        break;
      case DSACMD_FETCH:
        // (depth, location, position mask, object mask)
        if (TimerTraceActive)
        {
          PrintTrace("  FETCH ");
        };
        EX_FETCH(exPkt);
        break;
      case DSACMD_NOOP:
        if (TimerTraceActive)
        {
          PrintTrace("  NOOP ");
        };
        EX_NOOP(exPkt);
        break;
      case DSACMD_LOAD:
        if (TimerTraceActive)
        {
          PrintTrace("  LOAD ");
        };
        EX_LOAD(exPkt);
        break;
      case DSACMD_STORE:
        if (TimerTraceActive)
        {
          PrintTrace("  STORE ");
        };
        EX_STORE(exPkt);
        break;
      case DSACMD_MESSAGE:
        if (TimerTraceActive)
        {
          PrintTrace("  MESSAGE ");
        };
        EX_MESSAGE(exPkt, false);
        break;
      case DSACMD_MESSAGE32:
        if (TimerTraceActive)
        {
          PrintTrace("  MESSAGE ");
        };
        EX_MESSAGE(exPkt, true);
        break;
      case DSACMD_COPYTELEPORTER:
        if (TimerTraceActive)
        {
          PrintTrace("  COPYTELEPORTER ");
        };
        EX_COPYTELEPORTER(exPkt, false);
        break;
      case DSACMD_COPYTELEPORTER32:
        if (TimerTraceActive)
        {
          PrintTrace("  COPYTELEPORTER ");
        };
        EX_COPYTELEPORTER(exPkt, true);
        break;
      case DSACMD_GOSUB:
        if (TimerTraceActive)
        {
          PrintTrace("  GOSUB ");
        };
        EX_GOSUB(exPkt);
        break;
      case DSACMD_JUMP:
        if (TimerTraceActive)
        {
          PrintTrace("  JUMP ");
        };
        EX_JUMP(exPkt);
        break;
      case DSACMD_OVERRIDE:
        if (TimerTraceActive)
        {
          PrintTrace("  OVERRIDE ");
        };
        EX_OVERRIDE(exPkt);
        break;
      default: 
        message("Unknown DSA command code");
        return curState;
      };
      if (TimerTraceActive)
      {
        stack.dump();
        PrintTrace("\n");
      };
      if (exPkt.m_transferState >= 0) break; //Jump command
      if (    (finalState == -1)
           && (exPkt.m_nextState >= 0))
      {
        finalState = exPkt.m_nextState;
      };
    };
    if (exPkt.m_nextState >= 0) 
    {
      if (finalState == -1) finalState = exPkt.m_nextState;
    };
    // Process result of jump command
    exPkt.m_curState = exPkt.m_transferState;
    exPkt.m_column   = exPkt.m_transferCol;
    exPkt.m_transferState = -1;
    exPkt.m_transferCol = -1;
  } while (exPkt.m_curState >= 0);
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"(%d)RETURN",exPkt.m_subroutineLevel);
  };
  if (forceState >= 0) 
  {
    finalState = forceState;
  };
  return finalState;
}

void ProcessDSATimer5(RN objSlave, 
                      const TIMER *pTimer,
                      LOCATIONREL locrSlave)
{
  ProcessDSATimer6(objSlave, pTimer, locrSlave);
}


void ProcessDSATimer6(RN objSlave, 
                      const TIMER *pTimer,
                      LOCATIONREL locrSlave)
{
  i32 level;
  i32 levelDSAindex, absDSAindex;
  i32 timerFunction, timerX, timerY, timerPosition;
  i32 masterX, masterY, masterPos;
  i32 curState;
  i32 inputMsgType;
  LOCATIONREL locrMaster;
  EXECUTIONPACKET exPkt;
  SAVEPEXPKT save(&exPkt);
  RN objMaster;
  DB3 *pDBSlave ,*pDBMaster;
  DSA *pSlave   ,*pMaster;
  level = d.LoadedLevel;
  locrMaster.l = level;
  pDBSlave = GetRecordAddressDB3(objSlave);
  levelDSAindex = pDBSlave->DSAselector();
  absDSAindex = DSALevelIndex[level][levelDSAindex];
  if (absDSAindex > 255)
  {
    message("Undefined Slave DSA Level Index");
    return;
  };
  pSlave = DSAIndex.GetDSA(absDSAindex);
  if (pSlave == NULL)
  {
    message("Undefined Slave DSA");
    return;
  };
  timerFunction = pTimer->timerUByte9;
  timerPosition = pTimer->timerUByte8;
  timerY        = pTimer->timerUByte7;
  timerX        = pTimer->timerUByte6;
  objMaster = FindMaster(pSlave, objSlave, timerX, timerY,
                            &masterX, &masterY, &masterPos);
  locrMaster.x = masterX;
  locrMaster.y = masterY;
  locrMaster.p = objMaster.pos();
  pDBMaster = GetRecordAddressDB3(objMaster);
  levelDSAindex = pDBMaster->DSAselector();
  absDSAindex = DSALevelIndex[level][levelDSAindex];
  if (absDSAindex > 255)
  {
    message("Undefined Master DSA Level Index");
    return;
  };
  pMaster = DSAIndex.GetDSA(absDSAindex);
  if (pMaster == NULL)
  {
    message("Undefined Master DSA");
    return;
  };
  //
  //  Get the current state of the machine.
  //
  curState = GetState(pDBMaster, pMaster);
  inputMsgType = 3 * timerPosition + timerFunction;
  if (TimerTraceActive)
  {
    fprintf(GETFILE(TraceFile),"DSA %s State %d MSG %d\n",
                      pSlave->Description(), 
                      curState, 
                      inputMsgType);
  };
  Override_P = false;
  stack.Clear();
  curState = Execute(objMaster, 
                     pDBMaster, 
                     pMaster, 
                     pSlave, 
                     curState, 
                     inputMsgType,
                     locrMaster,
                     locrSlave,
                     0);
  if (TimerTraceActive)
  {
    stack.dump();
    PrintTrace("\n");
  };
  PutState(pDBMaster, pMaster, curState);
}


DSAINDEX::DSAINDEX(void)
{
  i32 i;
  for (i=0; i<256; i++)
  {
    m_pDSA[i] = NULL;
  };
}

DSAINDEX::~DSAINDEX(void)
{
  Cleanup();
}

void DSAINDEX::Cleanup(void)
{
  i32 i;
  for (i=0; i<256; i++)
  {
    if (m_pDSA[i] != NULL) delete m_pDSA[i];
    m_pDSA[i] = NULL;
  };
}

void DSAINDEX::SetDSA(i32 index, DSA *pDSA)
{
  if (index > 255)
  {
    die(0,"DSA index > 255");
  };
  if (m_pDSA[index] != NULL) delete m_pDSA[index];
  m_pDSA[index] = pDSA;
}

DSA *DSAINDEX::GetDSA(i32 index)
{
  if (index > 255)
  {
    die(0,"DSA index > 255");
  };
  return m_pDSA[index];
}

DSA::DSA(void)
{
  strcpy(m_description,"New DSA");
  m_localState = true;
  m_groupID = 0;
  m_numState = 0;
  m_pStates = NULL;
  m_firstDisplayedState = 0;
  m_state = 0;
}

DSA::~DSA(void)
{
  i32 i;
  for (i=0; i<m_numState; i++)
  {
    delete m_pStates[i];
    m_pStates[i] = NULL;
  };
  m_numState = 0;
  if (m_pStates != NULL) UI_free (m_pStates);
  m_pStates = NULL;
}

DSAState *DSA::StatePointer(i32 stateNum)
{
  if (stateNum >= m_numState) return NULL;
  return m_pStates[stateNum];
}

void DSA::Read(void)
{
  ui32 i;
  ui32 numNonEmptyStates;
  ui32 stateNum;
  RCS(m_description, 80);
  RCS(&m_state);
  RCS(&m_localState);
  RCS(&m_groupID);
  RCS(&m_numState);
  RCS(&m_firstDisplayedState);
  RCS(&numNonEmptyStates);
  if (m_pStates != NULL)
  {
    die(0,"DSA state table not NULL");
  };
  m_pStates = (DSAState **)UI_malloc(m_numState*sizeof(DSAState *),
                                     MALLOC023);
  for (i=0; i<(unsigned)m_numState; i++) m_pStates[i] = NULL;
  for (i=0; i<numNonEmptyStates; i++)
  {
    RCS(&stateNum);
    if (stateNum >= (unsigned)m_numState)
    {
      die(0,"DSA state number > #states");
    };
    if (m_pStates[stateNum] != NULL)
    {
      die(0,"Duplicate DSA state");
    };
    m_pStates[stateNum] = new DSAState;
    m_pStates[stateNum]->Read();
  };
};

void DSA::Write(void)
{
  ui32 i;
  ui32 numNonEmptyStates = 0;
  WCS(m_description, 80);
  WCS(&m_state);
  WCS(&m_localState);
  WCS(&m_groupID);
  WCS(&m_numState);
  WCS(&m_firstDisplayedState);
  for (i=0; i<(unsigned)m_numState; i++)
  {
    if (m_pStates[i] != NULL) numNonEmptyStates++;
  };
  WCS(&numNonEmptyStates);
  for (i=0; i<(unsigned)m_numState; i++)
  {
    if (m_pStates[i] == NULL) continue;
    WCS(&i);
    m_pStates[i]->Write();
  };
};



DSAState::DSAState(void)
{
  m_numAction = 0;
  m_pActions = NULL;
}

DSAState::~DSAState(void)
{
  i32 i;
  for (i=0; i<m_numAction; i++)
  {
    delete m_pActions[i];
    m_pActions[i] = NULL;
  };
  if (m_pActions != NULL) UI_free (m_pActions);
  m_pActions = NULL;
  m_numAction = 0;
}

ui16 *DSAState::Program(i32 column)
{
  i32 i;
  for (i=0; i<m_numAction; i++)
  {
    if (m_pActions[i]->Column() == column)
    {
      return m_pActions[i]->Program();
    };
  };
  return NULL;
}

i32 DSAState::ProgramSize(i32 column)
{
  i32 i;
  for (i=0; i<m_numAction; i++)
  {
    if (m_pActions[i]->Column() == column)
    {
      return m_pActions[i]->ProgramSize();
    };
  };
  return 0;
}

void DSAState::Read(void)
{
  i32 i;
  RCS(&m_numAction);
  if (m_numAction > 12)
  {
    die(0,"Number of DSA actions > 12");
  };
  if (m_pActions != NULL)
  {
    die(0,"Action table npt NULL");
  };
  m_pActions = (DSAAction **)UI_malloc(
                    m_numAction * sizeof (DSAAction*),
                    MALLOC024);
  for (i=0; i<m_numAction; i++)
  {
    m_pActions[i] = new DSAAction;
    m_pActions[i]->Read();
  };
}

void DSAState::Write(void)
{
  i32 i;
  WCS(&m_numAction);
  for (i=0; i<m_numAction; i++)
  {
    m_pActions[i]->Write();
  };
}



DSAAction::DSAAction(void)
{
  m_column      = -1;
  m_numPgmWords = 0;
  m_program     = NULL;
}

DSAAction::~DSAAction(void)
{
  if (m_program != NULL) UI_free (m_program);
  m_program = NULL;
  m_numPgmWords = 0;
}

void DSAAction::Read(void)
{
  RCS(&m_column);
  RCS(&m_numPgmWords);
  m_program = (ui16 *)UI_malloc(
           m_numPgmWords * sizeof (ui16),
           MALLOC025);
  RCS(m_program, m_numPgmWords);
}

void DSAAction::Write(void)
{
  WCS(&m_column);
  WCS(&m_numPgmWords);
  WCS(m_program, m_numPgmWords);
}

