Free Hero Mesh

Check-in [2119a02b07]
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 selection rectangles, and PLAYFIELD virtual table.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 2119a02b072d13d700744368dd969fc4d50b0b9b
User & Date: user on 2021-09-30 03:21:12
Other Links: manifest | tags
Context
2021-09-30
21:08
Add SOLUTION_MOVE_LIST function, F9 to flash player location, shift and middle mouse button, and a documentation correction. check-in: 28b482dea1 user: user tags: trunk
03:21
Implement selection rectangles, and PLAYFIELD virtual table. check-in: 2119a02b07 user: user tags: trunk
2021-09-28
21:54
More improvements of documentation of game/editor check-in: 8d8766182a user: user tags: trunk
Changes

Modified TODO from [88c718bc67] to [f9befcda8c].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  * "Goto message" instruction (?)
  * Returning a class from COLLIDE/COLLIDEBY to transform
  * Coordinate input (may be suitable for some kind of games)
  * A way to change the order of objects execution
* Editor
  * Mouse dragging
  * Level index editor
  * Bizarro world
  * Selection rectangles
* Table of contents for levels
  * Can define your own columns
  * User can write SQL queries on them
* Deal better with allowing to skip past corrupted levels
* Picture editor/loading
  * Allowing more altimages
  * Spare page (for temporary use while editing)







<
<







12
13
14
15
16
17
18


19
20
21
22
23
24
25
  * "Goto message" instruction (?)
  * Returning a class from COLLIDE/COLLIDEBY to transform
  * Coordinate input (may be suitable for some kind of games)
  * A way to change the order of objects execution
* Editor
  * Mouse dragging
  * Level index editor


* Table of contents for levels
  * Can define your own columns
  * User can write SQL queries on them
* Deal better with allowing to skip past corrupted levels
* Picture editor/loading
  * Allowing more altimages
  * Spare page (for temporary use while editing)

Modified bindings.doc from [8ef2bcacd1] to [057f589131].

123
124
125
126
127
128
129



130
131
132
133
134
135
136

'^S'
  Save level.

'^T'
  Edit the level title.




'^a' <location>
  Add an object with the current MRU values to that location, if there
  is not already another object of the same class there.

'^c'
  Display the class selection menu.








>
>
>







123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

'^S'
  Save level.

'^T'
  Edit the level title.

'^Z'
  Cancel selection rectangle.

'^a' <location>
  Add an object with the current MRU values to that location, if there
  is not already another object of the same class there.

'^c'
  Display the class selection menu.

145
146
147
148
149
150
151






152
153
154
155
156
157
158
'^u' <location>
  Add an object with the current MRU values to that location, even if
  there is already another object of the same class at that location.

'^w'
  Swap the normal world with the bizarro world.







'am' <class> <image> <misc1> <misc2> <misc3> <dir>
  Add a MRU and select the added MRU.

'em' <object>
  Edit the Misc/Dir of an existing object.

'ex' <command>







>
>
>
>
>
>







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
'^u' <location>
  Add an object with the current MRU values to that location, even if
  there is already another object of the same class at that location.

'^w'
  Swap the normal world with the bizarro world.

'^<' <location>
  Set top corner of selection rectangle.

'^>' <location>
  Set bottom corner of selection rectangle.

'am' <class> <image> <misc1> <misc2> <misc3> <dir>
  Add a MRU and select the added MRU.

'em' <object>
  Edit the Misc/Dir of an existing object.

'ex' <command>

Modified default.heromeshrc from [6c809dba16] to [4827705334].

1
2
3
4
5
6
7
8
! Hero Mesh configuration settings
?.screenWidth: 800
?.screenHeight: 600
?.imageSize: 24
?.traceAll: true
?.showInventory: 0
?.maxTrigger: 32767
?.pasteCommand: xclip -o
|







1
2
3
4
5
6
7
8
! hERo Mesh configuration settings
?.screenWidth: 800
?.screenHeight: 600
?.imageSize: 24
?.traceAll: true
?.showInventory: 0
?.maxTrigger: 32767
?.pasteCommand: xclip -o
152
153
154
155
156
157
158



159
160
161
162
163


164
165
166
167
168
169
170
171
172
173
174
175
176
?.editKey.ctrl.N: ^N
?.editKey.ctrl.P: ^P
?.editKey.ctrl.X: select 're',pfwidth(),pfheight();
?.editKey.space: ^c
?.editKey.return: ^e
?.editKey.f1: select 'im',:Import_Level;
?.editKey.f2: select 'ex',:Export_Level;



?.editClick.left: ^a
?.editClick.ctrl.left: select 'em',id from objects where x=$X and y=$Y and up is null;
?.editClick.alt.left: ^u
?.editClick.right: delete from objects where x=$X and y=$Y and up is null;
?.editClick.ctrl.right: select 'am',class,image,misc1,misc2,misc3,dir from objects where x=$X and y=$Y and up is null;



