Index: class.c
==================================================================
--- class.c
+++ class.c
@@ -974,10 +974,11 @@
 
 static int parse_pattern(int cla,int ptr,Hash*hash) {
   Class*cl=classes[cla];
   Uint8 depth=0;
   Uint16 nest[32];
+  Uint16 nest0[32];
   int x,y;
   for(;;) {
     nxttok();
     if(Tokenf(TF_MACRO)) ParseError("Unexpected macro\n");
     if(Tokenf(TF_DIR)) {
@@ -985,37 +986,43 @@
     } else if(Tokenf(TF_NAME)) {
       switch(tokenv) {
         case OP_ADD: case OP_CLIMB: case OP_EIGHT: case OP_FOUR:
         case OP_HEIGHT: case OP_LOC: case OP_MARK: case OP_SUB:
         case OP_DIR: case OP_DIR_C: case OP_DIR_E: case OP_DIR_EC:
+        case OP_OBJTOPAT: case OP_OBJBOTTOMAT: case OP_CUT: case OP_MUL:
+        case OP_OBJABOVE: case OP_OBJBELOW: case OP_TRACE:
         case 0x0200 ... 0x02FF: // message
         case 0x4000 ... 0x7FFF: // class
         case 0xC000 ... 0xFFFF: // message
           cl->codes[ptr++]=tokenv;
           break;
         case OP_BEGIN: case OP_IF:
           if(depth==31) ParseError("Too much pattern nesting\n");
-          nest[depth++]=ptr;
+          nest0[depth]=nest[depth]=ptr;
           cl->codes[ptr++]=tokenv;
           cl->codes[ptr++]=0;
+          depth++;
           break;
         case OP_ELSE:
           if(!depth) ParseError("Premature end of subpattern\n");
           cl->codes[nest[depth-1]+1]=ptr;
+          cl->codes[ptr++]=OP_ELSE;
           cl->codes[ptr++]=cl->codes[nest[depth-1]];
-          cl->codes[nest[depth-1]]=OP_ELSE;
+          cl->codes[nest[depth-1]]=OP_IF;
           cl->codes[ptr++]=0;
           break;
         case OP_THEN:
           if(!depth) ParseError("Premature end of subpattern\n");
           cl->codes[nest[--depth]+1]=ptr;
+          cl->codes[ptr++]=OP_THEN;
           break;
         case OP_AGAIN:
           if(!depth) ParseError("Premature end of subpattern\n");
-          cl->codes[ptr++]=OP_GOTO;
-          cl->codes[ptr++]=nest[depth-1];
+          cl->codes[ptr++]=OP_AGAIN;
+          cl->codes[ptr++]=nest0[depth-1];
           cl->codes[nest[--depth]+1]=ptr;
+          cl->codes[ptr++]=OP_THEN;
           break;
         case OP_USERFLAG:
           x=look_hash(glohash,HASH_SIZE,0x1000,0x10FF,0,"user flags");
           if(!x) ParseError("User flag ^%s not defined\n",tokenstr);
           if(Tokenf(TF_COMMA)) x+=0x100;

Index: class.doc
==================================================================
--- class.doc
+++ class.doc
@@ -2075,11 +2075,104 @@
 direction of movement.
 
 
 === Pattern matching ===
 
-(TODO)
+Where an instruction is expected, you can have pattern matching. This can
+be (P <pattern...>) or (,P <pattern...>) where the first one uses the
+current location, and the second kind uses the position and direction of
+the specified object as the matching. It pushes the found object to the
+stack, or pushes zero if the pattern does not match.
+
+<class>
+  Match an object of the specified class.
+
+<dir>
+  Move in the specified direction. (This does not move any objects; it
+  only moves the cursor for pattern matching.) The direction can be
+  relative to the current direction (initially the Dir variable of the
+  origin object) or absolute.
+
+<message>
+  Send the message to all objects at the current location. From is the
+  position from which pattern matching started. Arg1 is the direction.
+  Arg2 is the currently matched object (which may be zero). If the
+  return value is zero, it continues normally; if one, the match fails;
+  if two, it continues without sending more messages at this location;
+  if an object, sets the matched object and current location.
+
+<userflag>
+  Match an object with the specified flag.
+
+,<userflag>
+  Fail if there are any objects having the specified flag here.
+
++
+  Push the matched object to the stack.
+
+-
+  Push zero to the stack.
+
+*
+  Update the current choice point with the location and direction.
+
+_
+  Push a mark to the stack.
+
+again
+  End a block with begin or if, allowing it to occur more than once; it
+  is repeated until it no longer matches. If the first word is begin,
+  then it must match at least once; if it is if, then it can match zero
+  or more times. The match is greedy; it will match as much as possible.
+
+begin
+  Begin a block.
+
+Climb
+  Fail the match if the origin object cannot climb here.
+
+(Climb <number>)
+  Fail the match if the height here is greater than this number.
+
+cut
+  Discards the most recent choice point.
+
+else
+  Delimits alternatives within a block.
+
+Four
+  Try four directions (E, N, W, and S).
+
+Eight
+  Try all eight directions.
+
+Height
+  Fail the match if the origin object can climb here.
+
+(Height <number>)
+  Fail the match if the height here isn't greater than this number.
+
+if
+  Begin a block that can optionally match.
+
+ObjAbove
+  Match the object above the currently matched object.
+
+ObjBelow
+  Match the object below the currently matched object.
+
+ObjBottomAt
+  Match the object at the bottom of this location.
+
+ObjTopAt
+  Match the object at the top of this location.
+
+then
+  Ends a block started with begin or if.
+
+Trace
+  Used for debugging.
 
 
 === Compatibility ===
 
 Compatible objects have the following differences from the default:

Index: exec.c
==================================================================
--- exec.c
+++ exec.c
@@ -1510,10 +1510,252 @@
   if(a==VOIDLINK || b==VOIDLINK) return 0;
   if((objects[a]->oflags|objects[b]->oflags)&OF_DESTROYED) return 0;
   if((objects[a]->oflags^objects[b]->oflags)&OF_BIZARRO) return 0;
   return (objects[a]->x==objects[b]->x && objects[a]->y==objects[b]->y);
 }
+
+static Uint32 pat_find_at(int xy,Uint32 m4,Uint32 m5,Uint32 m6,Uint32 m7,Uint8 cl) {
+  Uint8 n=playfield[xy];
+  Object*o;
+  while(n!=VOIDLINK) {
+    o=objects[n];
+    if(!(o->oflags&(OF_DESTROYED|OF_VISUALONLY))) {
+      if(o->misc4.t==TY_SOUND || o->misc4.t==TY_USOUND) Throw("Cannot convert sound to type");
+      if(o->misc5.t==TY_SOUND || o->misc5.t==TY_USOUND) Throw("Cannot convert sound to type");
+      if(o->misc6.t==TY_SOUND || o->misc6.t==TY_USOUND) Throw("Cannot convert sound to type");
+      if(o->misc7.t==TY_SOUND || o->misc7.t==TY_USOUND) Throw("Cannot convert sound to type");
+      if(o->misc4.t==TY_NUMBER && (o->misc4.u&m4)) return n;
+      if(o->misc5.t==TY_NUMBER && (o->misc5.u&m5)) return n;
+      if(o->misc6.t==TY_NUMBER && (o->misc6.u&m6)) return n;
+      if(o->misc7.t==TY_NUMBER && (o->misc7.u&m7)) return n;
+      if(classes[o->class]->collisionLayers&cl) return n;
+    }
+    n=o->up;
+  }
+  return VOIDLINK;
+}
+
+typedef struct {
+  Uint16 ptr,depth;
+  Uint8 x,y,dir,t;
+} ChoicePoint;
+
+#define MAXCHOICE 80
+static Uint32 v_pattern(Uint16*code,int ptr,Uint32 obj,char all) {
+  Uint8 x=objects[obj]->x;
+  Uint8 y=objects[obj]->y;
+  Uint8 d=objects[obj]->dir;
+  Uint32 n=VOIDLINK;
+  Uint32 m;
+  Uint16 g;
+  Value v;
+  static ChoicePoint cp[MAXCHOICE];
+  Uint8 cpi=0;
+  cp->depth=vstackptr;
+  again: switch(code[ptr++]) {
+    case 0 ... 7:
+      n=VOIDLINK;
+      x+=x_delta[code[ptr-1]];
+      y+=y_delta[code[ptr-1]];
+      if(x<1 || x>pfwidth || y<1 || y>pfheight) goto fail;
+      break;
+    case 8 ... 15:
+      n=VOIDLINK;
+      x+=x_delta[(code[ptr-1]+objects[obj]->dir)&7];
+      y+=y_delta[(code[ptr-1]+objects[obj]->dir)&7];
+      if(x<1 || x>pfwidth || y<1 || y>pfheight) goto fail;
+      break;
+    case 0x0200 ... 0x02FF:
+      g=code[ptr-1]&255;
+      goto message;
+    case 0x1000 ... 0x10FF:
+      g=code[ptr-1]&255;
+      if(g<0x20) n=pat_find_at(x+y*64-65,1UL<<(g&31),0,0,0,0);
+      else if(g<0x40) n=pat_find_at(x+y*64-65,0,1UL<<(g&31),0,0,0);
+      else if(g<0x60) n=pat_find_at(x+y*64-65,0,0,1UL<<(g&31),0,0);
+      else if(g<0x80) n=pat_find_at(x+y*64-65,0,0,0,1UL<<(g&31),0);
+      else n=pat_find_at(x+y*64-65,0,0,0,0,1U<<(g&7));
+      if(n==VOIDLINK) goto fail;
+      break;
+    case 0x4000 ... 0x7FFF:
+      n=obj_class_at(code[ptr-1]&0x3FFF,x,y);
+      if(n==VOIDLINK) goto fail;
+      break;
+    case 0xC000 ... 0xFFFF:
+      g=(code[ptr-1]&0x3FFF)+256;
+    message:
+      m=playfield[x+y*64-65];
+      while(m!=VOIDLINK) {
+        v=send_message(obj,m,g,NVALUE(d),OVALUE(n),NVALUE(0));
+        if(v.t==TY_NUMBER) {
+          if(v.u==1) goto fail;
+          else if(v.u==2) break;
+          else if(v.u) Throw("Invalid return value from message in pattern matching");
+        } else if(v.t>TY_MAXTYPE) {
+          n=v_object(v);
+          x=objects[n]->x;
+          y=objects[n]->y;
+        } else {
+          Throw("Type mismatch");
+        }
+        m=objects[m]->up;
+      }
+      break;
+    case OP_ADD:
+      if(vstackptr>=VSTACKSIZE-1) Throw("Stack overflow");
+      m=(n==VOIDLINK?obj_bottom_at(x,y):n);
+      Push(OVALUE(m));
+      break;
+    case OP_AGAIN:
+      if(cp[cpi].ptr!=ptr+1) {
+        if(cpi>=MAXCHOICE-1) Throw("Choice overflow");
+        cpi++;
+        cp[cpi].ptr=ptr+1;
+      }
+      cp[cpi].x=x;
+      cp[cpi].y=y;
+      cp[cpi].dir=d;
+      cp[cpi].depth=vstackptr;
+      ptr=code[ptr]+2;
+      break;
+    case OP_BEGIN:
+      ptr++;
+      break;
+    case OP_CLIMB:
+      if(playfield[x+y*64-65]==VOIDLINK || height_at(x,y)>objects[obj]->climb) goto fail;
+      break;
+    case OP_CLIMB_C:
+      g=code[ptr++];
+      if(playfield[x+y*64-65]==VOIDLINK || height_at(x,y)>g) goto fail;
+      break;
+    case OP_CUT:
+      if(cpi) cpi--;
+      break;
+    case OP_DIR:
+      d=objects[obj]->dir;
+      break;
+    case OP_DIR_C:
+      if(n==VOIDLINK) Throw("No object specified in pattern");
+      d=objects[n]->dir;
+      break;
+    case OP_DIR_E:
+      changed=1;
+      objects[obj]->dir=d;
+      break;
+    case OP_DIR_EC:
+      if(n==VOIDLINK) Throw("No object specified in pattern");
+      changed=1;
+      objects[n]->dir=d;
+      break;
+    case OP_EIGHT:
+      if(cpi>=MAXCHOICE-8) Throw("Choice overflow");
+      d=0;
+      cpi+=7;
+      cp[cpi].x=x;
+      cp[cpi].y=y;
+      cp[cpi].depth=vstackptr;
+      cp[cpi].ptr=ptr;
+      cp[cpi-1]=cp[cpi-2]=cp[cpi-3]=cp[cpi-4]=cp[cpi-5]=cp[cpi-6]=cp[cpi];
+      cp[cpi].dir=1;
+      cp[cpi-1].dir=2;
+      cp[cpi-2].dir=3;
+      cp[cpi-3].dir=4;
+      cp[cpi-4].dir=5;
+      cp[cpi-5].dir=6;
+      cp[cpi-6].dir=7;
+      break;
+    case OP_ELSE:
+      ptr--;
+      while(code[ptr]==OP_ELSE) ptr=code[ptr+2];
+      break;
+    case OP_FOUR:
+      if(cpi>=MAXCHOICE-4) Throw("Choice overflow");
+      d=0;
+      cpi+=3;
+      cp[cpi].x=x;
+      cp[cpi].y=y;
+      cp[cpi].depth=vstackptr;
+      cp[cpi].ptr=ptr;
+      cp[cpi-1]=cp[cpi-2]=cp[cpi];
+      cp[cpi].dir=2;
+      cp[cpi-1].dir=4;
+      cp[cpi-2].dir=6;
+      break;
+    case OP_HEIGHT:
+      if(playfield[x+y*64-65]==VOIDLINK || height_at(x,y)<=objects[obj]->climb) goto fail;
+      break;
+    case OP_HEIGHT_C:
+      g=code[ptr++];
+      if(playfield[x+y*64-65]==VOIDLINK || height_at(x,y)<=g) goto fail;
+      break;
+    case OP_IF:
+      if(cpi>=MAXCHOICE-1) Throw("Pattern stack overflow");
+      cpi++;
+      cp[cpi].x=x;
+      cp[cpi].y=y;
+      cp[cpi].dir=d;
+      cp[cpi].depth=vstackptr;
+      cp[cpi].ptr=code[ptr++];
+      break;
+    case OP_LOC:
+      if(vstackptr>=VSTACKSIZE-2) Throw("Stack overflow");
+      Push(NVALUE(x));
+      Push(NVALUE(y));
+      break;
+    case OP_MARK:
+      if(vstackptr>=VSTACKSIZE-1) Throw("Stack overflow");
+      Push(UVALUE(0,TY_MARK));
+      break;
+    case OP_MUL:
+      cp[cpi].x=x;
+      cp[cpi].y=y;
+      cp[cpi].dir=d;
+      break;
+    case OP_OBJABOVE:
+      if(n==VOIDLINK) Throw("No object specified in pattern");
+      n=obj_above(n);
+      break;
+    case OP_OBJBELOW:
+      if(n==VOIDLINK) Throw("No object specified in pattern");
+      n=obj_below(n);
+      break;
+    case OP_OBJBOTTOMAT:
+      n=obj_bottom_at(x,y);
+      break;
+    case OP_OBJTOPAT:
+      n=obj_top_at(x,y);
+      break;
+    case OP_RET:
+      return n==VOIDLINK?obj_bottom_at(x,y):n;
+    case OP_SUB:
+      if(vstackptr>=VSTACKSIZE-1) Throw("Stack overflow");
+      Push(NVALUE(0));
+      break;
+    case OP_THEN:
+      // This opcode does nothing
+      break;
+    case OP_TRACE:
+      if(main_options['t']) {
+        printf("ptr=%d cpi=%d x=%d y=%d dir=%d obj=%lu ",ptr,cpi,x,y,d,(long)n);
+        printf("[ptr=%d x=%d y=%d dir=%d]\n",cp[cpi].ptr,cp[cpi].x,cp[cpi].y,cp[cpi].dir);
+      }
+      break;
+    default: Throw("Unimplemented opcode in pattern");
+  }
+  goto again;
+  fail:
+  if(vstackptr<cp->depth) Throw("Stack underflow in pattern matching");
+  vstackptr=cp[cpi].depth;
+  if(!cpi) return VOIDLINK;
+  x=cp[cpi].x;
+  y=cp[cpi].y;
+  d=cp[cpi].dir;
+  ptr=cp[cpi].ptr;
+  n=VOIDLINK;
+  cpi--;
+  goto again;
+}
 
 // Here is where the execution of a Free Hero Mesh bytecode subroutine is executed.
 #define NoIgnore() do{ changed=1; }while(0)
 #define GetVariableOf(a,b) (i=v_object(Pop()),i==VOIDLINK?NVALUE(0):b(objects[i]->a))
 #define GetVariableOrAttributeOf(a,b) (t2=Pop(),t2.t==TY_CLASS?NVALUE(classes[t2.u]->a):(i=v_object(t2),i==VOIDLINK?NVALUE(0):b(objects[i]->a)))
@@ -1800,10 +2042,12 @@
     case OP_OBJDIR_C: StackReq(2,1); t2=Pop(); Numeric(t2); i=obj_dir(v_object(Pop()),t2.u); Push(OVALUE(i)); break;
     case OP_OBJLAYERAT: StackReq(3,1); t3=Pop(); Numeric(t3); t2=Pop(); Numeric(t2); t1=Pop(); Numeric(t1); i=obj_layer_at(t1.u,t2.u,t3.u); Push(OVALUE(i)); break;
     case OP_OBJMOVINGTO: StackReq(2,0); t2=Pop(); Numeric(t2); t1=Pop(); Numeric(t1); v_obj_moving_to(t1.u,t2.u); break;
     case OP_OBJTOPAT: StackReq(2,1); t2=Pop(); Numeric(t2); t1=Pop(); Numeric(t1); i=obj_top_at(t1.u,t2.u); Push(OVALUE(i)); break;
     case OP_OVER: StackReq(2,3); t2=Pop(); t1=Pop(); Push(t1); Push(t2); Push(t1); break;
+    case OP_PATTERN: StackReq(0,1); i=code[ptr++]; j=v_pattern(code,ptr,obj,0); ptr=i; Push(OVALUE(j)); break;
+    case OP_PATTERN_C: StackReq(1,1); i=code[ptr++]; j=v_object(Pop()); if(j!=VOIDLINK) j=v_pattern(code,ptr,j,0); ptr=i; Push(OVALUE(j)); break;
     case OP_PICK: StackReq(0,1); t1=Pop(); Numeric(t1); if(t1.u>=vstackptr) Throw("Stack index out of range"); t1=vstack[vstackptr-t1.u-1]; Push(t1); break;
     case OP_PLAYER: StackReq(0,1); if(classes[o->class]->cflags&CF_PLAYER) Push(NVALUE(1)); else Push(NVALUE(0)); break;
     case OP_PLAYER_C: StackReq(1,1); GetClassFlagOf(CF_PLAYER); break;
     case OP_PLUSMOVE: StackReq(1,1); t1=Pop(); Numeric(t1); i=defer_move(obj,t1.u,1); Push(NVALUE(i)); break;
     case OP_PLUSMOVE_C: StackReq(2,1); t1=Pop(); Numeric(t1); i=v_object(Pop()); i=defer_move(i,t1.u,1); Push(NVALUE(i)); break;

Index: instruc
==================================================================
--- instruc
+++ instruc
@@ -283,10 +283,11 @@
 ; Pattern matching
 -,=Pattern "P"
 -,=PatternS "P*"
 -Four
 -Eight
+-cut
 
 ; Specials
 *Function
 *Local
 *Label

Index: instruc.h
==================================================================
--- instruc.h
+++ instruc.h
@@ -415,18 +415,19 @@
 #define OP_PATTERNS_C 35023
 #define OP_PATTERNS_E 37071
 #define OP_PATTERNS_EC 39119
 #define OP_FOUR 32976
 #define OP_EIGHT 32977
-#define OP_FUNCTION 32978
-#define OP_LOCAL 32979
-#define OP_LABEL 32980
-#define OP_STRING 32981
-#define OP_INT16 32982
-#define OP_INT32 32983
-#define OP_DISPATCH 32984
-#define OP_USERFLAG 32985
+#define OP_CUT 32978
+#define OP_FUNCTION 32979
+#define OP_LOCAL 32980
+#define OP_LABEL 32981
+#define OP_STRING 32982
+#define OP_INT16 32983
+#define OP_INT32 32984
+#define OP_DISPATCH 32985
+#define OP_USERFLAG 32986
 #ifdef HEROMESH_CLASS
 static const Op_Names op_names[]={
 {"*",8486937},
 {"+",8421399},
 {"+Move",10584230},
@@ -697,10 +698,11 @@
 {"bnot",8421412},
 {"bor",8421410},
 {"bxor",8421411},
 {"c?",8421427},
 {"chain",8421520},
+{"cut",8683730},
 {"cz?",8421428},
 {"dup",8421377},
 {"el",8683532},
 {"else",8683530},
 {"eq",8421418},
@@ -744,7 +746,7 @@
 {"tmark",8421572},
 {"tuck",8421380},
 {"until",8683535},
 {"while",8683536},
 };
-#define N_OP_NAMES 318
+#define N_OP_NAMES 319
 #endif