Index: bindings.doc ================================================================== --- bindings.doc +++ bindings.doc @@ -96,10 +96,13 @@ will receive the move list (in the same format as above) on stdin. === Editor commands === +'^S' + Save level. + '^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' Index: default.heromeshrc ================================================================== --- default.heromeshrc +++ default.heromeshrc @@ -136,15 +136,16 @@ ?.editKey.8: select 'mr',7; ?.editKey.9: select 'mr',8; ?.editKey.up: select 'mR',-1; ?.editKey.down: select 'mR',+1; ?.editKey.C: select 'lc',:level_code where :level_code=cast(:level_code as int); -?.editKey.shift.F: with n(n) as (select 65 union all select n+1 h from n where h<=64*pfheight()+65) select '^a',n from n; +?.editKey.F: with n(n) as (select 65 union all select n+1 h from n where h<=64*pfheight()+65) select '^a',n from n; +?.editKey.R: select 're',substr(:resize,1,instr(:resize,'x')-1),substr(:resize,instr(:resize,'x')+1) where length(:resize); ?.editKey.ctrl.X: select 're',pfwidth(),pfheight(); ?.editKey.ctrl.P: ^P ?.editKey.ctrl.Q: ^Q -?.editKey.ctrl.R: select 're',substr(:resize,1,instr(:resize,'x')-1),substr(:resize,instr(:resize,'x')+1) where length(:resize); +?.editKey.ctrl.S: ^S ?.editKey.space: ^c ?.editKey.return: ^e ?.editClick.left: ^a ?.editClick.alt.left: ^u ?.editClick.right: delete from objects where x=$X and y=$Y and up is null; Index: edit.c ================================================================== --- edit.c +++ edit.c @@ -32,11 +32,11 @@ Uint8 cu[0x4000/8]; Uint8 mu[0x4000/8]; Object*o; long size=0; unsigned char*data=read_lump(FIL_LEVEL,LUMP_CLASS_DEF,&size); - sqlite3_str*s=sqlite3_str_new(0); + sqlite3_str*s; memset(cu,0,0x4000/8); memset(mu,0,0x4000/8); if(data && size) { for(i=0;i>8); sqlite3_str_appendall(s,classes[i]->name); sqlite3_str_appendchar(s,1,0); @@ -85,11 +86,155 @@ // End of data size=sqlite3_str_length(s); data=sqlite3_str_finish(s); if(!size || !data) fatal("Error in string builder\n"); write_lump(FIL_LEVEL,LUMP_CLASS_DEF,size,data); - free(data); + sqlite3_free(data); +} + +static inline void version_change(void) { + long sz=0; + unsigned char*buf=read_lump(FIL_SOLUTION,level_id,&sz); + if(!buf) return; + if(sz>2 && (buf[0]|(buf[1]<<8))==level_version) ++level_version; + free(buf); +} + +static void save_obj(sqlite3_str*s,const Object*o,const Object**m,Uint8 x,Uint8 y) { + static Uint8 r=0; + static Uint8 rx,ry; + const Object*p=0; + Uint8 b; + Uint16 c; + if(!o) goto nrle; + b=o->dir&7; + if(o->x==x+1) b|=0x40; else if(o->x!=x) b|=0x20; + if(o->y!=y) b|=0x10; + p=m[b&0x70?0:1]; + if((b&0x70)!=0x20 || !r || !p) goto nrle; + if(o->class!=p->class || o->image!=p->image || !ValueEq(o->misc1,p->misc1) || !ValueEq(o->misc2,p->misc2) || !ValueEq(o->misc3,p->misc3)) goto nrle; + if(0x0F&~r) { + r++; + } else { + sqlite3_str_appendchar(s,1,r); + if(r&0x20) sqlite3_str_appendchar(s,1,rx); + if(r&0x10) sqlite3_str_appendchar(s,1,ry); + r=0x20; + } + return; + nrle: + if(r) { + sqlite3_str_appendchar(s,1,r); + if(r&0x20) sqlite3_str_appendchar(s,1,rx); + if(r&0x10) sqlite3_str_appendchar(s,1,ry); + } + r=0; + if(!o) return; + if(o->misc1.t|o->misc1.u|o->misc2.t|o->misc2.u|o->misc3.t|o->misc3.u) b|=0x08; + if(p && o->class==p->class && o->image==p->image && ValueEq(o->misc1,p->misc1) && ValueEq(o->misc2,p->misc2) && ValueEq(o->misc3,p->misc3)) { + // Use RLE + r=0x80|b&0xF0; + rx=o->x; + ry=o->y; + return; + } + m[b&0x70?0:1]=o; + sqlite3_str_appendchar(s,1,b); + if(b&0x20) sqlite3_str_appendchar(s,1,o->x); + if(b&0x10) sqlite3_str_appendchar(s,1,o->y); + c=o->class; + for(x=0;x<=o->image && xnimages;x++) if(classes[c]->images[x]&0x8000) break; + if(x==o->image) c|=0x8000; + sqlite3_str_appendchar(s,1,c&0xFF); + sqlite3_str_appendchar(s,1,c>>8); + if(c<0x8000) sqlite3_str_appendchar(s,1,o->image); + if(b&0x08) { + b=o->misc1.t|(o->misc2.t<<2)|(o->misc3.t<<4); + if(o->misc1.t|o->misc1.u) { + if(o->misc3.t|o->misc3.u) b|=0xC0; + else if(o->misc2.t|o->misc2.u) b|=0x80; + else b|=0x40; + } + sqlite3_str_appendchar(s,1,b); + if(b&0xC0) { + sqlite3_str_appendchar(s,1,o->misc1.u&0xFF); + sqlite3_str_appendchar(s,1,o->misc1.u>>8); + } + if((b&0xC0)!=0x40) { + sqlite3_str_appendchar(s,1,o->misc2.u&0xFF); + sqlite3_str_appendchar(s,1,o->misc2.u>>8); + } + if(!((b&0xC0)%0xC0)) { + sqlite3_str_appendchar(s,1,o->misc3.u&0xFF); + sqlite3_str_appendchar(s,1,o->misc3.u>>8); + } + } +} + +static void save_level(void) { + /* + Format of objects: + * bit flags (or 0xFF for end): + bit7 = MRU (omit everything but position) + bit6 = Next position + bit5 = New X position + bit4 = New Y position + bit3 = Has MiscVars (RLE in case of MRU) + bit2-bit0 = LastDir (RLE in case of MRU) + * new X if applicable + * new Y if applicable + * class (one-based; add 0x8000 for default image) (two bytes) + * image (one byte) + * data types (if has MiscVars): + bit7-bit6 = How many (0=has Misc2 and Misc3, not Misc1) + bit5-bit4 = Misc3 type + bit3-bit2 = Misc2 type + bit1-bit0 = Misc1 type + * misc data (variable size) + Store/use MRU slot 0 if any bits of 0x70 set in flag byte; slot 1 otherwise + */ + Uint8 x=0; + Uint8 y=1; + const Object*m[2]={0,0}; + sqlite3_str*str=sqlite3_str_new(0); + Uint32 n; + long sz; + char*data; + int i; + // Header + if(level_changed) version_change(); + level_changed=0; + sqlite3_str_appendchar(str,1,level_version&255); + sqlite3_str_appendchar(str,1,level_version>>8); + sqlite3_str_appendchar(str,1,level_code&255); + sqlite3_str_appendchar(str,1,level_code>>8); + sqlite3_str_appendchar(str,1,pfwidth-1); + sqlite3_str_appendchar(str,1,pfheight-1); + if(level_title) sqlite3_str_appendall(str,level_title); + sqlite3_str_appendchar(str,1,0); + // Objects + for(i=0;i<64*64;i++) { + n=playfield[i]; + while(n!=VOIDLINK) { + save_obj(str,objects[n],m,x,y); + x=objects[n]->x; + y=objects[n]->y; + n=objects[n]->up; + } + } + save_obj(str,0,m,x,y); + sqlite3_str_appendchar(str,1,0xFF); + // Level strings + for(i=0;i=left_margin?(x-left_margin)/picture_size+1:0; y=y/picture_size+1; if(x>0 && y>0 && x<=pfwidth && y<=pfheight) snprintf(buf,8,"(%2d,%2d)",x,y); else strcpy(buf," "); draw_text(0,40,buf,0xF0,0xF3); @@ -561,10 +706,13 @@ return 0; case '^P': // Play return -2; case '^Q': // Quit return -1; + case '^S': // Save level + save_level(); + return 1; case 'go': // Select level load_level(number); return 1; case 'lc': // Set level code level_code=number; Index: heromesh.h ================================================================== --- heromesh.h +++ heromesh.h @@ -31,10 +31,11 @@ #define CVALUE(x) UVALUE(x,TY_CLASS) #define MVALUE(x) UVALUE(x,TY_MESSAGE) #define ZVALUE(x) UVALUE(x,TY_STRING) #define OVALUE(x) ((x)==VOIDLINK?NVALUE(0):UVALUE(x,objects[x]->generation)) #define ValueTo64(v) (((sqlite3_int64)((v).u))|(((sqlite3_int64)((v).t))<<32)) +#define ValueEq(x,y) ((x).t==(y).t && (x).u==(y).u) #define N_MESSAGES 24 extern const char*const standard_message_names[]; extern const char*const standard_sound_names[]; extern const char*const heromesh_key_names[256]; Index: main.c ================================================================== --- main.c +++ main.c @@ -131,11 +131,11 @@ return id; } unsigned char*read_lump_or_userstate(int sol,int lvl,long*sz,char us) { // Returns a pointer to the data; must be freed using free(). - // If there is no data, returns null and sets *sz and *us to zero. + // If there is no data, returns null and sets *sz to zero. // Third argument is a pointer to a variable to store the data size (must be not null). // Fourth argument is 1 for user state or 0 for lump data. unsigned char*buf=0; sqlite3_reset(readusercachest); sqlite3_bind_int64(readusercachest,1,sol?solutionuc:leveluc);