Index: TODO ================================================================== --- TODO +++ TODO @@ -14,12 +14,10 @@ * 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 Index: bindings.doc ================================================================== --- bindings.doc +++ bindings.doc @@ -125,10 +125,13 @@ Save level. '^T' Edit the level title. +'^Z' + Cancel selection rectangle. + '^a' Add an object with the current MRU values to that location, if there is not already another object of the same class there. '^c' @@ -147,10 +150,16 @@ there is already another object of the same class at that location. '^w' Swap the normal world with the bizarro world. +'^<' + Set top corner of selection rectangle. + +'^>' + Set bottom corner of selection rectangle. + 'am' Add a MRU and select the added MRU. 'em' Edit the Misc/Dir of an existing object. Index: default.heromeshrc ================================================================== --- default.heromeshrc +++ default.heromeshrc @@ -1,6 +1,6 @@ -! Hero Mesh configuration settings +! hERo Mesh configuration settings ?.screenWidth: 800 ?.screenHeight: 600 ?.imageSize: 24 ?.traceAll: true ?.showInventory: 0 @@ -154,15 +154,20 @@ ?.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()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;xpfwidth || (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; @@ -1559,10 +1580,11 @@ case 'mr': // Select MRU absolute if(number>=0 && number64 || y>64) return 0; level_changed=1; annihilate(); Index: edit.doc ================================================================== --- edit.doc +++ edit.doc @@ -159,10 +159,23 @@ * 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. @@ -200,14 +213,19 @@ 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) - RIGHT Delete object - CTRL+RIGHT Copy object to MRU + 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 Index: function.c ================================================================== --- function.c +++ function.c @@ -241,10 +241,17 @@ } } 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)); } @@ -360,10 +367,14 @@ 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); @@ -1074,19 +1085,60 @@ .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); @@ -1100,10 +1152,14 @@ 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); @@ -1116,6 +1172,7 @@ 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);"); } Index: heromesh.h ================================================================== --- heromesh.h +++ heromesh.h @@ -106,10 +106,11 @@ // 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); @@ -290,12 +291,18 @@ 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); Index: picture.c ================================================================== --- picture.c +++ picture.c @@ -109,10 +109,28 @@ if(inimages) 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;ipixels; Index: sql.doc ================================================================== --- sql.doc +++ sql.doc @@ -55,10 +55,14 @@ '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. @@ -104,10 +108,22 @@ 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) @@ -131,11 +147,12 @@ Zero extends a 32-bit number to 64-bits. Same as NVALUE. === Tables === -Asterisks denote virtual 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 @@ -142,12 +159,12 @@ 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);" * +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); * @@ -168,10 +185,13 @@ 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