Free Hero Mesh

Check-in [87a8bc6a74]
Login
This is a mirror of the main repository for Free Hero Mesh. New tickets and changes will not be accepted at this mirror.
Overview
Comment:Implement coordinate input.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 87a8bc6a74f1c8ca189ba21dc66c980a121c628b
User & Date: user on 2022-07-06 19:36:13
Other Links: manifest | tags
Context
2022-07-07
00:08
Add the documentation about coordinate input binding into the bindings.doc file. check-in: ef46fdbc36 user: user tags: trunk
2022-07-06
19:36
Implement coordinate input. check-in: 87a8bc6a74 user: user tags: trunk
05:20
Several changes needed to allow coordinate input to be implemented in future (including the CLICK message and InputXY keyword). Also fixes a bug that was caused by changing MoveItem from Uint8 to Uint16. check-in: 156b0a7c35 user: user tags: trunk
Changes

Modified TODO from [2d7748f974] to [8666aa5000].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* Sound effects
  * Compressed wave sounds (?)
  * Numeric sounds (?)
* Game engine features
  * String data (partially implemented)
  * A ,PopUp command to use a popup with arguments starting from a mark
  * Returning a class from COLLIDE/COLLIDEBY to transform
  * Coordinate input (may be suitable for some kind of games)
  * Possibility to define auto-generation levels mode
* Editor
  * Mouse dragging
  * Level index editor
* Deal better with allowing to skip past corrupted levels
* Picture editor/loading
  * Allowing more altimages







<







1
2
3
4
5
6
7

8
9
10
11
12
13
14
* Sound effects
  * Compressed wave sounds (?)
  * Numeric sounds (?)
* Game engine features
  * String data (partially implemented)
  * A ,PopUp command to use a popup with arguments starting from a mark
  * Returning a class from COLLIDE/COLLIDEBY to transform

  * Possibility to define auto-generation levels mode
* Editor
  * Mouse dragging
  * Level index editor
* Deal better with allowing to skip past corrupted levels
* Picture editor/loading
  * Allowing more altimages

Modified class.c from [1c13f6111e] to [23e8cf302b].

49
50
51
52
53
54
55

56
57
58
59
60
61
62
char**stringpool;
AnimationSlot anim_slot[8];
Uint8 keymask[256/8];
Uint16 array_size;
Uint16*orders;
Uint8 norders;
Uint16 control_class;


char*ll_head;
DisplayColumn*ll_disp;
Uint8 ll_ndisp;
DataColumn*ll_data;
Uint8 ll_ndata;
Uint8 ll_naggregate;







>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
char**stringpool;
AnimationSlot anim_slot[8];
Uint8 keymask[256/8];
Uint16 array_size;
Uint16*orders;
Uint8 norders;
Uint16 control_class;
Uint8 has_xy_input;

char*ll_head;
DisplayColumn*ll_disp;
Uint8 ll_ndisp;
DataColumn*ll_data;
Uint8 ll_ndata;
Uint8 ll_naggregate;
2675
2676
2677
2678
2679
2680
2681





2682
2683
2684
2685
2686
2687
2688
          }
          nxttok();
          if(tokent!=TF_CLOSE) ParseError("Expected close parenthesis\n");
          break;
        case OP_LEVELTABLE:
          level_table_definition();
          break;





        default:
          ParseError("Invalid top level definition: %s\n",tokenstr);
      }
    } else {
      ParseError("Invalid top level definition\n");
    }
  }







>
>
>
>
>







2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
          }
          nxttok();
          if(tokent!=TF_CLOSE) ParseError("Expected close parenthesis\n");
          break;
        case OP_LEVELTABLE:
          level_table_definition();
          break;
        case OP_INPUTXY:
          has_xy_input=1;
          nxttok();
          if(tokent!=TF_CLOSE) ParseError("Expected close parenthesis\n");
          break;
        default:
          ParseError("Invalid top level definition: %s\n",tokenstr);
      }
    } else {
      ParseError("Invalid top level definition\n");
    }
  }

Modified class.doc from [09d41b7e9c] to [b650a160b5].

285
286
287
288
289
290
291





292
293
294
295
296
297
298
  created, destroyed, or moved. It still receives broadcast messages
  normally, and can also be addressed specifically.