! Global key bindings
?.?.kp_minus: select 'go',-(level()-1) where level()>1;
?.?.kp_plus: select 'go',-(level()+1) where level()<max_level();
?.?.shift.kp_minus: select 'go',-1;
?.?.shift.kp_plus: select 'go',-max_level();
?.?.ctrl.G: select 'go',-:Go_To_Level where :Go_To_Level=cast(:Go_To_Level as int) and cast(:Go_To_Level as int)>0;
?.?.ctrl.Q: ^Q
?.?.ctrl.S: ^S
?.?.ctrl.T: ^T
?.?.shift.ctrl.M: select ':s';
?.?.f10: select ':x';








>
>
>





>
>













152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
?.editKey.ctrl.N: ^N
?.editKey.ctrl.P: ^P
?.editKey.ctrl.X: select 're',pfwidth(),pfheight();
?.editKey.space: ^c
?.editKey.return: ^e
?.editKey.f1: select 'im',:Import_Level;
?.editKey.f2: select 'ex',:Export_Level;
?.editKey.escape: ^Z
?.editKey.shift.D: delete from objects where inrect(x,y);
?.editKey.shift.F: select '^a',xy(x,y) from playfield where inrect(x,y);
?.editClick.left: ^a
?.editClick.ctrl.left: select 'em',id from objects where x=$X and y=$Y and up is null;
?.editClick.alt.left: ^u
?.editClick.right: delete from objects where x=$X and y=$Y and up is null;
?.editClick.ctrl.right: select 'am',class,image,misc1,misc2,misc3,dir from objects where x=$X and y=$Y and up is null;
?.editClick.shift.left: ^<
?.editClick.shift.right: ^>

! Global key bindings
?.?.kp_minus: select 'go',-(level()-1) where level()>1;
?.?.kp_plus: select 'go',-(level()+1) where level()<max_level();
?.?.shift.kp_minus: select 'go',-1;
?.?.shift.kp_plus: select 'go',-max_level();
?.?.ctrl.G: select 'go',-:Go_To_Level where :Go_To_Level=cast(:Go_To_Level as int) and cast(:Go_To_Level as int)>0;
?.?.ctrl.Q: ^Q
?.?.ctrl.S: ^S
?.?.ctrl.T: ^T
?.?.shift.ctrl.M: select ':s';
?.?.f10: select ':x';

Modified edit.c from [60ceeab4c3] to [f2c9f9d8a1].

13
14
15
16
17
18
19


20
21
22
23
24
25
26
#include <string.h>
#include "sqlite3.h"
#include "smallxrm.h"
#include "heromesh.h"
#include "quarks.h"
#include "cursorshapes.h"



typedef struct {
  Uint16 class;
  Uint8 img,dir;
  Value misc1,misc2,misc3;
} MRU;

#define MRUCOUNT 32







>
>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string.h>
#include "sqlite3.h"
#include "smallxrm.h"
#include "heromesh.h"
#include "quarks.h"
#include "cursorshapes.h"

EditorRect editrect;

typedef struct {
  Uint16 class;
  Uint8 img,dir;
  Value misc1,misc2,misc3;
} MRU;

#define MRUCOUNT 32
320
321
322
323
324
325
326

