Index: TODO ================================================================== --- TODO +++ TODO @@ -3,11 +3,10 @@ * 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 Index: class.c ================================================================== --- class.c +++ class.c @@ -51,10 +51,11 @@ 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; @@ -2677,10 +2678,15 @@ 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"); Index: class.doc ================================================================== --- class.doc +++ class.doc @@ -287,10 +287,15 @@ (CollisionLayers ) Define user flags as CollisionLayers bits; the first defined flag is bit0. Up to 8 flags can be defined in this way. +(InputXY ) + 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 ) Define the level table. See the section about level table definition for details. Can only occur once. (Misc4 ) @@ -1612,11 +1617,12 @@ ,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, 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 ) @@ -2260,10 +2266,19 @@ 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 Index: default.heromeshrc ================================================================== --- default.heromeshrc +++ default.heromeshrc @@ -103,10 +103,11 @@ ?.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 Index: exec.c ================================================================== --- exec.c +++ exec.c @@ -3599,17 +3599,19 @@ 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) { @@ -3620,12 +3622,10 @@ changed=0; key_ignored=0; all_flushed=0; lastimage_processing=0; vstackptr=0; - current_key=key; - tc=0; for(n=0;ndistance=0; o->oflags&=~(OF_KEYCLEARED|OF_DONE|OF_MOVING); if(o->anim) { o->anim->count=0; @@ -3633,27 +3633,52 @@ } } // Input phase m=VOIDLINK; v=NVALUE(0); - if(!quiz_obj.t) { - n=lastobj; + 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) { - 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; + 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 { - 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)); + 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 Index: fileformat.doc ================================================================== --- fileformat.doc +++ fileformat.doc @@ -151,10 +151,15 @@ 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. Index: function.c ================================================================== --- function.c +++ function.c @@ -172,10 +172,14 @@ 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; @@ -1791,10 +1795,11 @@ 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); Index: game.c ================================================================== --- game.c +++ game.c @@ -45,10 +45,15 @@ // 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); } } @@ -65,10 +70,13 @@ // 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); } @@ -206,11 +214,20 @@ 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=0 && 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); @@ -1214,11 +1231,11 @@ 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 + case '\' ': play: // Play a move if(replay_time) { replay_time=0; return -3; } if(solution_replay) { @@ -1350,10 +1367,17 @@ 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; } } @@ -1424,11 +1448,11 @@ } timerflag=1; return n; } -static inline void input_move(Uint8 k) { +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; Index: heromesh.h ================================================================== --- heromesh.h +++ heromesh.h @@ -184,10 +184,11 @@ 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]; @@ -243,10 +244,11 @@ #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 { Index: sql.doc ================================================================== --- sql.doc +++ sql.doc @@ -67,10 +67,14 @@ 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