Free Hero Mesh

Check-in [702847e470]
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:More corrections, and more work on implementation, of custom level table definitions.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 702847e4701bfe25ba300ad2cb9b33841d7a4e42
User & Date: user on 2021-12-27 20:33:50
Other Links: manifest | tags
Context
2021-12-29
00:04
Add a entry to the Frequently Asked Questions section about use of Free Hero Mesh with gdb. check-in: b3b7b53f2b user: user tags: trunk
2021-12-27
20:33
More corrections, and more work on implementation, of custom level table definitions. check-in: 702847e470 user: user tags: trunk
2021-12-25
06:33
Some corrections of custom level table, including initializing the schema properly; start a few more parts of its implementation too. check-in: 66cf1d39ad user: user tags: trunk
Changes

Modified class.c from [64b3d009e0] to [7cb8ff67cc].

2288
2289
2290
2291
2292
2293
2294

2295
2296
2297
2298
2299
2300
2301
      case OP_LABEL:
        i=look_hash(hash,LOCAL_HASH_SIZE,0x100,0x13F,ll_ndata+0x100,"data columns")?:(ll_ndata++)+0x100;
        i-=0x100;
        if(datac[i].name) ParseError("Duplicate definition\n");
        datac[i].name=strdup(tokenstr);
        if(!datac[i].name) fatal("Allocation failed\n");
        datac[i].ptr=ptr;

        ptr=level_table_code(ptr,hash);
        break;
      case OP_STRING:
        if(last) ParseError("Extra columns after fill column\n");
        for(i=0;tokenstr[i];i++) if(!(tokenstr[i]&~31)) ParseError("Improper column heading\n");
        strcpy(buf,tokenstr);
        // Data







>







2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
      case OP_LABEL:
        i=look_hash(hash,LOCAL_HASH_SIZE,0x100,0x13F,ll_ndata+0x100,"data columns")?:(ll_ndata++)+0x100;
        i-=0x100;
        if(datac[i].name) ParseError("Duplicate definition\n");
        datac[i].name=strdup(tokenstr);
        if(!datac[i].name) fatal("Allocation failed\n");
        datac[i].ptr=ptr;
        if(Tokenf(TF_COMMA)) datac[i].sgn=1;
        ptr=level_table_code(ptr,hash);
        break;
      case OP_STRING:
        if(last) ParseError("Extra columns after fill column\n");
        for(i=0;tokenstr[i];i++) if(!(tokenstr[i]&~31)) ParseError("Improper column heading\n");
        strcpy(buf,tokenstr);
        // Data
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394

2395
2396
2397
2398
2399
2400
2401
        i=look_hash(hash,LOCAL_HASH_SIZE,0x200,0x23F,ll_naggregate+0x200,"aggregates")?:(ll_naggregate++)+0x200;
        i-=0x200;
        if(aggrc[i].ag) ParseError("Duplicate definition\n");
        nxttok();
        if(!Tokenf(TF_NAME)) ParseError("Improper aggregate\n");
        switch(tokenv) {
          case OP_ADD: aggrc[i].ag=1; break;
          case OP_MIN: aggrc[i].ag=2; break;
          case OP_MAX: aggrc[i].ag=3; break;
          case OP_MIN_C: aggrc[i].ag=4; break;
          case OP_MAX_C: aggrc[i].ag=5; break;
          default: ParseError("Improper aggregate\n");
        }
        aggrc[i].ptr=ptr;
        ptr=level_table_code(ptr,hash);
        break;
      default: ParseError("Unexpected token in (LevelTable) block\n");
    }
  }
  ll_head=sqlite3_str_finish(str);
  if(!ll_head) fatal("Allocation failed\n");
  for(i=0;i<ll_ndata;i++) if(!datac[i].name) ParseError("Undefined data column\n");
  for(i=0;i<ll_naggregate;i++) if(!aggrc[i].ag) ParseError("Undefined aggregate\n");
  ll_data=realloc(datac,(ll_ndata+ll_naggregate)*sizeof(DataColumn));
  if(!ll_data) fatal("Allocation failed\n");
  for(i=0;i<ll_naggregate;i++) ll_data[i+ll_ndata]=aggrc[i];
  free(aggrc);

  // The next line will result in an invalid pointer if ptr is zero,
  // but this is harmless, since ll_code is never accessed if ptr is zero.
  ll_code=realloc(ll_code,ptr*sizeof(Uint16))?:ll_code;
  for(i=0;i<LOCAL_HASH_SIZE;i++) free(hash[i].txt);
  free(hash);
}








<
<
|
|
















>







2369
2370
2371
2372
2373
2374
2375