327
328
329
330
331
332
333
  draw_text(0,40,buf,0xF0,0xF3);
  for(x=0;x<MRUCOUNT;x++) {
    y=picture_size*x+56;
    if(y+picture_size>screen->h) break;
    if(x==curmru) draw_text(0,y,">",0xF0,0xFE);
    if(mru[x].misc1.u|mru[x].misc1.t|mru[x].misc2.u|mru[x].misc2.t|mru[x].misc3.u|mru[x].misc3.t) draw_text(picture_size+16,y,"*",0xF0,0xFB);
  }

  SDL_UnlockSurface(screen);
  r.w=r.h=picture_size;
  r.x=8;
  for(x=0;x<MRUCOUNT;x++) {
    y=picture_size*x+56;
    if(y+picture_size>screen->h) break;
    if(mru[x].class) {







>







322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
  draw_text(0,40,buf,0xF0,0xF3);
  for(x=0;x<MRUCOUNT;x++) {
    y=picture_size*x+56;
    if(y+picture_size>screen->h) break;
    if(x==curmru) draw_text(0,y,">",0xF0,0xFE);
    if(mru[x].misc1.u|mru[x].misc1.t|mru[x].misc2.u|mru[x].misc2.t|mru[x].misc3.u|mru[x].misc3.t) draw_text(picture_size+16,y,"*",0xF0,0xFB);
  }
  if(editrect.x0 && editrect.x1) draw_selection_rectangle();
  SDL_UnlockSurface(screen);
  r.w=r.h=picture_size;
  r.x=8;
  for(x=0;x<MRUCOUNT;x++) {
    y=picture_size*x+56;
    if(y+picture_size>screen->h) break;
    if(mru[x].class) {
1509
1510
1511
1512
1513
1514
1515


















1516
1517
1518
1519
1520
1521
1522
      return -1;
    case '^S': // Save level
      save_level();
      return 1;
    case '^T': // Level title
      edit_string(&level_title);
      return 0;


















    case 'am': // Add MRU
      if(argc<7) return prev;
      for(x=1;x<7;x++) if(sqlite3_column_type(args,x)==SQLITE_NULL) return prev;
      x=sqlite3_column_int(args,1)&0x3FFF;
      if(!x) return prev;
      y=sqlite3_column_int(args,2)&0xFF;
      add_mru(x,y);







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
      return -1;
    case '^S': // Save level
      save_level();
      return 1;
    case '^T': // Level title
      edit_string(&level_title);
      return 0;
    case '^Z': // Cancel rectangle
      editrect.x0=editrect.y0=editrect.x1=editrect.y1=0;
      return prev;
    case '^<': // First corner
      if((number&63?:64)>pfwidth || (number/64?:64)>pfheight) return prev;
      editrect.x0=number&63?:64;
      editrect.y0=number/64?:64;
      goto setrect;
    case '^>': // Second corner
      if((number&63?:64)>pfwidth || (number/64?:64)>pfheight) return prev;
      editrect.x1=number&63?:64;
      editrect.y1=number/64?:64;
      setrect:
      if(!editrect.x1) editrect.x1=editrect.x0;
      if(!editrect.y1) editrect.y1=editrect.y0;
      if(editrect.x0>editrect.x1) x=editrect.x0,editrect.x0=editrect.x1,editrect.x1=x;
      if(editrect.y0>editrect.y1) y=editrect.y0,editrect.y0=editrect.y1,editrect.y1=y;
      return prev;
    case 'am': // Add MRU
      if(argc<7) return prev;
      for(x=1;x<7;x++) if(sqlite3_column_type(args,x)==SQLITE_NULL) return prev;
      x=sqlite3_column_int(args,1)&0x3FFF;
      if(!x) return prev;
      y=sqlite3_column_int(args,2)&0xFF;
      add_mru(x,y);
1557
1558
1559
1560
1561
1562
1563

1564
1565
1566
1567
1568
1569
1570
      number+=curmru;
      // fall through
    case 'mr': // Select MRU absolute
      if(number>=0 && number<MRUCOUNT) curmru=number;
      return 0;
    case 're': // Resize and clear
      if(argc<3) return 0;

      x=sqlite3_column_int(args,1);
      y=sqlite3_column_int(args,2);
      if(x<1 || y<1 || x>64 || y>64) return 0;
      level_changed=1;
      annihilate();
      pfwidth=x;
      pfheight=y;







>







1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
      number+=curmru;
      // fall through
    case 'mr': // Select MRU absolute
      if(number>=0 && number<MRUCOUNT) curmru=number;
      return 0;
    case 're': // Resize and clear
      if(argc<3) return 0;
      editrect.x0=editrect.y0=editrect.x1=editrect.y1=0;
      x=sqlite3_column_int(args,1);
      y=sqlite3_column_int(args,2);
      if(x<1 || y<1 || x>64 || y>64) return 0;
      level_changed=1;
      annihilate();
      pfwidth=x;
      pfheight=y;

Modified edit.doc from [e0a5afd96f] to [df66040602].

157
158
159
160
161
162
163













164
165
166
167
168
169
170
* Push numbers on number pad to set direction. You can also push the +
and - on number pad to adjust direction.

* Push F1 or G to display the (Help) text for this class.

* Push F2 or H to display the (EditorHelp) text for this class.















=== Import/export ===

You can import/export levels.

Push F1 to import, and then at the prompt, type a operating system shell
command which produces the level text in the exported format (in X window







>
>
>
>
>
>
>
>
>
>
>
>
>







157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
* Push numbers on number pad to set direction. You can also push the +
and - on number pad to adjust direction.

* Push F1 or G to display the (Help) text for this class.

* Push F2 or H to display the (EditorHelp) text for this class.


=== Selection rectangle ===

You can use shift and the left/right mouse buttons to set the selection
rectangle, and escape to cancel. If it is selected, then:

* SHIFT+D deletes everything in that area, but leaves the rest of the
game unchanged.

* SHIFT+F fills it with the current MRU. (Existing objects at those
locations will be retained; they are not deleted unless CollisionLayers
bits cause conflicts.)


=== Import/export ===

You can import/export levels.

Push F1 to import, and then at the prompt, type a operating system shell
command which produces the level text in the exported format (in X window
198
199
200
201
202
203
204



205
206
207
208
209
210

211
212

213
  F1        Import level (replacing current level)
  F2        Export level
  F10       SQL
  SPACE     Select class/image
  RETURN    Edit Misc/Dir of current MRU
  UP/DOWN   Select previous/next MRU
  1-9       Select MRU




Mouse (in grid):

  LEFT        Add object
  CTRL+LEFT   Edit Misc/Dir of object
  ALT+LEFT    Add object (allow duplicate)

  RIGHT       Delete object
  CTRL+RIGHT  Copy object to MRU









>
>
>



|
|
|
>
|
|
>

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
  F1        Import level (replacing current level)
  F2        Export level
  F10       SQL
  SPACE     Select class/image
  RETURN    Edit Misc/Dir of current MRU
  UP/DOWN   Select previous/next MRU
  1-9       Select MRU
  ESC       Cancel selection rectangle
  SHIFT+D   Delete all objects in selection rectangle
  SHIFT+F   Fill selection rectangle with current MRU

Mouse (in grid):

  LEFT         Add object
  CTRL+LEFT    Edit Misc/Dir of object
  ALT+LEFT     Add object (allow duplicate)
  SHIFT+LEFT   Set top corner of selection rectangle
  RIGHT        Delete object
  CTRL+RIGHT   Copy object to MRU
  SHIFT+RIGHT  Set bottom corner of selection rectangle

Modified function.c from [e41cea6b80] to [567c012195].

239
240
241
242
243
244
245







246
247
248
249
250
251
252
    } else {
      u[un++]=c;
    }
  }
  done:
  sqlite3_result_blob(cxt,u,un,sqlite3_free);
}








static void fn_level(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  sqlite3_result_int(cxt,*(Uint16*)sqlite3_user_data(cxt));
}

static void fn_level_title(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  if(level_title) sqlite3_result_blob(cxt,level_title,strlen(level_title),SQLITE_TRANSIENT);







>
>
>
>
>
>
>







239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
    } else {
      u[un++]=c;
    }
  }
  done:
  sqlite3_result_blob(cxt,u,un,sqlite3_free);
}

static void fn_inrect(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  int x=sqlite3_value_int(argv[0]);
  int y=sqlite3_value_int(argv[1]);
  if(!editrect.x0 || !editrect.y0) return;
  sqlite3_result_int(cxt,(x>=editrect.x0 && x<=editrect.x1 && y>=editrect.y0 && y<=editrect.y1));
}

static void fn_level(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  sqlite3_result_int(cxt,*(Uint16*)sqlite3_user_data(cxt));
}

static void fn_level_title(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  if(level_title) sqlite3_result_blob(cxt,level_title,strlen(level_title),SQLITE_TRANSIENT);
358
359
360
361
362
363
364




365
366
367
368
369
370
371
    if(!buf) fatal("Allocation failed\n");
    if(fread(buf,1,sz,fp)<=0) fatal("I/O error in READ_LUMP_AT() function\n");
    sqlite3_result_blob64(cxt,buf,sz,free);
  } else {
    sqlite3_result_zeroblob64(cxt,0);
  }
}