(CollisionLayers <userflags...>)
  Define user flags as CollisionLayers bits; the first defined flag is
  bit0. Up to 8 flags can be defined in this way.






(LevelTable <definitions...>)
  Define the level table. See the section about level table definition
  for details. Can only occur once.

(Misc4 <userflags...>)
  Define user flags as Misc4 bits; the first defined flag is bit0. Up to
  32 flags can be defined in this way.







>
>
>
>
>







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
  created, destroyed, or moved. It still receives broadcast messages
  normally, and can also be addressed specifically.

(CollisionLayers <userflags...>)
  Define user flags as CollisionLayers bits; the first defined flag is
  bit0. Up to 8 flags can be defined in this way.

(InputXY <options...>)
  Enables coordinate input. Currently there are no options, so the options
  must be left blank. See the documentation about the CLICK message for
  more details about coordinate input.

(LevelTable <definitions...>)
  Define the level table. See the section about level table definition
  for details. Can only occur once.

(Misc4 <userflags...>)
  Define user flags as Misc4 bits; the first defined flag is bit0. Up to
  32 flags can be defined in this way.
1610
1611
1612
1613
1614
1615
1616
1617

1618
1619
1620
1621
1622
1623
1624
  successfully teleported.

,JumpTo  ( obj x y -- bool ) **
  As ,MoveTo but sends JUMPED message to that object after it has been
  successfully teleported.

Key  ( -- number )
  During the input phase, the key input. During other phases, zero.


land  ( in1 in2 -- out )
  Logical AND.

le  ( in1 in2 -- bool )
  Test if first input is less or equal to second input (unsigned).








|
>







1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
  successfully teleported.

,JumpTo  ( obj x y -- bool ) **
  As ,MoveTo but sends JUMPED message to that object after it has been
  successfully teleported.

Key  ( -- number )
  During the input phase, the key input. During other phases, zero. During
  the input phase of coordinate input, this value will be 1.

land  ( in1 in2 -- out )
  Logical AND.

le  ( in1 in2 -- bool )
  Test if first input is less or equal to second input (unsigned).

2258
2259
2260
2261
2262
2263
2264









2265
2266
2267
2268
2269
2270
2271
  When scheduling deferred movement, if the moving object is blocked by
  another object, then the moving object sends this message to the object
  which is blocking it. From is the object attempting to move, Arg1 and
  Arg2 are that object's coordinates, and Arg3 is 1 for +Move or 0 for
  -Move. Of the return value, bit0 clears the Moving flag (causing the
  move to fail), and bit1 causes it to not send any more BLOCKED messages.










COLLIDE
  Received when this object is trying to move into a location where there
  is a collision, so it can't move there. Of the return value, bit0 means
  to prevent the movement (even if the objects are moved out of the way or
  destroyed in order to make room), bit1 means to not send any COLLIDEBY
  messages, and bit2 means to pretend the move attempt was successful even
  if it isn't successful. Even if bit0 is set, that won't prevent sending







>
>
>
>
>
>
>
>
>







2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
  When scheduling deferred movement, if the moving object is blocked by
  another object, then the moving object sends this message to the object
  which is blocking it. From is the object attempting to move, Arg1 and
  Arg2 are that object's coordinates, and Arg3 is 1 for +Move or 0 for
  -Move. Of the return value, bit0 clears the Moving flag (causing the
  move to fail), and bit1 causes it to not send any more BLOCKED messages.

CLICK
  Sent during coordinate input. It is first sent to the control object,
  and then if IgnoreKey is not executed, to each object at the clicked
  coordinates, from top to bottom; it will stop if any one returns nonzero
  or if the object that received this message has moved. Arg1 and Arg2 are
  the X and Y coordinates. Arg3 is zero for the control object, but is the
  return value of the CLICK message from the control object when this
  message is sent to other objects.

COLLIDE
  Received when this object is trying to move into a location where there
  is a collision, so it can't move there. Of the return value, bit0 means
  to prevent the movement (even if the objects are moved out of the way or
  destroyed in order to make room), bit1 means to not send any COLLIDEBY
  messages, and bit2 means to pretend the move attempt was successful even
  if it isn't successful. Even if bit0 is set, that won't prevent sending