2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
        i=look_hash(hash,LOCAL_HASH_SIZE,0x200,0x23F,ll_naggregate+0x200,"aggregates")?:(ll_naggregate++)+0x200;
        i-=0x200;
        if(aggrc[i].ag) ParseError("Duplicate definition\n");
        nxttok();
        if(!Tokenf(TF_NAME)) ParseError("Improper aggregate\n");
        switch(tokenv) {
          case OP_ADD: aggrc[i].ag=1; break;


          case OP_MIN_C: aggrc[i].sgn=1; case OP_MIN: aggrc[i].ag=2; break;
          case OP_MAX_C: aggrc[i].sgn=1; case OP_MAX: aggrc[i].ag=3; break;
          default: ParseError("Improper aggregate\n");
        }
        aggrc[i].ptr=ptr;
        ptr=level_table_code(ptr,hash);
        break;
      default: ParseError("Unexpected token in (LevelTable) block\n");
    }
  }
  ll_head=sqlite3_str_finish(str);
  if(!ll_head) fatal("Allocation failed\n");
  for(i=0;i<ll_ndata;i++) if(!datac[i].name) ParseError("Undefined data column\n");
  for(i=0;i<ll_naggregate;i++) if(!aggrc[i].ag) ParseError("Undefined aggregate\n");
  ll_data=realloc(datac,(ll_ndata+ll_naggregate)*sizeof(DataColumn));
  if(!ll_data) fatal("Allocation failed\n");
  for(i=0;i<ll_naggregate;i++) ll_data[i+ll_ndata]=aggrc[i];
  free(aggrc);
  ll_disp=realloc(dispc,(ll_ndisp)*sizeof(DisplayColumn))?:dispc;
  // The next line will result in an invalid pointer if ptr is zero,
  // but this is harmless, since ll_code is never accessed if ptr is zero.
  ll_code=realloc(ll_code,ptr*sizeof(Uint16))?:ll_code;
  for(i=0;i<LOCAL_HASH_SIZE;i++) free(hash[i].txt);
  free(hash);
}

Modified class.doc from [9f8228ce84] to [e3fcdbff9c].

2583
2584
2585
2586
2587
2588
2589
2590







2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608

2609


2610

2611

2612



2613








2614
2615
=== Level table definition ===

The following blocks are allowed in a level table definition:

(<label> <code...>)
  Define a data column. The SQL name of the data column will be that of
  the label name, preceded by a underscore, and some characters will be
  stripped out.








(<string> <label> <width> <format> <color>)
  Defines a display column. The <label> is the name of the data column.
  The <width> is a number 1 to 255 or it can be * for fill width; only
  the last column is allowed to be fill width (although it is also OK if
  no colums are fill width). The <format> is a string of exactly one or
  two characters; see the below list of possible formats. The <color> is
  the text colour, and is optional; it can be a number from 1 to 255, or
  it can be a sequence of parenthesized pairs of a number (-127 to +127)
  and colour, to mean use those colour if the value of this column is
  in range. It uses the colour for all values up to and including the
  specified number, so the numbers should be listed in ascending order,
  in order to work. The <string> is the heading; it is not allowed to
  include control characters, nor can it include graphic characters with
  code number less than 32.

(<local> <type> <code...>)
  Defines a aggregate calculation. The <local> is the name of the aggregate

  to be referred by other blocks. The <type> is the type of aggregate,


  which can be one of: + for the sum, min for the least value (unsigned),

  max for the greatest value (unsigned), ,min for the least value (signed),

  and ,max for the greatest value (signed).












(TODO: Mention valid instructions.)








|
>
>
>
>
>
>
>


















>
|
>
>
|
>
|
>
|
>
>
>

>
>
>
>
>
>
>
>


2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
=== Level table definition ===

The following blocks are allowed in a level table definition:

(<label> <code...>)
  Define a data column. The SQL name of the data column will be that of
  the label name, preceded by a underscore, and some characters will be
  stripped out; note that SQL is case-insensitive. (Other implementations
  or ports of Free Hero Mesh might not implement it as a SQL column, or
  at all. For best portability, names should contain only ASCII letters,
  digits, and underscores, and should not start with a digit, and you
  should not define two or more data columns whose names differ only by
  their case. Aggregates are not subject to this restriction, though.)
  The label name must have a colon at front; if a comma is also added
  then numeric values of this column are treated as signed.

(<string> <label> <width> <format> <color>)
  Defines a display column. The <label> is the name of the data column.
  The <width> is a number 1 to 255 or it can be * for fill width; only
  the last column is allowed to be fill width (although it is also OK if
  no colums are fill width). The <format> is a string of exactly one or
  two characters; see the below list of possible formats. The <color> is
  the text colour, and is optional; it can be a number from 1 to 255, or
  it can be a sequence of parenthesized pairs of a number (-127 to +127)
  and colour, to mean use those colour if the value of this column is
  in range. It uses the colour for all values up to and including the
  specified number, so the numbers should be listed in ascending order,
  in order to work. The <string> is the heading; it is not allowed to
  include control characters, nor can it include graphic characters with
  code number less than 32.

