Index: TODO ================================================================== --- TODO +++ TODO @@ -2,11 +2,10 @@ * 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 * Popup inventory list (with optional possibility of choice) (?) * Playfield array * Editor * Mouse dragging @@ -50,10 +49,11 @@ * Testing * Bizarro world * Connection movement (it is partially tested, already) * Sweep, SweepEx, HitMe * Overriding order of execution (partially tested) + * Returning classes/objects from COLLIDEBY * Conversion from other games * DOS Hero Hearts * Berusky * PC Wanderer * Escape Index: class.doc ================================================================== --- class.doc +++ class.doc @@ -710,10 +710,11 @@ MOVING NEXTWARP PLAYERMOVING POSTINIT SUNK + XCREATE Input constants: 'BACK = 8 'TAB = 9 'CENTER = 12 @@ -1074,12 +1075,11 @@ NextR : any This variable is normally zero, but when there is a collision by the CollisionLayers, it can be programmed to destroy an object and create another one which represents the combination of the two. In this case, - the NextR variable of the destroyed object points to the new one. (Note: - This feature is not yet fully implemented.) + the NextR variable of the destroyed object points to the new one. Player : bool [c] [ro] If this object is the player. This is used implicitly as the From of some messages sent by the game engine, and has a few other purposes. (Normally, a level should have exactly one object of such a class, @@ -2397,11 +2397,16 @@ attempt is successful even if it isn't (there is no effect for creating objects; this bit is meaningful only for moves), and bit4 means that if necessary, it will try to destroy this object to make room. From is the object attempting to move here, and Arg1 and Arg2 are its location (if attempting to create an object, then From, Arg1, and Arg2 are all zero). - Arg3 is the class of the object being moved or created. + Arg3 is the class of the object being moved or created. If the return + value is a class, then it will destroy this one, and if moving also the + object moving, and attempt to create an object of the specified class + instead and set the NextR of both old objects to the new one. If the + return value is an object, it is treated as 1 but creation will return + the specified object instead of the one which is trying to be created. COLLIDING Called when deferred movement is being performed if the move cannot occur due to a collision with some other object. From is the object that it is colliding with, Arg1 and Arg2 are its location, and Arg3 @@ -2437,11 +2442,13 @@ CREATE Sent when the object is created by the Create instruction. This is done after the object is created, but before sending any other messages. The From is the object that created it. The return value will be used as the Arg3 of the SUNK and CREATED messages it might send if appropriate. From - is the object which executed the Create instruction to create it. + is the object which executed the Create instruction to create it, or zero + if it was sent after a XCREATE message due to objects being replaced + after a collision. CREATED Sent to all objects whose Arrivals care about objects in the location where an object has just been created. From is the newly created object, Arg1 and Arg2 are the X and Y coordinates where the object was created, @@ -2454,12 +2461,17 @@ DESTROY Received when the object is about to be destroyed. Arg3 is the reason: 0 for the Destroy or ,Destroy instruction, 1 due to this object moving into something sharp, 2 due to something sharp moving into this one, - 3 for a conflict with CollisionLayers, or 4 for crushing. The return - value is false to allow it to be destroyed or true to keep the object. + 3 for a conflict with CollisionLayers, 4 for crushing, 5 if it was + destroyed due to COLLIDEBY returning a class or object to be created + in its place to substitute for it, or 6 if it was trying to move into + another place but could not and COLLIDEBY told it to create a different + object in its place (in which case From is the object that it collided + with). The return value is false to allow it to be destroyed or true to + keep the object. DESTROYED Sent to all objects whose Departures care about objects in the location where an object has just been destroyed (if its VisualOnly flag isn't set). From is the object which has just been destroyed. Arg3 is the @@ -2576,10 +2588,23 @@ is created but is not the least dense object at that location. In this case, From is zero, and the return value is not used. For creation, Arg3 is the return value of the corresponding CREATE message; for movement, Arg3 is always zero. (This has nothing to do with sinking in water.) +XCREATE + This message is received by an object which has been just created due to + a COLLIDEBY message returning a class. From is the object at the target + location that a collision with layers occurred (which is now destroyed). + Arg1 is the class of the attempted creation or object trying to move + here, Arg2 is the image of the attempted creation or object trying to + move here, and Arg3 is the object trying to move here (or zero if it was + trying to create an object). If the return value is zero, then it will + continue with CREATE, CREATED, SUNK, and FLOATED messages like with a + usual creation, but if a number other than zero then they are skipped. + If the return value is an object, then the Create command will return + the specified object. + === Hit values === This section describes the bits of the return value of the HIT and HITBY messages; these values are also used as the Arg3 of those messages. Index: exec.c ================================================================== --- exec.c +++ exec.c @@ -69,10 +69,12 @@ static Value traced_obj; static Uint32 control_obj=VOIDLINK; static Connection conn[VSTACKSIZE]; static int nconn,pconn; static Uint8 conn_dir; +static Uint16 subst_class; +static Uint32 subst_obj; #define Throw(x) (my_error=(x),longjmp(my_env,1)) #define StackReq(x,y) do{ if(vstackptr<(x)) Throw("Stack underflow"); if(vstackptr-(x)+(y)>=VSTACKSIZE) Throw("Stack overflow"); }while(0) #define Push(x) (vstack[vstackptr++]=(x)) #define Pop() (vstack[--vstackptr]) @@ -676,11 +678,11 @@ n=objects[n]->up; } return c; } -static Uint8 collide_with(Uint8 b,Uint32 n,Uint8 x,Uint8 y,Uint16 c) { +static Uint16 collide_with(Uint8 b,Uint32 n,Uint8 x,Uint8 y,Uint16 c) { int i,j; Uint8 r=0; Uint32 e[8]={VOIDLINK,VOIDLINK,VOIDLINK,VOIDLINK,VOIDLINK,VOIDLINK,VOIDLINK,VOIDLINK}; Uint8 re[8]={0,0,0,0,0,0,0,0}; Value v; @@ -692,10 +694,19 @@ if(v.t) Throw("Type mismatch in COLLIDE"); r=v.u; } for(i=0;i<8;i++) if(e[i]!=VOIDLINK && !(r&0x02)) { v=send_message(n,e[i],MSG_COLLIDEBY,NVALUE(n==VOIDLINK?0:objects[n]->x),NVALUE(n==VOIDLINK?0:objects[n]->y),CVALUE(c)); + if(v.t==TY_CLASS) { + subst_class=v.u; + subst_obj=e[i]; + return 0x8001; + } else if(v.t>TY_MAXTYPE) { + subst_class=0; + subst_obj=v_object(v); + return 0x8001; + } if(v.t) Throw("Type mismatch in COLLIDEBY"); r|=re[i]=v.u; } if(!(r&0x01)) { // See if we can destroy some objects to make room @@ -821,10 +832,89 @@ objects[n]->prev=obj; if(o->prev==VOIDLINK) firstobj=obj; else objects[o->prev]->next=obj; notfound: objects[obj]->oflags|=OF_ORDERED; } + +static Uint32 x_create(Uint32 from,Uint16 c,Uint32 x,Uint32 y,Uint32 d,Value arg1,Value arg2,Value arg3) { + // This function is only called if x and y and d are already verified to be valid, so need not be checked again. + Uint32 m,n; + int i,xx,yy; + Object*o; + Object*p; + Value v; + if(c==control_class || !c) return VOIDLINK; + // Although this function can be called recursively, StackProtection() is unnecessary, + // because in the case that it is called recursively, it is guaranteed to call either + // collide_with or execute_program (indirectly), which then calls StackProtection(). + if((i=classes[c]->collisionLayers) && (xx=collisions_at(x,y)&i)) { + if((i=collide_with(xx,VOIDLINK,x,y,c))&0x01) { + if(i&0x8000) { + if(subst_class) { + m=subst_obj; + v=destroy(VOIDLINK,m,5); + if(v.t) return VOIDLINK; + n=x_create(m,subst_class,x,y,d,arg1,arg2,arg3); + if(n!=VOIDLINK) objects[from]->replacement=objects[m]->replacement=OVALUE(n); + return n; + } else { + return subst_obj; + } + } + return VOIDLINK; + } + } + n=objalloc(c); + if(n==VOIDLINK) Throw("Error creating object"); + o=objects[n]; + o->x=x; + o->y=y; + o->image=0; + o->dir=d; + o->oflags&=~OF_BIZARRO; + objects[from]->replacement=OVALUE(n); + pflink(n); + v=send_message(from,n,MSG_XCREATE,arg1,arg2,arg3); + if(v.t>TY_MAXTYPE) { + if(classes[objects[n]->class]->order && !(o->oflags&OF_DESTROYED)) set_order(n); + return v_object(v); + } else if(v.t) { + Throw("Type mismatch"); + } + if(o->oflags&OF_DESTROYED) return VOIDLINK; + if(v.u) goto skip; + v=send_message(VOIDLINK,n,MSG_CREATE,NVALUE(0),NVALUE(0),NVALUE(0)); + if(o->oflags&OF_DESTROYED) return VOIDLINK; + if(o->oflags&OF_BIZARRO) { + skip: + if(classes[objects[n]->class]->order) set_order(n); + return n; + } + for(y=0;y<5;y++) for(x=0;x<5;x++) { + xx=o->x+x-2; yy=o->y+y-2; + if(xx<1 || xx>pfwidth || yy<1 || yy>pfheight) continue; + i=x+5*(4-y); + m=playfield[xx+yy*64-65]; + while(m!=VOIDLINK) { + p=objects[m]; + if(p->arrivals&(1<x),NVALUE(o->y),v); + m=p->up; + } + } + if(o->oflags&OF_DESTROYED) return VOIDLINK; + if(classes[objects[n]->class]->order) set_order(n); + m=objects[n]->up; + if(m!=VOIDLINK) { + v=send_message(VOIDLINK,n,MSG_SUNK,NVALUE(0),NVALUE(0),v); + while(m!=VOIDLINK) { + send_message(n,m,MSG_FLOATED,NVALUE(0),NVALUE(0),v); + m=objects[m]->up; + } + } + if(o->oflags&OF_DESTROYED) return VOIDLINK; + return n; +} static Uint32 create(Uint32 from,Uint16 c,Uint32 x,Uint32 y,Uint32 im,Uint32 d) { Uint32 m,n; int i,xx,yy; Object*o; @@ -831,11 +921,25 @@ Object*p; Value v; if(d>7) d=0; if(x<1 || y<1 || x>pfwidth || y>pfheight || c==control_class) return VOIDLINK; if(!(classes[c]->oflags&OF_BIZARRO) && (i=classes[c]->collisionLayers) && (xx=collisions_at(x,y)&i)) { - if(collide_with(xx,VOIDLINK,x,y,c)&0x01) return VOIDLINK; + if((i=collide_with(xx,VOIDLINK,x,y,c))&0x01) { + if(i&0x8000) { + if(subst_class) { + m=subst_obj; + v=destroy(VOIDLINK,m,5); + if(v.t) return VOIDLINK; + n=x_create(m,subst_class,x,y,d,CVALUE(c),NVALUE(im),NVALUE(0)); + if(n!=VOIDLINK) objects[m]->replacement=OVALUE(n); + return n; + } else { + return subst_obj; + } + } + return VOIDLINK; + } } n=objalloc(c); if(n==VOIDLINK) Throw("Error creating object"); o=objects[n]; o->x=x; @@ -909,11 +1013,11 @@ } return v; } static int move_to(Uint32 from,Uint32 n,Uint32 x,Uint32 y) { - Uint32 m; + Uint32 k,m; int i,xx,yy; Object*o; Object*p; Value v; if(n==VOIDLINK || (objects[n]->oflags&OF_DESTROYED) || n==control_obj) return 0; @@ -927,11 +1031,26 @@ if(v_bool(send_message(n,m,MSG_PLAYERMOVING,NVALUE(x),NVALUE(y),OVALUE(from)))) return 0; m=objects[m]->prev; } } if(!(o->oflags&OF_BIZARRO) && (i=classes[o->class]->collisionLayers) && (xx=collisions_at(x,y)&i)) { - if((i=collide_with(xx,n,x,y,o->class))&0x01) return i&0x04?1:0; + if((i=collide_with(xx,n,x,y,o->class))&0x01) { + if(i&0x8000) { + m=subst_obj; + v=destroy(n,m,5); + if(v.t) return 0; + k=x_create(m,subst_class,x,y,o->dir,CVALUE(o->class),NVALUE(o->image),OVALUE(n)); + if(k!=VOIDLINK) { + objects[m]->replacement=objects[n]->replacement=OVALUE(k); + destroy(m,n,6); + o->distance+=abs(x-o->x)+abs(y-o->y); + return 1; + } + return 0; + } + return i&0x04?1:0; + } } pfunlink(n); if(!(o->oflags&((classes[o->class]->cflags&CF_COMPATIBLE?OF_VISUALONLY:0)|OF_STEALTHY|OF_BIZARRO))) { for(i=25;i>=0;i--) { xx=o->x+Xbit(i); yy=o->y+Ybit(i); Index: heromesh.h ================================================================== --- heromesh.h +++ heromesh.h @@ -44,11 +44,11 @@ #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 28 +#define N_MESSAGES 29 #define N_STANDARD_SOUNDS 49 extern const char*const standard_message_names[]; extern const char*const standard_sound_names[]; extern const char*const heromesh_key_names[256]; Index: instruc.h ================================================================== --- instruc.h +++ instruc.h @@ -779,10 +779,11 @@ {"WAHOO",8389399}, {"WHACK",8389422}, {"Walkable",8487139}, {"Weight",9142355}, {"WinLevel",8487140}, +{"XCREATE",8389148}, {"XDir",8487141}, {"XStep",8487142}, {"XYDir",8421607}, {"Xloc",8486983}, {"YDir",8487144}, @@ -889,7 +890,7 @@ {"tuck",8421380}, {"uniq",8421618}, {"until",8683534}, {"while",8683535}, }; -#define N_OP_NAMES 372 +#define N_OP_NAMES 373 #endif Index: names.h ================================================================== --- names.h +++ names.h @@ -25,10 +25,11 @@ #define MSG_COLLIDING 23 #define MSG_BLOCKED 24 #define MSG_CONNECT 25 #define MSG_NEXTWARP 26 #define MSG_CLICK 27 +#define MSG_XCREATE 28 #ifdef HEROMESH_MAIN const char*const standard_message_names[]={ "INIT", "CREATE", "DESTROY", @@ -55,10 +56,11 @@ "COLLIDING", "BLOCKED", "CONNECT", "NEXTWARP", "CLICK", + "XCREATE", }; const char*const standard_sound_names[]={ "SPLASH", "POUR", "DOOR", Index: names.js ================================================================== --- names.js +++ names.js @@ -30,10 +30,11 @@ 23 = COLLIDING 24 = BLOCKED 25 = CONNECT 26 = NEXTWARP 27 = CLICK + 28 = XCREATE `.split("\n").map(x=>/^ *([0-9]+) = ([^ ]*) *$/.exec(x)).filter(x=>x); const standard_sound_names=[]; ` SPLASH POUR