Modified default.heromeshrc from [62267c997a] to [f6f6f8f1b2].

101
102
103
104
105
106
107

108
109
110
111
112
113
114
?.gameKey.alt.N: 'NUMLOCK
?.gameKey.alt.O: 'SCRLOCK
?.gameKey.alt.S: 'SHIFT
?.gameKey.alt.T: 'TAB
?.gameKey.alt.Z: 'CTRL

! Game key bindings

?.gameKey.ctrl.D: select '^d',$key_xy;
?.gameKey.ctrl.E: ^E
?.gameKey.ctrl.I: select 'mi',:import_move_list;
?.gameKey.ctrl.K: with a(x,y,z) as (select count() filter (where solved),count() filter (where solved or solvable),count() from levels) select ':m',x||'/'||y||' ('||(100*x/y)||'%)'||iif(z-y,' + '||(z-y),'') from a;
?.gameKey.ctrl.L: ^L
?.gameKey.ctrl.R: select 'go',id from levels order by random() limit 1;
?.gameKey.ctrl.X: select 'mx',:export_move_list;







>







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
?.gameKey.alt.N: 'NUMLOCK
?.gameKey.alt.O: 'SCRLOCK
?.gameKey.alt.S: 'SHIFT
?.gameKey.alt.T: 'TAB
?.gameKey.alt.Z: 'CTRL

! Game key bindings
?.gameClick.left: select 'xy',$X,$Y where has_xy_input();
?.gameKey.ctrl.D: select '^d',$key_xy;
?.gameKey.ctrl.E: ^E
?.gameKey.ctrl.I: select 'mi',:import_move_list;
?.gameKey.ctrl.K: with a(x,y,z) as (select count() filter (where solved),count() filter (where solved or solvable),count() from levels) select ':m',x||'/'||y||' ('||(100*x/y)||'%)'||iif(z-y,' + '||(z-y),'') from a;
?.gameKey.ctrl.L: ^L
?.gameKey.ctrl.R: select 'go',id from levels order by random() limit 1;
?.gameKey.ctrl.X: select 'mx',:export_move_list;

Modified exec.c from [a0ee5dcd19] to [4c5452cf58].

3597
3598
3599
3600
3601
3602
3603

3604
3605
3606
3607
3608
3609
3610

3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637


3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651






















3652
3653
3654

3655
3656
3657
3658
3659
3660
3661