(<local> <type> <code...>)
  Defines a aggregate calculation. The <local> is the name of the aggregate
  to be referred by other blocks (which must start with a percentage sign,
  like a local variable does). The <type> is the type of aggregate, which
  can be + (sum of all values), min (least value), or max (greatest value);
  any one with a comma at first means it is a signed aggregate.

The valid formats are:

* "L" or "R" = Left or right aligned text.

* "L*" or "R*" replacing * with any character = Left or right aligned
displaying the specified character repeated as many times as the column
value is.

* "d", "d-", "d+", "x", "x0", "X", "X0", "o", "o0", "u" = Display a number
(right aligned) as signed decimal (d), hexadecimal (x/X), octal (o),
unsigned decimal (u). The ones with "0" will pad by 0 instead of spaces,
and the "d-" and "d+" will display a left aligned sign.

If a numeric format is specified but it is a text value, then it is treated
as "L" instead, unless format is "R*" in which case it is treated as "R".

(TODO: Mention valid instructions.)

Modified game.c from [d6b8ec0435] to [b19cf21fe8].

696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716



























































717
718
719
720
721
722
723
  if(i&~0xFFFF) return;
  if(replay_size<i) replay_list=realloc(replay_list,replay_size=i);
  if(!replay_list) fatal("Allocation failed");
  replay_count=i;
  if(i) memcpy(replay_list,sqlite3_column_blob(st,1),i);
}

static void copy_text_to_plain(unsigned char*out,int maxlen,const unsigned char*in) {
  int at=0;
  if(!in) {
    *out=0;
    return;
  }
  while(*in && at<maxlen) switch(*in) {
    case 10: if(at && out[at-1]!=32) out[at++]=32; in++; break;
    case 14: case 30: in=strchrnul(in,'\\'); if(*in) in++; break;
    case 31: in++; if(*in) out[at++]=*in++; break;
    case 32 ... 255: out[at++]=*in++; break;
    default: in++;
  }
  out[at]=0;



























































}

static int list_levels(void) {
  static Sint8 mo=-1;
  static Uint8 columns=0;
  static int scroll=0;
  sqlite3_stmt*st;







|



|









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







696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
  if(i&~0xFFFF) return;
  if(replay_size<i) replay_list=realloc(replay_list,replay_size=i);
  if(!replay_list) fatal("Allocation failed");
  replay_count=i;
  if(i) memcpy(replay_list,sqlite3_column_blob(st,1),i);
}

static int copy_text_to_plain(unsigned char*out,int maxlen,const unsigned char*in) {
  int at=0;
  if(!in) {
    *out=0;
    return 0;
  }
  while(*in && at<maxlen) switch(*in) {
    case 10: if(at && out[at-1]!=32) out[at++]=32; in++; break;
    case 14: case 30: in=strchrnul(in,'\\'); if(*in) in++; break;
    case 31: in++; if(*in) out[at++]=*in++; break;
    case 32 ... 255: out[at++]=*in++; break;
    default: in++;
  }
  out[at]=0;
  return at;
}

static inline void levels_column(int x,int y,int n,int bg,sqlite3_stmt*st,char*buf) {
  const DisplayColumn*dc=ll_disp+n;
  Uint8 co=dc->color;
  int w=dc->width;
  int nc=dc->data+(dc->flag&4?0:8);
  int t=sqlite3_column_type(st,nc);
  int a=0;
  int i;
  const char*p;
  sqlite3_int64 v;
  if(t==SQLITE_NULL) return;
  if(dc->flag&1) w=255;
  if(t==SQLITE_BLOB || t==SQLITE_TEXT) {
    blob:
    i=copy_text_to_plain(buf,w,sqlite3_column_text(st,nc));
    if(dc->form[0]=='R') a=w-i;
    if(dc->flag&2) co=0xFF;
  } else {
    // This implementation does not check that the format is necessarily valid.
    // You should not rely on the use of any undocumented format.
    v=sqlite3_column_int64(st,nc);
    if(dc->flag&2) {
      //TODO: colours
    }
    switch(dc->form[0]) {
      case 'L': case 'R':
        if(dc->form[1]) {
          if(v<0 || v>w) v=w;
          memset(buf,dc->form[1],v);
          buf[v]=0;
          if(dc->form[0]=='R') a=w-v;
        } else {
          goto blob;
        }
        break;
      case 'd':
        snprintf(buf,w+1,"%*lld",w,(long long)(dc->form[1] && v<0?-v:v));
        if(dc->form[1]=='-') *buf=v<0?'-':' ';
        if(dc->form[1]=='+') *buf=v<0?'-':'+';
        break;
      case 'o':
        snprintf(buf,w+1,dc->form[1]?"%0*llo":"%*llo",w,(unsigned long long)v);
        break;
      case 'u':
        snprintf(buf,w+1,dc->form[1]?"%0*llu":"%*llu",w,(unsigned long long)v);
        break;
      case 'x':
        snprintf(buf,w+1,dc->form[1]?"%0*llx":"%*llx",w,(unsigned long long)v);
        break;
      case 'X':
        snprintf(buf,w+1,dc->form[1]?"%0*llX":"%*llX",w,(unsigned long long)v);
        break;
      default: *buf=0;
    }
  }
  draw_text(a*8+x,y,buf,bg,co);
}