static void fn_resource(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  int i;
  if(argc>14 || argc<1) {
    sqlite3_result_error(cxt,"Invalid number of XRM resource components",-1);
  } else {
    for(i=0;i<argc;i++) optionquery[i+1]=xrm_make_quark(sqlite3_value_text(argv[i])?:(const unsigned char*)"?",0)?:xrm_anyq;







>
>
>
>







365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    if(!buf) fatal("Allocation failed\n");
    if(fread(buf,1,sz,fp)<=0) fatal("I/O error in READ_LUMP_AT() function\n");
    sqlite3_result_blob64(cxt,buf,sz,free);
  } else {
    sqlite3_result_zeroblob64(cxt,0);
  }
}

static void fn_rect_x0(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  sqlite3_result_int(cxt,*(Uint8*)sqlite3_user_data(cxt));
}

static void fn_resource(sqlite3_context*cxt,int argc,sqlite3_value**argv) {
  int i;
  if(argc>14 || argc<1) {
    sqlite3_result_error(cxt,"Invalid number of XRM resource components",-1);
  } else {
    for(i=0;i<argc;i++) optionquery[i+1]=xrm_make_quark(sqlite3_value_text(argv[i])?:(const unsigned char*)"?",0)?:xrm_anyq;
1072
1073
1074
1075
1076
1077
1078








































1079
1080
1081
1082
1083
1084
1085
1086
1087

1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104




1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120

1121

Module(vt_inventory,
  .xBestIndex=vt1_inventory_index,
  .xColumn=vt1_inventory_column,
  .xFilter=vt1_inventory_filter,
  .xNext=vt1_inventory_next,
);









































void init_sql_functions(sqlite3_int64*ptr0,sqlite3_int64*ptr1) {
  sqlite3_create_function(userdb,"BASENAME",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_basename,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,"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,"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);
  sqlite3_create_function(userdb,"LEVEL_TITLE",0,SQLITE_UTF8,0,fn_level_title,0,0);
  sqlite3_create_function(userdb,"LOAD_LEVEL",1,SQLITE_UTF8,0,fn_load_level,0,0);
  sqlite3_create_function(userdb,"MAX_LEVEL",0,SQLITE_UTF8,0,fn_max_level,0,0);
  sqlite3_create_function(userdb,"MODSTATE",0,SQLITE_UTF8,0,fn_modstate,0,0);
  sqlite3_create_function(userdb,"MOVE_LIST",0,SQLITE_UTF8,0,fn_move_list,0,0);
  sqlite3_create_function(userdb,"MOVENUMBER",0,SQLITE_UTF8,0,fn_movenumber,0,0);
  sqlite3_create_function(userdb,"MVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_mvalue,0,0);
  sqlite3_create_function(userdb,"NVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_zero_extend,0,0);
  sqlite3_create_function(userdb,"OVALUE",1,SQLITE_UTF8,0,fn_ovalue,0,0);
  sqlite3_create_function(userdb,"PFHEIGHT",0,SQLITE_UTF8,&pfheight,fn_pfsize,0,0);
  sqlite3_create_function(userdb,"PFWIDTH",0,SQLITE_UTF8,&pfwidth,fn_pfsize,0,0);
  sqlite3_create_function(userdb,"PICTURE_SIZE",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_picture_size,0,0);
  sqlite3_create_function(userdb,"PIPE",1,SQLITE_UTF8,0,fn_pipe,0,0);
  sqlite3_create_function(userdb,"READ_LUMP_AT",2,SQLITE_UTF8,0,fn_read_lump_at,0,0);




  sqlite3_create_function(userdb,"RESOURCE",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_resource,0,0);
  sqlite3_create_function(userdb,"SIGN_EXTEND",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_sign_extend,0,0);
  sqlite3_create_function(userdb,"SOLUTION_CACHEID",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,ptr1,fn_cacheid,0,0);
  sqlite3_create_function(userdb,"TRACE_OFF",0,SQLITE_UTF8,"",fn_trace_on,0,0);
  sqlite3_create_function(userdb,"TRACE_ON",0,SQLITE_UTF8,"\x01",fn_trace_on,0,0);
  sqlite3_create_function(userdb,"XY",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_xy,0,0);
  sqlite3_create_function(userdb,"ZERO_EXTEND",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_zero_extend,0,0);
  bizarro_vtab=""; // ensure that it is not null
  sqlite3_create_module(userdb,"CLASSES",&vt_classes,"CREATE TABLE `CLASSES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `EDITORHELP` TEXT, `HELP` TEXT,"
   "`INPUT` INT, `QUIZ` INT, `TRACEIN` INT, `TRACEOUT` INT, `GROUP` TEXT, `PLAYER` INT);");
  sqlite3_create_module(userdb,"MESSAGES",&vt_messages,"CREATE TABLE `MESSAGES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `TRACE` INT);");
  sqlite3_create_module(userdb,"OBJECTS",&vt_objects,"CREATE TABLE `OBJECTS`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `MISC1` INT, `MISC2` INT, `MISC3` INT,"
   "`IMAGE` INT, `DIR` INT, `X` INT, `Y` INT, `UP` INT, `DOWN` INT, `DENSITY` INT HIDDEN, `BIZARRO` INT HIDDEN);");
  sqlite3_create_module(userdb,"BIZARRO_OBJECTS",&vt_objects,"CREATE TABLE `OBJECTS`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `MISC1` INT, `MISC2` INT, `MISC3` INT,"
   "`IMAGE` INT, `DIR` INT, `X` INT, `Y` INT, `UP` INT, `DOWN` INT, `DENSITY` INT HIDDEN, `BIZARRO` INT HIDDEN);");
  sqlite3_create_module(userdb,"INVENTORY",&vt_inventory,"CREATE TABLE `INVENTORY`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `IMAGE` INT, `VALUE` INT);");

}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









>

















>
>
>
>
















>

1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178

Module(vt_inventory,
  .xBestIndex=vt1_inventory_index,
  .xColumn=vt1_inventory_column,
  .xFilter=vt1_inventory_filter,
  .xNext=vt1_inventory_next,
);

static int vt1_playfield_index(sqlite3_vtab*vt,sqlite3_index_info*info) {
  int i;
  info->estimatedCost=32*32;
  info->estimatedRows=32*64;
  return SQLITE_OK;
}

static int vt1_playfield_next(sqlite3_vtab_cursor*pcur) {
  Cursor*cur=(void*)pcur;
  ++cur->rowid;
  if((cur->rowid&63)>=pfwidth) cur->rowid=(cur->rowid&~63)+64;
  if(cur->rowid/64>=pfheight) cur->eof=1;
  return SQLITE_OK;
}

static int vt1_playfield_filter(sqlite3_vtab_cursor*pcur,int idxNum,const char*idxStr,int argc,sqlite3_value**argv) {
  Cursor*cur=(void*)pcur;
  cur->rowid=0;
  cur->eof=0;
  return SQLITE_OK;
}

static int vt1_playfield_column(sqlite3_vtab_cursor*pcur,sqlite3_context*cxt,int n) {
  Cursor*cur=(void*)pcur;
  switch(n) {
    case 0: sqlite3_result_int(cxt,(cur->rowid&63)+1); break;
    case 1: sqlite3_result_int(cxt,(cur->rowid/64)+1); break;
    case 2: if(playfield[cur->rowid]!=VOIDLINK) sqlite3_result_int64(cxt,playfield[cur->rowid]); break;
    case 3: if(bizplayfield[cur->rowid]!=VOIDLINK) sqlite3_result_int64(cxt,bizplayfield[cur->rowid]); break;
  }
  return SQLITE_OK;
}

Module(vt_playfield,
  .xBestIndex=vt1_playfield_index,
  .xColumn=vt1_playfield_column,
  .xFilter=vt1_playfield_filter,
  .xNext=vt1_playfield_next,
);

void init_sql_functions(sqlite3_int64*ptr0,sqlite3_int64*ptr1) {
  sqlite3_create_function(userdb,"BASENAME",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_basename,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,"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);
  sqlite3_create_function(userdb,"LEVEL_TITLE",0,SQLITE_UTF8,0,fn_level_title,0,0);
  sqlite3_create_function(userdb,"LOAD_LEVEL",1,SQLITE_UTF8,0,fn_load_level,0,0);
  sqlite3_create_function(userdb,"MAX_LEVEL",0,SQLITE_UTF8,0,fn_max_level,0,0);
  sqlite3_create_function(userdb,"MODSTATE",0,SQLITE_UTF8,0,fn_modstate,0,0);
  sqlite3_create_function(userdb,"MOVE_LIST",0,SQLITE_UTF8,0,fn_move_list,0,0);
  sqlite3_create_function(userdb,"MOVENUMBER",0,SQLITE_UTF8,0,fn_movenumber,0,0);
  sqlite3_create_function(userdb,"MVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_mvalue,0,0);
  sqlite3_create_function(userdb,"NVALUE",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_zero_extend,0,0);
  sqlite3_create_function(userdb,"OVALUE",1,SQLITE_UTF8,0,fn_ovalue,0,0);
  sqlite3_create_function(userdb,"PFHEIGHT",0,SQLITE_UTF8,&pfheight,fn_pfsize,0,0);
  sqlite3_create_function(userdb,"PFWIDTH",0,SQLITE_UTF8,&pfwidth,fn_pfsize,0,0);
  sqlite3_create_function(userdb,"PICTURE_SIZE",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_picture_size,0,0);
  sqlite3_create_function(userdb,"PIPE",1,SQLITE_UTF8,0,fn_pipe,0,0);
  sqlite3_create_function(userdb,"READ_LUMP_AT",2,SQLITE_UTF8,0,fn_read_lump_at,0,0);
  sqlite3_create_function(userdb,"RECT_X0",0,SQLITE_UTF8,&editrect.x0,fn_rect_x0,0,0);
  sqlite3_create_function(userdb,"RECT_X1",0,SQLITE_UTF8,&editrect.x1,fn_rect_x0,0,0);
  sqlite3_create_function(userdb,"RECT_Y0",0,SQLITE_UTF8,&editrect.y0,fn_rect_x0,0,0);
  sqlite3_create_function(userdb,"RECT_Y1",0,SQLITE_UTF8,&editrect.y1,fn_rect_x0,0,0);
  sqlite3_create_function(userdb,"RESOURCE",-1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_resource,0,0);
  sqlite3_create_function(userdb,"SIGN_EXTEND",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_sign_extend,0,0);
  sqlite3_create_function(userdb,"SOLUTION_CACHEID",0,SQLITE_UTF8|SQLITE_DETERMINISTIC,ptr1,fn_cacheid,0,0);
  sqlite3_create_function(userdb,"TRACE_OFF",0,SQLITE_UTF8,"",fn_trace_on,0,0);
  sqlite3_create_function(userdb,"TRACE_ON",0,SQLITE_UTF8,"\x01",fn_trace_on,0,0);
  sqlite3_create_function(userdb,"XY",2,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_xy,0,0);
  sqlite3_create_function(userdb,"ZERO_EXTEND",1,SQLITE_UTF8|SQLITE_DETERMINISTIC,0,fn_zero_extend,0,0);
  bizarro_vtab=""; // ensure that it is not null
  sqlite3_create_module(userdb,"CLASSES",&vt_classes,"CREATE TABLE `CLASSES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `EDITORHELP` TEXT, `HELP` TEXT,"
   "`INPUT` INT, `QUIZ` INT, `TRACEIN` INT, `TRACEOUT` INT, `GROUP` TEXT, `PLAYER` INT);");
  sqlite3_create_module(userdb,"MESSAGES",&vt_messages,"CREATE TABLE `MESSAGES`(`ID` INTEGER PRIMARY KEY, `NAME` TEXT, `TRACE` INT);");
  sqlite3_create_module(userdb,"OBJECTS",&vt_objects,"CREATE TABLE `OBJECTS`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `MISC1` INT, `MISC2` INT, `MISC3` INT,"
   "`IMAGE` INT, `DIR` INT, `X` INT, `Y` INT, `UP` INT, `DOWN` INT, `DENSITY` INT HIDDEN, `BIZARRO` INT HIDDEN);");
  sqlite3_create_module(userdb,"BIZARRO_OBJECTS",&vt_objects,"CREATE TABLE `OBJECTS`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `MISC1` INT, `MISC2` INT, `MISC3` INT,"
   "`IMAGE` INT, `DIR` INT, `X` INT, `Y` INT, `UP` INT, `DOWN` INT, `DENSITY` INT HIDDEN, `BIZARRO` INT HIDDEN);");
  sqlite3_create_module(userdb,"INVENTORY",&vt_inventory,"CREATE TABLE `INVENTORY`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `IMAGE` INT, `VALUE` INT);");
  sqlite3_create_module(userdb,"PLAYFIELD",&vt_playfield,"CREATE TABLE `PLAYFIELD`(`X` INT, `Y` INT, `OBJ` INT, `BIZARRO_OBJ` INT);");
}

Modified heromesh.h from [0ccc1cadde] to [9db2b324b2].

104
105
106
107
108
109
110

111
112
113
114
115
116
117
void draw_picture(int x,int y,Uint16 img);
void draw_cell(int x,int y);

// Use only when screen is locked
void draw_text(int x,int y,const unsigned char*t,int bg,int fg);
int draw_text_line(int x,int y,unsigned char*t,int cur,Uint8**cp);
void draw_key(int x,int y,int k,int bg,int fg);


const char*screen_prompt(const char*txt);
int screen_message(const char*txt);

int scrollbar(int*cur,int page,int max,SDL_Event*ev,SDL_Rect*re);

void draw_popup(const unsigned char*txt);







>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
void draw_picture(int x,int y,Uint16 img);
void draw_cell(int x,int y);

// Use only when screen is locked
void draw_text(int x,int y,const unsigned char*t,int bg,int fg);
int draw_text_line(int x,int y,unsigned char*t,int cur,Uint8**cp);
void draw_key(int x,int y,int k,int bg,int fg);
void draw_selection_rectangle(void);

const char*screen_prompt(const char*txt);
int screen_message(const char*txt);

int scrollbar(int*cur,int page,int max,SDL_Event*ev,SDL_Rect*re);

void draw_popup(const unsigned char*txt);
288
289
290
291
292
293
294






295
296
297
298
299
300
301

void run_game(void);
void run_auto_test(void);
void locate_me(int x,int y);

// == edit ==







void run_editor(void);
void write_empty_level_set(FILE*);

// == picedit ==

void run_picture_editor(void);








>
>
>
>
>
>







289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

void run_game(void);
void run_auto_test(void);
void locate_me(int x,int y);

// == edit ==

typedef struct {
  Uint8 x0,y0,x1,y1;
} EditorRect;

extern EditorRect editrect;

void run_editor(void);
void write_empty_level_set(FILE*);

// == picedit ==

void run_picture_editor(void);

Modified picture.c from [b22c8897cb] to [923fd4390d].

107
108
109
110
111
112
113


















114
115
116
117
118
119
120
      c=classes[objects[o]->class];
      if(objects[o]->anim && (objects[o]->anim->status&ANISTAT_VISUAL)) i=objects[o]->anim->vimage; else i=objects[o]->image;
      if(i<c->nimages) draw_picture((x-1)*picture_size+left_margin,(y-1)*picture_size,c->images[i]&0x7FFF);
    }
    o=objects[o]->up;
  }
}



















void draw_text(int x,int y,const unsigned char*t,int bg,int fg) {
  // To be called only when screen is locked!
  int len=strlen(t);
  Uint8*pix=screen->pixels;
  Uint8*p;
  Uint16 pitch=screen->pitch;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
      c=classes[objects[o]->class];
      if(objects[o]->anim && (objects[o]->anim->status&ANISTAT_VISUAL)) i=objects[o]->anim->vimage; else i=objects[o]->image;
      if(i<c->nimages) draw_picture((x-1)*picture_size+left_margin,(y-1)*picture_size,c->images[i]&0x7FFF);
    }
    o=objects[o]->up;
  }
}

void draw_selection_rectangle(void) {
  Uint16 pitch=screen->pitch;
  Uint8*p=screen->pixels+left_margin+(editrect.x0-1)*picture_size+pitch*(editrect.y0-1)*picture_size;
  int xr=(editrect.x1+1-editrect.x0)*picture_size-1;
  int yr=(editrect.y1+1-editrect.y0)*picture_size-1;
  int i;
  if(p+xr+yr*pitch>=((Uint8*)screen->pixels)+screen->h*pitch+screen->w) return;
  memset(p,0xF7,xr);
  memset(p+yr*pitch,0xF7,xr);
  for(i=1;i<yr;i++) {
    p[i*pitch]=p[i*pitch+xr]=0xF7;
    p[i*pitch+1]=p[i*pitch+xr-1]=0xF0;
  }
  memset(p+pitch+1,0xF0,xr-2);
  memset(p+(yr-1)*pitch+1,0xF0,xr-2);
  p[0]=p[xr]=p[yr*pitch]=p[yr*pitch+xr]=0xF8;
}

void draw_text(int x,int y,const unsigned char*t,int bg,int fg) {
  // To be called only when screen is locked!
  int len=strlen(t);
  Uint8*pix=screen->pixels;
  Uint8*p;
  Uint16 pitch=screen->pitch;

Modified sql.doc from [3bb83c39fa] to [d7c3542661].

53
54
55
56
57
58
59




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

HEROMESH_UNESCAPE(text)
  Converts escaped representation of a game string into text format.





LEVEL()
  The one-based order number of the current level.

LEVEL_CACHEID()
  The user cache ID of the level file.

LEVEL_ID()







>
>
>
>







53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
HEROMESH_TYPE(value)
  The type of a game value, given as a 64-bit integer. The types are
  'class', 'number', 'string', 'object', and 'sound'.

HEROMESH_UNESCAPE(text)
  Converts escaped representation of a game string into text format.

INRECT(x,y)
  True if the coordinates are inside of the current selection rectangle.
  If no selection rectangle is set, then the result is null.

LEVEL()
  The one-based order number of the current level.

LEVEL_CACHEID()
  The user cache ID of the level file.

LEVEL_ID()
102
103
104
105
106
107
108












109
110
111
112
113
114
115
PIPE(command)
  Execute a operating system shell command and return the output of that
  command as a blob.

READ_LUMP_AT(offset,ptr)
  Used internally; there is no way to use this in user SQL codes.













RESOURCE(...)
  Given the list of resource names, read a value from the X resource
  manager. Returns null if there is no such resource value.

SIGN_EXTEND(number)
  Sign extends a 32-bit number to 64-bits.








>
>
>
>
>
>
>
>
>
>
>
>







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
PIPE(command)
  Execute a operating system shell command and return the output of that
  command as a blob.

READ_LUMP_AT(offset,ptr)
  Used internally; there is no way to use this in user SQL codes.

RECT_X0()
  X0 coordinate of selection rectangle.

RECT_X1()
  X1 coordinate of selection rectangle.

RECT_Y0()
  Y0 coordinate of selection rectangle.

RECT_Y1()
  Y1 coordinate of selection rectangle.

RESOURCE(...)
  Given the list of resource names, read a value from the X resource
  manager. Returns null if there is no such resource value.

SIGN_EXTEND(number)
  Sign extends a 32-bit number to 64-bits.

129
130
131
132
133
134
135
136

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

ZERO_EXTEND(number)
  Zero extends a 32-bit number to 64-bits. Same as NVALUE.


=== Tables ===

Asterisks denote virtual tables.


CREATE TABLE "CLASSES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT, "EDITORHELP"
TEXT, "HELP" TEXT, "INPUT" INT, "QUIZ" INT, "TRACEIN" INT, "TRACEOUT" INT,
"GROUP" TEXT, "PLAYER" INT); *
  A list of classes in the current puzzle set; mostly read-only. Only
  QUIZ, TRACEIN, and TRACEOUT are writable. If TRACEIN is true then it
  will trace messages received by this class (if tracing is enabled). If
  TRACEOUT is true then it will trace messages sent by this class (if
  tracing is enabled).

CREATE TABLE `INVENTORY`(`ID` INTEGER PRIMARY KEY, `CLASS` INT, `IMAGE`
INT, `VALUE` INT);" *
  This table contains the current inventory, and is read-only. It is not
  meaningful in the editor.

CREATE TABLE "MESSAGES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT, "TRACE"
INT); *
  The list of messages in the current puzzle set; mostly read-only. Only
  TRACE is writable; if true, this message will be traced.







