win/share/decals.txt contains (initially) a decal_delimiter tile, a decal_pet tile, and a decal_pile tile. The latter two can be used for hilite_pet and hilite_pile implementations that don't have something in place already. The implementation would just need to apply (merge) the non-background decal pixels over a regular tile. The special decal_delimiter tile can be used to confirm the presence of the delimiter tiles in the tileset and mark the end of the regular tiles, and the start of the decal tiles. row 1 contains the background color whose pixels should be ignored when applying a decal to a tile. row 2 contains a row of pixels colored (0, 0, 0). row 3 contains a row of pixels colored pure green (0, 255, 0). row 3 contains a row of pixels colored pure blue (0, 0, 255).
461 lines
14 KiB
C
461 lines
14 KiB
C
/* NetHack 5.0 tiletext.c $NHDT-Date: 1781973099 2026/06/20 16:31:39 $ $NHDT-Branch: NetHack-5.0 $:$NHDT-Revision: 1.31 $ */
|
|
/* Copyright (c) 2016 by Pasi Kallinen */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "config.h"
|
|
#include "tile.h"
|
|
|
|
pixval ColorMap[3][MAXCOLORMAPSIZE];
|
|
int colorsinmap;
|
|
pixval MainColorMap[3][MAXCOLORMAPSIZE];
|
|
int colorsinmainmap;
|
|
|
|
static short color_index[MAXCOLORMAPSIZE];
|
|
static int num_colors;
|
|
static char charcolors[MAXCOLORMAPSIZE];
|
|
|
|
static int placeholder_init = 0;
|
|
static pixel placeholder[TILE_Y][TILE_X];
|
|
static FILE *tile_file;
|
|
static int tile_set, tile_set_indx;
|
|
static const char *const text_sets[] = {
|
|
#if (TILE_X == 8)
|
|
"monthin.txt", "objthin.txt", "oththin.txt", "monthin.txt", "decthin.txt"
|
|
#else
|
|
"monsters.txt", "objects.txt", "other.txt", "monsters.txt", "decals.txt"
|
|
#endif
|
|
};
|
|
|
|
static char inbuf[BUFSZ];
|
|
|
|
extern const char *tilename(int, int, int);
|
|
extern boolean acceptable_tilename(int, int, const char *, const char *);
|
|
static int get_next_line(FILE *, boolean);
|
|
static void read_text_colormap(FILE *);
|
|
static boolean write_text_colormap(FILE *);
|
|
static boolean read_txttile(FILE *, pixel (*)[TILE_X]);
|
|
static void write_txttile(FILE *, pixel (*)[TILE_X]);
|
|
|
|
enum { MONSTER_SET, OBJECT_SET, OTHER_SET, STATUE_SET, DECAL_SET };
|
|
|
|
/* Ugh. DICE doesn't like %[A-Z], so we have to spell it out... */
|
|
#define FORMAT_STRING \
|
|
"%1[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.] = " \
|
|
"(%d, %d, %d) "
|
|
|
|
/*
|
|
-----------------------------------------------------------------------------
|
|
Tile palette Feb 9, 2022
|
|
|
|
[0] . = (71, 108, 108)
|
|
[1] A = (0, 0, 0) grayshade maps to [1] itself
|
|
[2] B = (0, 182, 255) maps to [17] or Q for shade of gray
|
|
[3] C = (255, 108, 0) maps to [18] or R for shade of gray
|
|
[4] D = (255, 0, 0) maps to [19] or S for shade of gray
|
|
[5] E = (0, 0, 255) maps to [20] or T for shade of gray
|
|
[6] F = (0, 145, 0) maps to [27] or 0 for shade of gray
|
|
[7] G = (108, 255, 0) maps to [22] or V for shade of gray
|
|
[8] H = (255, 255, 0) maps to [23] or W for shade of gray
|
|
[9] I = (255, 0, 255) maps to [24] or X for shade of gray
|
|
[10] J = (145, 71, 0) maps to [25] or Y for shade of gray
|
|
[11] K = (204, 79, 0) maps to [26] or Z for shade of gray
|
|
[12] L = (255, 182, 145) maps to [21] or U for shade of gray
|
|
[13] M = (237, 237, 237) grayshade maps to [15] or O for shade of gray
|
|
[14] N = (255, 255, 255) grayshade maps to [13] or M for shade of gray
|
|
[15] O = (215, 215, 215) grayshade maps to [14] or N for shade of gray
|
|
[16] P = (108, 145, 182) maps to [14] or N for shade of gray
|
|
[17] Q = (18, 18, 18) grayshade maps to [1] or A for shade of gray
|
|
[18] R = (54, 54, 54) grayshade maps to [17] or Q for shade of gray
|
|
[19] S = (73, 73, 73) grayshade maps to [18] or R for shade of gray
|
|
[20] T = (82, 82, 82) grayshade maps to [19] or S for shade of gray
|
|
[21] U = (205,205,205) grayshade maps to [20] or T for shade of gray
|
|
[22] V = (104, 104, 104) grayshade maps to [27] or 0 for shade of gray
|
|
[23] W = (131, 131, 131) grayshade maps to [22] or V for shade of gray
|
|
[24] X = (140, 140, 140) grayshade maps to [23] or W for shade of gray
|
|
[25] Y = (149, 149, 149) grayshade maps to [24] or X for shade of gray
|
|
[26] Z = (195, 195, 195) grayshade maps to [25] or Y for shade of gray
|
|
[27] 0 = (100, 100, 100) grayshade maps to [20] or T for shade of gray
|
|
[28] 1 = (72, 108, 108) not mapped in graymappings
|
|
-----------------------------------------------------------------------------
|
|
*/
|
|
|
|
static int grayscale = 0;
|
|
/* grayscale color mapping */
|
|
static const int graymappings[] = {
|
|
/* . A B C D E F G H I J K L M N O P */
|
|
0, 1, 17, 18, 19, 20, 27, 22, 23, 24, 25, 26, 21, 15, 13, 14, 14,
|
|
/* Q R S T U V W X Y Z 0 */
|
|
1, 17, 18, 19, 20, 27, 22, 23, 24, 25, 20
|
|
};
|
|
|
|
void
|
|
set_grayscale(int gs)
|
|
{
|
|
grayscale = gs;
|
|
}
|
|
|
|
/* read next line; repeat until it's a non-comment; populates 'inbuf[]';
|
|
the buffer might already have data (first line after colormap) */
|
|
static int
|
|
get_next_line(FILE *txtfile, boolean force)
|
|
{
|
|
int ch;
|
|
|
|
for (;;) {
|
|
if (force || !inbuf[0]) {
|
|
/* skip leading whitespace */
|
|
do {
|
|
ch = fgetc(txtfile);
|
|
} while (ch == ' ');
|
|
ungetc(ch, txtfile);
|
|
/* get rest of line */
|
|
if (!fgets(inbuf, BUFSZ, txtfile))
|
|
break;
|
|
force = TRUE;
|
|
/* ignore blank lines;
|
|
the old fscanf() processing did that, possibly by accident */
|
|
if (!inbuf[0] || (inbuf[0] == '\n' && !inbuf[1]))
|
|
continue;
|
|
}
|
|
if (inbuf[0] != '#' || !strncmp(inbuf, "# tile ", 7))
|
|
return 1;
|
|
}
|
|
inbuf[0] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
read_text_colormap(FILE *txtfile)
|
|
{
|
|
int i, r, g, b;
|
|
char c[2];
|
|
|
|
for (i = 0; i < MAXCOLORMAPSIZE; i++)
|
|
color_index[i] = -1;
|
|
|
|
num_colors = 0;
|
|
while (get_next_line(txtfile, TRUE)) {
|
|
if (sscanf(inbuf, FORMAT_STRING, c, &r, &g, &b) != 4)
|
|
break;
|
|
color_index[(int) c[0]] = num_colors;
|
|
ColorMap[CM_RED][num_colors] = r;
|
|
ColorMap[CM_GREEN][num_colors] = g;
|
|
ColorMap[CM_BLUE][num_colors] = b;
|
|
num_colors++;
|
|
}
|
|
colorsinmap = num_colors;
|
|
}
|
|
|
|
#undef FORMAT_STRING
|
|
|
|
static boolean
|
|
write_text_colormap(FILE *txtfile)
|
|
{
|
|
int i;
|
|
char c;
|
|
|
|
num_colors = colorsinmainmap;
|
|
if (num_colors > 62) {
|
|
Fprintf(stderr, "too many colors (%d)\n", num_colors);
|
|
return FALSE;
|
|
}
|
|
for (i = 0; i < num_colors; i++) {
|
|
if (i < 26)
|
|
c = 'A' + i;
|
|
else if (i < 52)
|
|
c = 'a' + i - 26;
|
|
else
|
|
c = '0' + i - 52;
|
|
|
|
charcolors[i] = c;
|
|
Fprintf(
|
|
txtfile, "%c = (%d, %d, %d)\n", c, (int) MainColorMap[CM_RED][i],
|
|
(int) MainColorMap[CM_GREEN][i], (int) MainColorMap[CM_BLUE][i]);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* read one tile from win/share/{monsters,objects,other}.txt */
|
|
static boolean
|
|
read_txttile(FILE *txtfile, pixel (*pixels)[TILE_X])
|
|
{
|
|
static int gidx = 0;
|
|
int ph, i = 0, j, k, reslt;
|
|
char buf[BUFSZ], ttype[BUFSZ], gend[BUFSZ];
|
|
const char *p;
|
|
char *q;
|
|
boolean res = FALSE;
|
|
|
|
gend[0] = '\0';
|
|
reslt = 0;
|
|
if (get_next_line(txtfile, FALSE)) {
|
|
if (tile_set == MONSTER_SET || tile_set == STATUE_SET)
|
|
reslt = sscanf(inbuf, "# %255s %d (%255[^,],%255[^)])",
|
|
ttype, &i, buf, gend);
|
|
else
|
|
reslt = sscanf(inbuf, "# %255s %d (%255[^)])",
|
|
ttype, &i, buf);
|
|
}
|
|
if (reslt <= 0)
|
|
goto done;
|
|
|
|
ttype[sizeof ttype - 1] = '\0';
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
if ((tile_set == MONSTER_SET || tile_set == STATUE_SET) && gend[0] == 'f')
|
|
gidx = 1;
|
|
|
|
ph = strcmp(ttype, "placeholder") == 0;
|
|
|
|
if (!ph && strcmp(ttype, "tile") != 0)
|
|
Fprintf(stderr, "Keyword \"%s\" unexpected for entry %d\n", ttype, i);
|
|
|
|
/* check tile name, but not relative number, which will
|
|
* change when tiles are added
|
|
*/
|
|
p = tilename(tile_set, tile_set_indx, gidx);
|
|
if (p && (q = strstr(p, " {")) != 0) {
|
|
*q = '\0';
|
|
}
|
|
if (p && strcmp(p, buf)) {
|
|
boolean other_mismatch =
|
|
(tile_set == OTHER_SET
|
|
&& !acceptable_tilename(tile_set, tile_set_indx, buf, p));
|
|
|
|
if (tile_set != OTHER_SET || other_mismatch) {
|
|
Fprintf(stderr, "warning: for tile %d (numbered %d) of %s,\n",
|
|
tile_set_indx, i, text_sets[tile_set]);
|
|
Fprintf(stderr, "\tfound '%s' while expecting '%s'\n", buf, p);
|
|
}
|
|
}
|
|
tile_set_indx++;
|
|
|
|
/* look for non-whitespace at each stage */
|
|
if (!get_next_line(txtfile, TRUE)) {
|
|
Fprintf(stderr, "unexpected EOF\n");
|
|
goto done;
|
|
}
|
|
if (inbuf[0] != '{') {
|
|
Fprintf(stderr, "didn't find expected '{'\n");
|
|
goto done;
|
|
}
|
|
|
|
for (j = 0; j < TILE_Y; j++) {
|
|
/* read next line of color indices */
|
|
if (!get_next_line(txtfile, TRUE)) {
|
|
Fprintf(stderr, "unexpected EOF\n");
|
|
goto done;
|
|
}
|
|
if ((q = strchr(inbuf, '\n')) == 0)
|
|
q = &inbuf[strlen(inbuf)]; /* eos(inbuf) */
|
|
while (q < &inbuf[TILE_X])
|
|
*q++ = '.';
|
|
*q = '\0';
|
|
|
|
for (i = 0; i < TILE_X; i++) {
|
|
k = color_index[(unsigned char) inbuf[i]];
|
|
if (grayscale && k >= 0) {
|
|
if (k > (SIZE(graymappings) - 1))
|
|
Fprintf(stderr, "Gray mapping issue %d > %d.\n", k,
|
|
SIZE(graymappings) - 1);
|
|
else
|
|
k = graymappings[k];
|
|
}
|
|
if (k == -1) {
|
|
Fprintf(stderr, "color %c not in colormap!\n", inbuf[i]);
|
|
} else if (k >= 0 && k < MAXCOLORMAPSIZE) {
|
|
pixels[j][i].r = ColorMap[CM_RED][k];
|
|
pixels[j][i].g = ColorMap[CM_GREEN][k];
|
|
pixels[j][i].b = ColorMap[CM_BLUE][k];
|
|
} else {
|
|
Fprintf(stderr,
|
|
"%d exceeds array boundary for ColorMap[][%d]!\n",
|
|
k, MAXCOLORMAPSIZE);
|
|
}
|
|
}
|
|
}
|
|
if (ph) {
|
|
/* remember it for later */
|
|
memcpy(placeholder, pixels, sizeof placeholder);
|
|
}
|
|
if (!get_next_line(txtfile, TRUE)) {
|
|
Fprintf(stderr, "unexpected EOF\n");
|
|
goto done;
|
|
}
|
|
if (inbuf[0] != '}') {
|
|
Fprintf(stderr, "didn't find expected '}'\n");
|
|
goto done;
|
|
}
|
|
res = TRUE;
|
|
done:
|
|
inbuf[0] = '\0';
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
write_txttile(FILE *txtfile, pixel (*pixels)[TILE_X])
|
|
{
|
|
const char *p;
|
|
const char *type;
|
|
int i = 0, j, k;
|
|
|
|
if (memcmp(placeholder, pixels, sizeof(placeholder)) == 0)
|
|
type = "placeholder";
|
|
else
|
|
type = "tile";
|
|
|
|
if (tile_set == MONSTER_SET) {
|
|
for (i = 0; i < 2; ++i) {
|
|
Fprintf(txtfile, "# %s %d (unknown,%s)\n", type, tile_set_indx,
|
|
i ? "female" : "male");
|
|
if (i == 0)
|
|
tile_set_indx++;
|
|
}
|
|
} else {
|
|
p = tilename(tile_set, tile_set_indx, i);
|
|
if (p)
|
|
Fprintf(txtfile, "# %s %d (%s)\n", type, tile_set_indx, p);
|
|
else
|
|
Fprintf(txtfile, "# %s %d (null)\n", type, tile_set_indx);
|
|
}
|
|
tile_set_indx++;
|
|
|
|
Fprintf(txtfile, "{\n");
|
|
for (j = 0; j < TILE_Y; j++) {
|
|
Fprintf(txtfile, " ");
|
|
for (i = 0; i < TILE_X; i++) {
|
|
for (k = 0; k < num_colors; k++) {
|
|
if (ColorMap[CM_RED][k] == pixels[j][i].r
|
|
&& ColorMap[CM_GREEN][k] == pixels[j][i].g
|
|
&& ColorMap[CM_BLUE][k] == pixels[j][i].b)
|
|
break;
|
|
}
|
|
if (k >= num_colors)
|
|
Fprintf(stderr, "color not in colormap!\n");
|
|
(void) fputc(charcolors[k], txtfile);
|
|
}
|
|
Fprintf(txtfile, "\n");
|
|
}
|
|
Fprintf(txtfile, "}\n");
|
|
}
|
|
|
|
/* initialize main colormap from globally accessed ColorMap */
|
|
void
|
|
init_colormap(void)
|
|
{
|
|
int i;
|
|
|
|
colorsinmainmap = colorsinmap;
|
|
for (i = 0; i < colorsinmap; i++) {
|
|
MainColorMap[CM_RED][i] = ColorMap[CM_RED][i];
|
|
MainColorMap[CM_GREEN][i] = ColorMap[CM_GREEN][i];
|
|
MainColorMap[CM_BLUE][i] = ColorMap[CM_BLUE][i];
|
|
}
|
|
}
|
|
|
|
/* merge new colors from ColorMap into MainColorMap */
|
|
void
|
|
merge_colormap(void)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < colorsinmap; i++) {
|
|
for (j = 0; j < colorsinmainmap; j++) {
|
|
if (MainColorMap[CM_RED][j] == ColorMap[CM_RED][i]
|
|
&& MainColorMap[CM_GREEN][j] == ColorMap[CM_GREEN][i]
|
|
&& MainColorMap[CM_BLUE][j] == ColorMap[CM_BLUE][i])
|
|
break;
|
|
}
|
|
if (j >= colorsinmainmap) { /* new color */
|
|
if (colorsinmainmap >= MAXCOLORMAPSIZE) {
|
|
Fprintf(stderr,
|
|
"Too many colors to merge -- excess ignored.\n");
|
|
}
|
|
j = colorsinmainmap;
|
|
MainColorMap[CM_RED][j] = ColorMap[CM_RED][i];
|
|
MainColorMap[CM_GREEN][j] = ColorMap[CM_GREEN][i];
|
|
MainColorMap[CM_BLUE][j] = ColorMap[CM_BLUE][i];
|
|
colorsinmainmap++;
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean
|
|
fopen_text_file(const char *filename, const char *type)
|
|
{
|
|
const char *p;
|
|
int i;
|
|
|
|
if (tile_file != (FILE *) 0) {
|
|
Fprintf(stderr, "can only open one text file at a time\n");
|
|
return FALSE;
|
|
}
|
|
|
|
tile_file = fopen(filename, type);
|
|
if (tile_file == (FILE *) 0) {
|
|
Fprintf(stderr, "cannot open text file %s\n", filename);
|
|
return FALSE;
|
|
}
|
|
|
|
p = strrchr(filename, '/');
|
|
if (p)
|
|
p++;
|
|
else
|
|
p = filename;
|
|
|
|
tile_set = 0;
|
|
for (i = 0; i < SIZE(text_sets); i++) {
|
|
if (!strcmp(p, text_sets[i]))
|
|
tile_set = i;
|
|
}
|
|
tile_set_indx = 0;
|
|
|
|
if (!strcmp(type, RDTMODE)) {
|
|
/* Fill placeholder with noise */
|
|
if (!placeholder_init) {
|
|
placeholder_init++;
|
|
for (i = 0; i < (int) sizeof placeholder; i++)
|
|
((char *) placeholder)[i] = i % 256;
|
|
}
|
|
|
|
read_text_colormap(tile_file);
|
|
if (!colorsinmainmap)
|
|
init_colormap();
|
|
else
|
|
merge_colormap();
|
|
return TRUE;
|
|
} else if (!strcmp(type, WRTMODE)) {
|
|
if (!colorsinmainmap) {
|
|
Fprintf(stderr, "no colormap set yet\n");
|
|
return FALSE;
|
|
}
|
|
return (write_text_colormap(tile_file));
|
|
} else {
|
|
Fprintf(stderr, "bad mode (%s) for fopen_text_file\n", type);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
boolean
|
|
read_text_tile(pixel (*pixels)[TILE_X])
|
|
{
|
|
return read_txttile(tile_file, pixels);
|
|
}
|
|
|
|
boolean
|
|
write_text_tile(pixel (*pixels)[TILE_X])
|
|
{
|
|
write_txttile(tile_file, pixels);
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
fclose_text_file(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = fclose(tile_file);
|
|
tile_file = (FILE *) 0;
|
|
return ret;
|
|
}
|