static int list_levels(void) {
  static Sint8 mo=-1;
  static Uint8 columns=0;
  static int scroll=0;
  sqlite3_stmt*st;
784
785
786
787
788
789
790








791
792
793
794
795
796

797
798
799
800
801
802
803
        draw_text(2*8,y,"\x10",b=0xF8,0xFE);
      } else {
        draw_text(2*8,y,"\xB3",b=0x02,0xF8);
      }
      snprintf(buf,6,"%5u",mo&2?sqlite3_column_int(st,0):i);
      draw_text(3*8,y,buf,b,sqlite3_column_int(st,6)?0xFA:0xFC);
      draw_text(8*8,y,"\xB3",b,b^0xFA);








      snprintf(buf,6,"%2u %2u",sqlite3_column_int(st,3),sqlite3_column_int(st,4));
      draw_text(9*8,y,buf,b,0xFF);
      draw_text(11*8,y,"\xB3",b,b^0xFA);
      draw_text(14*8,y,"\xB3",b,b^0xFA);
      copy_text_to_plain(buf,255,sqlite3_column_text(st,5));
      draw_text(15*8,y,buf,b,0xFF);

    }
  } else {
    scrmax=(level_nindex+columns-1)/columns;
    draw_text(0,16,mo&2?"(ID)":"(Ord)",0xF7,0xF1);
    if(rescroll) {
      if(sel<scroll*columns) scroll=sel/columns;
      if(sel>(scroll+screen->h/8-3)*columns) scroll=(sel+3-screen->h/8)/columns;







>
>
>
>
>
>
>
>
|
|
|
|
|
|
>







843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        draw_text(2*8,y,"\x10",b=0xF8,0xFE);
      } else {
        draw_text(2*8,y,"\xB3",b=0x02,0xF8);
      }
      snprintf(buf,6,"%5u",mo&2?sqlite3_column_int(st,0):i);
      draw_text(3*8,y,buf,b,sqlite3_column_int(st,6)?0xFA:0xFC);
      draw_text(8*8,y,"\xB3",b,b^0xFA);
      if(ll_ndisp) {
        x=9*8;
        for(i=0;i<ll_ndisp && x<screen->w;i++) {
          levels_column(x,y,i,b,st,buf);
          x+=ll_disp[i].width*8+8;
          if(x<screen->w && !(ll_disp[i].flag&1)) draw_text(x-8,y,"\xB3",b,b^0xFA);
        }
      } else {
        snprintf(buf,6,"%2u %2u",sqlite3_column_int(st,3),sqlite3_column_int(st,4));
        draw_text(9*8,y,buf,b,0xFF);
        draw_text(11*8,y,"\xB3",b,b^0xFA);
        draw_text(14*8,y,"\xB3",b,b^0xFA);
        copy_text_to_plain(buf,255,sqlite3_column_text(st,5));
        draw_text(15*8,y,buf,b,0xFF);
      }
    }
  } else {
    scrmax=(level_nindex+columns-1)/columns;
    draw_text(0,16,mo&2?"(ID)":"(Ord)",0xF7,0xF1);
    if(rescroll) {
      if(sel<scroll*columns) scroll=sel/columns;
      if(sel>(scroll+screen->h/8-3)*columns) scroll=(sel+3-screen->h/8)/columns;

Modified heromesh.h from [39284f6f26] to [641a517ed3].

192
193
194
195
196
197
198

199
200
201
202
203
204
205

typedef struct {
  union {
    char*name; // free when loading table
    Uint8 ag; // aggregate type
  };
  Uint16 ptr;

} DataColumn;

extern char*ll_head;
extern DisplayColumn*ll_disp;
extern Uint8 ll_ndisp;
extern DataColumn*ll_data;
extern Uint8 ll_ndata;







>







192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

typedef struct {
  union {
    char*name; // free when loading table
    Uint8 ag; // aggregate type
  };
  Uint16 ptr;
  Uint8 sgn; // 0=unsigned, 1=signed
} DataColumn;

extern char*ll_head;
extern DisplayColumn*ll_disp;
extern Uint8 ll_ndisp;
extern DataColumn*ll_data;
extern Uint8 ll_ndata;