|
>










|
|







145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

ZERO_EXTEND(number)
  Zero extends a 32-bit number to 64-bits. Same as NVALUE.


=== Tables ===

Asterisks denote virtual tables. (The only disk tables are USERCACHEDATA
and USERCACHEINDEX; all others are eiter temporary or virtual.)

CREATE TABLE "CLASSES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT, "EDITORHELP"
TEXT, "HELP" TEXT, "INPUT" INT, "QUIZ" INT, "TRACEIN" INT, "TRACEOUT" INT,
"GROUP" TEXT, "PLAYER" INT); *
  A list of classes in the current puzzle set; mostly read-only. Only
  QUIZ, TRACEIN, and TRACEOUT are writable. If TRACEIN is true then it
  will trace messages received by this class (if tracing is enabled). If
  TRACEOUT is true then it will trace messages sent by this class (if
  tracing is enabled).

CREATE TABLE "INVENTORY"("ID" INTEGER PRIMARY KEY, "CLASS" INT, "IMAGE"
INT, "VALUE" INT); *
  This table contains the current inventory, and is read-only. It is not
  meaningful in the editor.

CREATE TABLE "MESSAGES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT, "TRACE"
INT); *
  The list of messages in the current puzzle set; mostly read-only. Only
  TRACE is writable; if true, this message will be traced.
