Index: class.c
==================================================================
--- class.c
+++ class.c
@@ -13,10 +13,11 @@
 #include <stdlib.h>
 #include <string.h>
 #include "sqlite3.h"
 #include "smallxrm.h"
 #include "heromesh.h"
+#include "names.h"
 
 #define TF_COMMA 0x0001 // has a comma modifier
 #define TF_EQUAL 0x0002 // has an equal sign modifier
 #define TF_ABNORMAL 0x0004 // with TF_NAME, not directly an opcode
 #define TF_COMPAT 0x0008 // add 1 to opcode in compatibility mode
@@ -45,10 +46,11 @@
 Sint32 max_volume=10000;
 Uint8 back_color=1;
 Uint8 inv_back_color=9;
 char**stringpool;
 AnimationSlot anim_slot[8];
+Uint8 keymask[256/8];
 
 #define HASH_SIZE 8888
 #define LOCAL_HASH_SIZE 5555
 typedef struct {
   Uint16 id;
@@ -1357,10 +1359,11 @@
   Hash*hash=calloc(LOCAL_HASH_SIZE,sizeof(Hash));
   Class*cl=classes[cla];
   int ptr=0;
   int compat=0;
   int i;
+  char disp=0;
   if(!hash) fatal("Allocation failed\n");
   if(!cl) fatal("Confusion of class definition somehow\n");
   if(cl->cflags&(CF_NOCLASS1|CF_NOCLASS2)) {
     cl->cflags=0;
   } else {
@@ -1373,11 +1376,31 @@
       ParseError("Unexpected end of file\n");
     } else if(Tokenf(TF_MACRO)) {
       ParseError("Unexpected macro token\n");
     } else if(Tokenf(TF_OPEN)) {
       nxttok();
-      if(Tokenf(TF_NAME)) {
+      if(Tokenf(TF_KEY)) {
+        if(!disp) {
+          cl->codes=realloc(cl->codes,0x10000*sizeof(Uint16));
+          if(!cl->codes) fatal("Allocation failed\n");
+          if(get_message_ptr(cla,MSG_KEY)!=0xFFFF) ParseError("Class $%s has a KEY message already\n",cl->name);
+          if(ptr>0xFDFF) ParseError("Out of code space\n");
+          disp=1;
+          set_message_ptr(cla,MSG_KEY,ptr);
+          cl->codes[ptr]=OP_DISPATCH;
+          for(i=1;i<256;i++) cl->codes[ptr+i]=0;
+          ptr+=256;
+        }
+        i=tokenv&255;
+        cl->codes[cl->messages[MSG_KEY]+i]=ptr;
+        if(cl->cflags&CF_INPUT) {
+          nxttok();
+          if(tokent!=TF_NAME || tokenv!=OP_IGNOREKEY) keymask[i>>3]|=1<<(i&7);
+          pushback=1;
+        }
+        ptr=parse_instructions(cla,ptr,hash,compat);
+      } else if(Tokenf(TF_NAME)) {
         switch(tokenv) {
           case OP_IMAGE:
             class_def_image(cla);
             break;
           case OP_DEFAULTIMAGE:

Index: class.doc
==================================================================
--- class.doc
+++ class.doc
@@ -383,11 +383,20 @@
 
 (<message> <code...>)
   Make a code block whose entry point is the specified message.
 
 (<key> <code...>)
-  (This is not implemented yet.)
+  Add a code block for key dispatch. This cannot be combined with a KEY
+  message in the same class; it causes the KEY message to be automatically
+  defined for this class. If this class has the Input flag and the first
+  instruction is not IgnoreKey, then it is entered into an internal list
+  of key codes to not ignore. Otherwise, the automatic KEY messages (for
+  all classes with them, not only this one) will have the effect of the
+  IgnoreKey instruction when an unrecognized key is specified. You can
+  still call the KEY message by yourself; Arg1 is the key code, and if
+  Arg3 is true then the implicit IgnoreKey will not be executed (although
+  any explicit IgnoreKey will still work).
 
 
 === Data types ===
 
 The following data types are available:
@@ -403,11 +412,12 @@
 * Object: A reference to an object. There are no literals of this type.
 
 * String: A string in quotation marks. There are no string manipulation
 functions; the only thing that can be done with a string is to display it.
 
-* Sound: A sound.
+* Sound: A named sound effect. Values of this type cannot be compared with
+anything, even other values of the same type.
 
 Some things are not their own types, and are other uses of numbers:
 
 * Null: A null class or null object is represented as zero.
 

Index: exec.c
==================================================================
--- exec.c
+++ exec.c
@@ -1240,10 +1240,22 @@
       quiz_obj.u=from;
     }
     if(*t==31 && t[1]) t+=2; else t+=1;
   }
 }
+
+static int v_dispatch(const Uint16*code) {
+  int i=msgvars.arg1.u;
+  if(msgvars.arg1.t!=TY_NUMBER) Throw("Type mismatch");
+  if(msgvars.arg1.u&~0xFF) {
+    if(current_key && !v_bool(msgvars.arg3)) key_ignored=all_flushed=1;
+    return 0;
+  }
+  if(!i) return 0;
+  if(current_key && !v_bool(msgvars.arg3) && !(keymask[i>>3]&(1<<(i&7)))) key_ignored=all_flushed=1;
+  return code[i];
+}
 
 // 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)))
@@ -1372,10 +1384,11 @@
     case OP_DESTROYED_C: StackReq(1,1); t1=Pop(); Push(NVALUE(v_destroyed(t1))); break;
     case OP_DIR: StackReq(0,1); Push(NVALUE(o->dir)); break;
     case OP_DIR_C: StackReq(1,1); Push(GetVariableOf(dir,NVALUE)); break;
     case OP_DIR_E: NoIgnore(); StackReq(1,0); t1=Pop(); Numeric(t1); o->dir=resolve_dir(obj,t1.u); break;
     case OP_DIR_EC: NoIgnore(); StackReq(2,0); t1=Pop(); Numeric(t1); i=v_object(Pop()); if(i!=VOIDLINK) objects[i]->dir=resolve_dir(i,t1.u); break;
+    case OP_DISPATCH: ptr=v_dispatch(code+ptr-1); if(!ptr) return; break;
     case OP_DISTANCE: StackReq(0,1); Push(NVALUE(o->distance)); break;
     case OP_DISTANCE_C: StackReq(1,1); Push(GetVariableOf(distance,NVALUE)); break;
     case OP_DISTANCE_E: StackReq(1,0); t1=Pop(); Numeric(t1); o->distance=t1.u; break;
     case OP_DISTANCE_EC: StackReq(2,0); t1=Pop(); Numeric(t1); i=v_object(Pop()); if(i!=VOIDLINK) objects[i]->distance=t1.u; break;
     case OP_DIV: StackReq(2,1); t2=Pop(); DivideBy(t2); t1=Pop(); Numeric(t1); Push(NVALUE(t1.u/t2.u)); break;

Index: heromesh.h
==================================================================
--- heromesh.h
+++ heromesh.h
@@ -161,10 +161,11 @@
 extern int max_animation; // max steps in animation queue (default 32)
 extern Sint32 max_volume; // max total volume to allow moving diagonally (default 10000)
 extern Uint8 back_color,inv_back_color;
 extern char**stringpool;
 extern AnimationSlot anim_slot[8];
+extern Uint8 keymask[256/8];
 
 Uint16 get_message_ptr(int c,int m);
 void load_classes(void);
 
 // == bindings ==

Index: instruc
==================================================================
--- instruc
+++ instruc
@@ -252,6 +252,7 @@
 *Local
 *Label
 *String
 *Int16
 *Int32
+*Dispatch
 

Index: instruc.h
==================================================================
--- instruc.h
+++ instruc.h
@@ -365,10 +365,11 @@
 #define OP_LOCAL 32946
 #define OP_LABEL 32947
 #define OP_STRING 32948
 #define OP_INT16 32949
 #define OP_INT32 32950
+#define OP_DISPATCH 32951
 #ifdef HEROMESH_CLASS
 static const Op_Names op_names[]={
 {"*",8486935},
 {"+",8421397},
 {"-",8421398},