const char*execute_turn(int key) {
  Uint8 busy,clock,x,y;
  Uint32 m,n,turn,tc;
  Object*o;
  Value v;
  int i;

  if(!key) {
    // This is part of initialization; if anything triggered, it must be executed now
    all_flushed=1;
    goto trig;
  }
  if(setjmp(my_env)) return my_error;
  if(quiz_text) {

    sqlite3_free(quiz_text);
    quiz_text=0;
    if(key_ignored) {
      if(quiz_obj.t) quiz_obj=NVALUE(0); else return 0;
    } else if(!quiz_obj.t) {
      move_number++;
      return 0;
    }
  }
  changed=0;
  key_ignored=0;
  all_flushed=0;
  lastimage_processing=0;
  vstackptr=0;
  current_key=key;
  tc=0;
  for(n=0;n<nobjects;n++) if(o=objects[n]) {
    o->distance=0;
    o->oflags&=~(OF_KEYCLEARED|OF_DONE|OF_MOVING);
    if(o->anim) {
      o->anim->count=0;
      if(o->anim->status==ANISTAT_VISUAL) o->anim->status=0;
    }
  }
  // Input phase
  m=VOIDLINK;
  v=NVALUE(0);


  if(!quiz_obj.t) {
    n=lastobj;
    while(n!=VOIDLINK) {
      i=classes[objects[n]->class]->cflags;
      if(i&CF_INPUT) v=send_message(VOIDLINK,n,MSG_KEY,NVALUE(key),v,NVALUE(0));
      if(i&CF_PLAYER) m=n;
      n=objects[n]->prev;
    }
  } else {
    n=quiz_obj.u;
    if(objects[n]->generation!=quiz_obj.t) n=VOIDLINK;
    quiz_obj=NVALUE(0);
    if(classes[objects[n]->class]->cflags&CF_COMPATIBLE) all_flushed=1;
    v=send_message(VOIDLINK,n,MSG_KEY,NVALUE(key),NVALUE(0),NVALUE(1));






















  }
  current_key=0;
  if(key_ignored) {

    quiz_obj=NVALUE(0);
    return changed?"Invalid use of IgnoreKey":0;
  }
  move_number++;
  // Beginning phase
  if(!all_flushed) {
    if(m==VOIDLINK) x=0,y=0; else x=objects[m]->x,y=objects[m]->y;







>







>














<
<











>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



>







3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626


3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686

const char*execute_turn(int key) {
  Uint8 busy,clock,x,y;
  Uint32 m,n,turn,tc;
  Object*o;
  Value v;
  int i;
  tc=0;
  if(!key) {
    // This is part of initialization; if anything triggered, it must be executed now
    all_flushed=1;
    goto trig;
  }
  if(setjmp(my_env)) return my_error;
  if(quiz_text) {
    if(key>256 && !key_ignored) return 0;
    sqlite3_free(quiz_text);
    quiz_text=0;
    if(key_ignored) {
      if(quiz_obj.t) quiz_obj=NVALUE(0); else return 0;
    } else if(!quiz_obj.t) {
      move_number++;
      return 0;
    }
  }
  changed=0;
  key_ignored=0;
  all_flushed=0;
  lastimage_processing=0;
  vstackptr=0;


  for(n=0;n<nobjects;n++) if(o=objects[n]) {
    o->distance=0;
    o->oflags&=~(OF_KEYCLEARED|OF_DONE|OF_MOVING);
    if(o->anim) {
      o->anim->count=0;
      if(o->anim->status==ANISTAT_VISUAL) o->anim->status=0;
    }
  }
  // Input phase
  m=VOIDLINK;
  v=NVALUE(0);
  if(key>=8 && key<256) {
    current_key=key;
    if(!quiz_obj.t) {
      n=lastobj;
      while(n!=VOIDLINK) {
        i=classes[objects[n]->class]->cflags;
        if(i&CF_INPUT) v=send_message(VOIDLINK,n,MSG_KEY,NVALUE(key),v,NVALUE(0));
        if(i&CF_PLAYER) m=n;
        n=objects[n]->prev;
      }
    } else {
      n=quiz_obj.u;
      if(objects[n]->generation!=quiz_obj.t) n=VOIDLINK;
      quiz_obj=NVALUE(0);
      if(classes[objects[n]->class]->cflags&CF_COMPATIBLE) all_flushed=1;
      v=send_message(VOIDLINK,n,MSG_KEY,NVALUE(key),NVALUE(0),NVALUE(1));
    }
  } else if(has_xy_input && key>=0x8000 && key<=0x8FFF) {
    x=((key>>6)&63)+1; y=(key&63)+1;
    if(x>pfwidth || y>pfheight) return "Illegal move";
    current_key=KEY_XY;
    if(control_obj!=VOIDLINK) {
      quiz_obj=NVALUE(0);
      v=send_message(VOIDLINK,control_obj,MSG_CLICK,NVALUE(x),NVALUE(y),NVALUE(0));
      if(key_ignored) goto ignored;
    }
    m=n=playfield[x+y*64-65];
    while(m!=VOIDLINK) {
      m=objects[m]->up;
      if(m!=VOIDLINK) n=m;
    }
    while(n!=VOIDLINK) {
      if(objects[n]->x!=x || objects[n]->y!=y) break;
      if(v_bool(send_message(VOIDLINK,n,MSG_CLICK,NVALUE(x),NVALUE(y),v))) break;
      n=objects[n]->down;
    }
  } else {
    return "Illegal move";
  }
  current_key=0;
  if(key_ignored) {
    ignored:
    quiz_obj=NVALUE(0);
    return changed?"Invalid use of IgnoreKey":0;
  }
  move_number++;
  // Beginning phase
  if(!all_flushed) {
    if(m==VOIDLINK) x=0,y=0; else x=objects[m]->x,y=objects[m]->y;

Modified fileformat.doc from [723b223c5e] to [5da3136896].

149
150
151
152
153
154
155





156
157
158
159
160
161
162

* Move list: One byte per turn, being the key codes. (Use of numbers 0-7
here is reserved for future use; they are not valid key codes.)

(Free Hero Mesh currently ignores the comment and time stamp, although
this might change in a future version of Free Hero Mesh.)







=== xclass/*.DEP ===

A dependent picture (defined by transforming one or more other pictures);
the part of the name before the dot is the picture name.

It starts with the base picture name (which must be a IMG lump, and cannot







>
>
>
>
>







149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

* Move list: One byte per turn, being the key codes. (Use of numbers 0-7
here is reserved for future use; they are not valid key codes.)

(Free Hero Mesh currently ignores the comment and time stamp, although
this might change in a future version of Free Hero Mesh.)

Special key codes are:

* 1 = Coordinate input; follow by two more bytes being the X coordinate
and Y coordinate (both in the range 1 to 64).


=== xclass/*.DEP ===

A dependent picture (defined by transforming one or more other pictures);
the part of the name before the dot is the picture name.

It starts with the base picture name (which must be a IMG lump, and cannot

Modified function.c from [0fcaf673c8] to [793458f469].

170
171
172
173
174
175
176




177
178
179
180
181
182
183
  const unsigned char*u=sqlite3_value_blob(*argv);
  int n=sqlite3_value_bytes(*argv);
  long long h=sqlite3_value_int64(argv[1]);
  int m=hash_length(h);
  if(sqlite3_value_type(*argv)==SQLITE_NULL || !m) return;
  sqlite3_result_blob(cxt,hash_buffer(h,u,n),m,free);
}





static void fn_heromesh_escape(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  const unsigned char*u=sqlite3_value_blob(*argv);
  int un=sqlite3_value_bytes(*argv);
  char*e;
  int en=0;
  int i=0;







>
>
>
>







170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
  const unsigned char*u=sqlite3_value_blob(*argv);
  int n=sqlite3_value_bytes(*argv);
  long long h=sqlite3_value_int64(argv[1]);
  int m=hash_length(h);
  if(sqlite3_value_type(*argv)==SQLITE_NULL || !m) return;
  sqlite3_result_blob(cxt,hash_buffer(h,u,n),m,free);
}

static void fn_has_xy_input(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  sqlite3_result_int(cxt,has_xy_input);
}

static void fn_heromesh_escape(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  const unsigned char*u=sqlite3_value_blob(*argv);
  int un=sqlite3_value_bytes(*argv);
  char*e;
  int en=0;
  int i=0;
1789
1790
1791
1792
1793
1794
1795

1796
1797
1798
1799
1800
1801
1802
  sqlite3_create_function(userdb,"BEST_MOVE_LIST",0,SQLITE_UTF8,0,fn_best_move_list,0,0);
  sqlite3_create_function(userdb,"BEST_SCORE",0,SQLITE_UTF8,0,fn_best_score,0,0);
  sqlite3_create_function(userdb,"BYTE",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_byte,0,0);
  sqlite3_create_function(userdb,"CL",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cl,0,0);
  sqlite3_create_function(userdb,"CLASS_DATA",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_class_data,0,0);
  sqlite3_create_function(userdb,"CVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cvalue,0,0);
  sqlite3_create_function(userdb,"HASH",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_hash,0,0);

  sqlite3_create_function(userdb,"HEROMESH_ESCAPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_escape,0,0);
  sqlite3_create_function(userdb,"HEROMESH_TYPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_type,0,0);
  sqlite3_create_function(userdb,"HEROMESH_UNESCAPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_unescape,0,0);
  sqlite3_create_function(userdb,"INRECT",2,SQLITE_UTF8,0,fn_inrect,0,0);
  sqlite3_create_function(userdb,"LEVEL",0,SQLITE_UTF8,&level_ord,fn_level,0,0);
  sqlite3_create_function(userdb,"LEVEL_CACHEID",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,ptr0,fn_cacheid,0,0);
  sqlite3_create_function(userdb,"LEVEL_ID",0,SQLITE_UTF8,&level_id,fn_level,0,0);







>







1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
  sqlite3_create_function(userdb,"BEST_MOVE_LIST",0,SQLITE_UTF8,0,fn_best_move_list,0,0);
  sqlite3_create_function(userdb,"BEST_SCORE",0,SQLITE_UTF8,0,fn_best_score,0,0);
  sqlite3_create_function(userdb,"BYTE",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_byte,0,0);
  sqlite3_create_function(userdb,"CL",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cl,0,0);
  sqlite3_create_function(userdb,"CLASS_DATA",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_class_data,0,0);
  sqlite3_create_function(userdb,"CVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_cvalue,0,0);
  sqlite3_create_function(userdb,"HASH",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_hash,0,0);
  sqlite3_create_function(userdb,"HAS_XY_INPUT",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_has_xy_input,0,0);
  sqlite3_create_function(userdb,"HEROMESH_ESCAPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_escape,0,0);
  sqlite3_create_function(userdb,"HEROMESH_TYPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_type,0,0);
  sqlite3_create_function(userdb,"HEROMESH_UNESCAPE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_heromesh_unescape,0,0);
  sqlite3_create_function(userdb,"INRECT",2,SQLITE_UTF8,0,fn_inrect,0,0);
  sqlite3_create_function(userdb,"LEVEL",0,SQLITE_UTF8,&level_ord,fn_level,0,0);
  sqlite3_create_function(userdb,"LEVEL_CACHEID",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,ptr0,fn_cacheid,0,0);
  sqlite3_create_function(userdb,"LEVEL_ID",0,SQLITE_UTF8,&level_id,fn_level,0,0);

Modified game.c from [d2afebc6be] to [05d4b7dc52].

43
44
45
46
47
48
49





50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69



70
71
72
73
74
75
76

int encode_move(FILE*fp,MoveItem v) {
  // Encodes a single move and writes the encoded move to the file.
  // Returns the number of bytes of the encoded move.
  if(v>=8 && v<256) {
    fputc(v,fp);
    return 1;





  } else {
    fatal("Unencodable move (%u)\n",(int)v);
  }
}

int encode_move_list(FILE*fp) {
  // Encodes the current replay list into the file; returns the number of bytes.
  // Does not write a null terminator.
  int i;
  int c=0;
  for(i=0;i<replay_count;i++) c+=encode_move(fp,replay_list[i]);
  return c;
}

MoveItem decode_move(FILE*fp) {
  // Decodes a single move from the file, and returns the move.
  // Returns zero if there is no more moves.
  int v=fgetc(fp);
  if(v>=8 && v<256) {
    return v;



  } else if(v==EOF || !v) {
    return 0;
  } else {
    fatal("Undecodable move (%u)\n",v);
  }
}








>
>
>
>
>




















>
>
>







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

int encode_move(FILE*fp,MoveItem v) {
  // Encodes a single move and writes the encoded move to the file.
  // Returns the number of bytes of the encoded move.
  if(v>=8 && v<256) {
    fputc(v,fp);
    return 1;
  } else if(v>=0x8000 && v<=0x8FFF) {
    fputc(KEY_XY,fp);
    fputc(((v>>6)&63)+1,fp);
    fputc((v&63)+1,fp);
    return 3;
  } else {
    fatal("Unencodable move (%u)\n",(int)v);
  }
}

int encode_move_list(FILE*fp) {
  // Encodes the current replay list into the file; returns the number of bytes.
  // Does not write a null terminator.
  int i;
  int c=0;
  for(i=0;i<replay_count;i++) c+=encode_move(fp,replay_list[i]);
  return c;
}

MoveItem decode_move(FILE*fp) {
  // Decodes a single move from the file, and returns the move.
  // Returns zero if there is no more moves.
  int v=fgetc(fp);
  if(v>=8 && v<256) {
    return v;
  } else if(v==KEY_XY) {
    v=0x8000|((fgetc(fp)-1)<<6);
    return v|(fgetc(fp)-1);
  } else if(v==EOF || !v) {
    return 0;
  } else {
    fatal("Undecodable move (%u)\n",v);
  }
}

204
205
206
207
208
209
210
211









212
213
214
215
216
217
218
    snprintf(buf,8,"%5d",replay_pos);
    draw_text(8,52,buf,0xF0,0xF9);
    snprintf(buf,8,"%5d",replay_count);
    draw_text(8,screen->h-8,buf,0xF0,solution_replay?0xFA:0xFC);
    for(y=44,x=replay_pos-(screen->h-68)/32;;x++) {
      y+=16;
      if(y+24>screen->h) break;
      if(x>=0 && x<replay_count) draw_key(16,y,replay_list[x],0xF8,0xFB);









      if(x==replay_count) draw_key(16,y,1,0xF0,0xF8);
      if(x==replay_pos) draw_text(0,y,inserting?"I~":"~~",0xF0,0xFE);
      if(x==replay_mark) draw_text(32,y,"~~",0xF0,0xFD);
    }
    SDL_UnlockSurface(screen);
  }
  if(quiz_text) draw_popup(quiz_text);







|
>
>
>
>
>
>
>
>
>







212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    snprintf(buf,8,"%5d",replay_pos);
    draw_text(8,52,buf,0xF0,0xF9);
    snprintf(buf,8,"%5d",replay_count);
    draw_text(8,screen->h-8,buf,0xF0,solution_replay?0xFA:0xFC);
    for(y=44,x=replay_pos-(screen->h-68)/32;;x++) {
      y+=16;
      if(y+24>screen->h) break;
      if(x>=0 && x<replay_count) {
        if(replay_list[x]<256) {
          draw_key(16,y,replay_list[x],0xF8,0xFB);
        } else if((replay_list[x]&0xF000)==0x8000) {
          sprintf(buf,"%02u",((replay_list[x]>>6)&63)+1);
          draw_text(16,y,buf,0xF8,0x47);
          sprintf(buf,"%02u",(replay_list[x]&63)+1);
          draw_text(16,y+8,buf,0xF8,0x45);
        }
      }
      if(x==replay_count) draw_key(16,y,1,0xF0,0xF8);
      if(x==replay_pos) draw_text(0,y,inserting?"I~":"~~",0xF0,0xFE);
      if(x==replay_mark) draw_text(32,y,"~~",0xF0,0xFD);
    }
    SDL_UnlockSurface(screen);
  }
  if(quiz_text) draw_popup(quiz_text);
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
  sqlite3_finalize(st);
  if(i==1) begin_level(mo&8?-sel-divmin:~sel);
  return i;
}

static int game_command(int prev,int cmd,int number,int argc,sqlite3_stmt*args,void*aux) {
  switch(cmd) {
    case '\' ': // Play a move
      if(replay_time) {
        replay_time=0;
        return -3;
      }
      if(solution_replay) {
        screen_message("You cannot play your own moves during the solution replay");
        return -3;







|







1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
  sqlite3_finalize(st);
  if(i==1) begin_level(mo&8?-sel-divmin:~sel);
  return i;
}

static int game_command(int prev,int cmd,int number,int argc,sqlite3_stmt*args,void*aux) {
  switch(cmd) {
    case '\' ': play: // Play a move
      if(replay_time) {
        replay_time=0;
        return -3;
      }
      if(solution_replay) {
        screen_message("You cannot play your own moves during the solution replay");
        return -3;
1348
1349
1350
1351
1352
1353
1354







1355
1356
1357
1358
1359
1360
1361
      do_export_moves(sqlite3_column_text(args,1));
      return 0;
    case 'rs': // Replay speed
      number+=replay_speed;
      if(number<1) number=1; else if(number>255) number=255;
      replay_speed=number;
      return prev;







    default:
      return prev;
  }
}

static void do_autowin(void) {
  const char*name;







>
>
>
>
>
>
>







1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
      do_export_moves(sqlite3_column_text(args,1));
      return 0;
    case 'rs': // Replay speed
      number+=replay_speed;
      if(number<1) number=1; else if(number>255) number=255;
      replay_speed=number;
      return prev;
    case 'xy': // Coordinate input
      if(argc<3 || !has_xy_input) break;
      argc=sqlite3_column_int(args,1);
      number=sqlite3_column_int(args,2);
      if(argc<1 || argc>pfwidth || number<1 || number>pfheight) return 0;
      number=(number-1)|((argc-1)<<6)|0x8000;
      goto play;
    default:
      return prev;
  }
}

static void do_autowin(void) {
  const char*name;
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
    static SDL_Event ev={SDL_USEREVENT};
    SDL_PushEvent(&ev);
  }
  timerflag=1;
  return n;
}

static inline void input_move(Uint8 k) {
  const char*t=execute_turn(k);
  if(replay_pos>0xFFFE && !gameover) t="Too many moves played";
  if(t) {
    screen_message(t);
    gameover=-1;
    return;
  }







|







1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
    static SDL_Event ev={SDL_USEREVENT};
    SDL_PushEvent(&ev);
  }
  timerflag=1;
  return n;
}

static inline void input_move(MoveItem k) {
  const char*t=execute_turn(k);
  if(replay_pos>0xFFFE && !gameover) t="Too many moves played";
  if(t) {
    screen_message(t);
    gameover=-1;
    return;
  }

Modified heromesh.h from [38b9ed2d66] to [c5267bcf80].

182
183
184
185
186
187
188

189
190
191
192
193
194
195
extern char**stringpool;
extern AnimationSlot anim_slot[8];
extern Uint8 keymask[256/8];
extern Uint16 array_size;
extern Uint16*orders;
extern Uint8 norders;
extern Uint16 control_class;


typedef struct {
  // Flags: 1=fill-width, 2=multi-colours, 4=built-in-data
  Uint8 width,data,color,flag;
  Uint8 form[2];
  Uint16 ptr;
} DisplayColumn;







>







182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
extern char**stringpool;
extern AnimationSlot anim_slot[8];
extern Uint8 keymask[256/8];
extern Uint16 array_size;
extern Uint16*orders;
extern Uint8 norders;
extern Uint16 control_class;
extern Uint8 has_xy_input; // zero if not, nonzero if it has

typedef struct {
  // Flags: 1=fill-width, 2=multi-colours, 4=built-in-data
  Uint8 width,data,color,flag;
  Uint8 form[2];
  Uint16 ptr;
} DisplayColumn;
241
242
243
244
245
246
247

248
249
250
251
252
253
254
#define ANI_STOP 0x00
#define ANI_ONCE 0x01
#define ANI_LOOP 0x02
#define ANI_OSC 0x08
#define ANI_SYNC 0x80

// Special key codes; used in encoded move lists and in some cases also values for Key

#define KEY_XY 1

typedef struct {
  Uint8 flag,start,end;
  union {
    Uint8 speed; // unsynchronized
    Uint8 slot; // synchronized







>







242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#define ANI_STOP 0x00
#define ANI_ONCE 0x01
#define ANI_LOOP 0x02
#define ANI_OSC 0x08
#define ANI_SYNC 0x80

// Special key codes; used in encoded move lists and in some cases also values for Key
// Only numbers 1 to 7 can be used in this way.
#define KEY_XY 1

typedef struct {
  Uint8 flag,start,end;
  union {
    Uint8 speed; // unsynchronized
    Uint8 slot; // synchronized

Modified sql.doc from [e099c9daa4] to [632d4d9f5a].

65
66
67
68
69
70
71




72
73
74
75
76
77
78
  Makes a game value of type 'class', given the class number. You can also
  specify the class name instead of the number.

HASH(data,algorithm)
  Make the hash of the data as a binary blob. See hash.h for a list of the
  valid numbers to use as the hash algorithm numbers.





HEROMESH_ESCAPE(blob)
  Converts blob representation of a game string into escaped format.

HEROMESH_TYPE(value)
  The type of a game value, given as a 64-bit integer. The types are
  'class', 'number', 'string', 'object', and 'sound'.








>
>
>
>







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
  Makes a game value of type 'class', given the class number. You can also
  specify the class name instead of the number.

HASH(data,algorithm)
  Make the hash of the data as a binary blob. See hash.h for a list of the
  valid numbers to use as the hash algorithm numbers.

HAS_XY_INPUT()
  Returns nonzero if this puzzle set has coordinate input, or zero if it
  does not have coordinate input.

HEROMESH_ESCAPE(blob)
  Converts blob representation of a game string into escaped format.

HEROMESH_TYPE(value)
  The type of a game value, given as a 64-bit integer. The types are
  'class', 'number', 'string', 'object', and 'sound'.