166
167
168
169
170
171
172



173
174
175
176
177
178
179
  a new object of the correct class. DENSITY is read-only, but you can
  use ORDER BY DENSITY to sort in the stacking order of the objects.
  UP and DOWN are also read-only.

CREATE TEMPORARY TABLE "PICTURES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT
COLLATE NOCASE, "OFFSET" INT, "DEPENDENT" INT);
  List of all pictures available in this puzzle set.




CREATE TABLE "USERCACHEDATA"("ID" INTEGER PRIMARY KEY, "FILE" INT,
"LEVEL" INT, "NAME" TEXT COLLATE NOCASE, "OFFSET" INT, "DATA" BLOB,
"USERSTATE" BLOB);
  Contains the user cache data for the .level and .solution files of each
  puzzle set, in order to speed up loading and saving. The DATA will be
  null unless the data has changed, in which case the new data is stored







>
>
>







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
  a new object of the correct class. DENSITY is read-only, but you can
  use ORDER BY DENSITY to sort in the stacking order of the objects.
  UP and DOWN are also read-only.

CREATE TEMPORARY TABLE "PICTURES"("ID" INTEGER PRIMARY KEY, "NAME" TEXT
COLLATE NOCASE, "OFFSET" INT, "DEPENDENT" INT);
  List of all pictures available in this puzzle set.

CREATE TABLE "PLAYFIELD"("X" INT, "Y" INT, "OBJ" INT, "BIZARRO_OBJ" INT); *
  All playfield cells, with their coordinates and bottom objects.

CREATE TABLE "USERCACHEDATA"("ID" INTEGER PRIMARY KEY, "FILE" INT,
"LEVEL" INT, "NAME" TEXT COLLATE NOCASE, "OFFSET" INT, "DATA" BLOB,
"USERSTATE" BLOB);
  Contains the user cache data for the .level and .solution files of each
  puzzle set, in order to speed up loading and saving. The DATA will be
  null unless the data has changed, in which case the new data is stored