3 * \brief Load a list of possible graphics modes.
5 * Copyright (c) 2011 Brett Reid
7 * This work is free software; you can redistribute it and/or modify it
8 * under the terms of either:
10 * a) the GNU General Public License as published by the Free Software
11 * Foundation, version 2, or
13 * b) the "Angband license":
14 * This software may be copied and distributed for educational, research,
15 * and not for profit purposes provided that this copyright and statement
16 * are included in all such copies. Other copyrights may also apply.
19 * Imported from Angband 4.2.0 to support main-cocoa.mm for Hengband. Did not
20 * bring over the datafile parsing (datafile.h and datafile.c) so the
21 * implementation here is different from that in Angband.
23 #include "system/angband.h"
24 #include "system/grafmode.h"
25 #include "io/files-util.h"
26 #include "util/angband-files.h"
27 #include "util/string-processor.h"
28 #include "view/display-messages.h"
30 #define GFPARSE_HAVE_NOTHING (0)
31 #define GFPARSE_HAVE_NAME (1)
32 #define GFPARSE_HAVE_DIR (2)
33 #define GFPARSE_HAVE_SIZE (4)
34 #define GFPARSE_HAVE_PREF (8)
35 #define GFPARSE_HAVE_EXTRA (16)
36 #define GFPARSE_HAVE_GRAF (32)
38 typedef struct GrafModeParserState {
45 } GrafModeParserState;
48 /* This is the global state exposed to Angband. */
49 graphics_mode *graphics_modes;
50 graphics_mode *current_graphics_mode = NULL;
51 int graphics_mode_high_id;
54 static int check_last_mode(GrafModeParserState* pgps) {
57 if ((pgps->stage & GFPARSE_HAVE_DIR) == 0) {
59 msg_format("no directory set for tile set, %s, in %s",
60 pgps->list->menuname, pgps->file_name);
62 if ((pgps->stage & GFPARSE_HAVE_SIZE) == 0) {
64 msg_format("no size set for tile set, %s, in %s",
65 pgps->list->menuname, pgps->file_name);
67 if ((pgps->stage & GFPARSE_HAVE_PREF) == 0) {
69 msg_format("no preference file for tile set, %s, in %s",
70 pgps->list->menuname, pgps->file_name);
72 if ((pgps->stage & GFPARSE_HAVE_GRAF) == 0) {
74 msg_format("no graf string set for tile set, %s, in %s",
75 pgps->list->menuname, pgps->file_name);
81 static void parse_line(GrafModeParserState* pgps, const char* line) {
82 static const char whitespc[] = " \t\v\f";
86 while (line[offset] && strchr(whitespc, line[offset]) != 0) {
89 if (! line[offset] || line[offset] == '#') {
93 if (strncmp(line + offset, "name:", 5) == 0) {
94 stage = GFPARSE_HAVE_NAME;
96 } else if (strncmp(line + offset, "directory:", 10) == 0) {
97 stage = GFPARSE_HAVE_DIR;
99 } else if (strncmp(line + offset, "size:", 5) == 0) {
100 stage = GFPARSE_HAVE_SIZE;
102 } else if (strncmp(line + offset, "pref:", 5) == 0) {
103 stage = GFPARSE_HAVE_PREF;
105 } else if (strncmp(line + offset, "extra:", 6) == 0) {
106 stage = GFPARSE_HAVE_EXTRA;
108 } else if (strncmp(line + offset, "graf:", 5) == 0) {
109 stage = GFPARSE_HAVE_GRAF;
112 msg_format("Unexpected data at line %d of %s", pgps->line_no,
118 if (stage == GFPARSE_HAVE_NAME) {
119 graphics_mode *new_mode;
121 if (pgps->stage != GFPARSE_HAVE_NOTHING) {
122 if (check_last_mode(pgps) != 0) {
127 pgps->stage = GFPARSE_HAVE_NAME;
128 new_mode = (graphics_mode *) malloc(sizeof(graphics_mode));
131 msg_format("failed memory allocation for tile set "
132 "information at line %d of %s", pgps->line_no,
136 new_mode->pNext = pgps->list;
137 new_mode->grafID = 0;
138 new_mode->alphablend = 0;
139 new_mode->overdrawRow = 0;
140 new_mode->overdrawMax = 0;
141 new_mode->cell_width = 0;
142 new_mode->cell_height = 0;
143 new_mode->path[0] = '\0';
144 new_mode->pref[0] = '\0';
145 new_mode->file[0] = '\0';
146 new_mode->menuname[0] = '\0';
147 new_mode->graf[0] = '\0';
148 pgps->list = new_mode;
150 if (pgps->stage == GFPARSE_HAVE_NOTHING) {
152 msg_format("values set before tile set name given at line %d"
153 " of %s", pgps->line_no, pgps->file_name);
156 if (pgps->stage & stage) {
157 msg_format("values set more than once for tile set, %s, at line "
158 " %d of %s", pgps->list->menuname, pgps->line_no,
164 case GFPARSE_HAVE_NAME:
169 if (sscanf(line + offset, "%u:%n", &id, &nscan) == 1) {
172 msg_format("ID greater than 255 for tile set at line"
173 " %d of %s", pgps->line_no, pgps->file_name);
174 } else if (id == GRAPHICS_NONE) {
176 msg_format("ID of tile set matches value, %d, reserved "
177 " for no graphics at line %d of %s",
178 GRAPHICS_NONE, pgps->line_no, pgps->file_name);
180 graphics_mode *mode = pgps->list->pNext;
186 if (mode->grafID == id) {
188 msg_format("ID for tile set, %s, at line %d of %s"
189 " is the same as for tile set %s",
190 pgps->list->menuname, pgps->line_no,
191 pgps->file_name, mode->menuname);
196 pgps->list->grafID = id;
199 if (strlen(line + offset) >= sizeof(pgps->list->menuname)) {
201 msg_format("name is too long for tile set at line %d"
202 " of %s", pgps->line_no, pgps->file_name);
203 } else if (line[offset] == '\0') {
205 msg_format("empty name for tile set at line %d of %s",
206 pgps->line_no, pgps->file_name);
208 strcpy(pgps->list->menuname, line + offset);
212 msg_format("malformed ID for tile set at line %d of %s",
213 pgps->line_no, pgps->file_name);
218 case GFPARSE_HAVE_DIR:
220 size_t len = strlen(line + offset);
221 size_t sep_len = strlen(PATH_SEP);
223 if (len >= sizeof(pgps->list->path) ||
224 len + pgps->dir_len + sep_len >= sizeof(pgps->list->path)) {
226 msg_format("directory name is too long for tile set, %s, at"
227 " line %d of %s", pgps->list->menuname,
228 pgps->line_no, pgps->file_name);
229 } else if (line[offset] == '\0') {
231 msg_format("empty directory name for tile set, %s, at line"
232 " line %d of %s", pgps->list->menuname,
233 pgps->line_no, pgps->file_name);
236 * Temporarily hack the path to list.txt so it is not necessary
237 * to separately store the base directory for the tile files.
239 char chold = pgps->file_name[pgps->dir_len];
240 std::filesystem::path p;
242 pgps->file_name[pgps->dir_len] = '\0';
243 p = path_build(std::filesystem::path(pgps->file_name,
244 std::filesystem::path::native_format), line + offset);
245 pgps->file_name[pgps->dir_len] = chold;
246 angband_strcpy(pgps->list->path, p.native().data(),
247 sizeof(pgps->list->path));
252 case GFPARSE_HAVE_SIZE:
257 if (sscanf(line + offset, "%u:%u:%n", &w, &h, &nscan) == 2) {
259 pgps->list->cell_width = w;
262 msg_format("zero width for tile set, %s, at line"
263 " %d of %s", pgps->list->menuname,
264 pgps->line_no, pgps->file_name);
267 pgps->list->cell_height = h;
270 msg_format("zero height for tile set, %s, at line"
271 " %d of %s", pgps->list->menuname,
272 pgps->line_no, pgps->file_name);
275 if (strlen(line + offset) >= sizeof(pgps->list->file)) {
277 msg_format("file name is too long for tile set, %s,"
278 " at line %d of %s", pgps->list->menuname,
279 pgps->line_no, pgps->file_name);
280 } else if (line[offset] == '\0') {
282 msg_format("empty file name for tile set, %s, at line %d"
283 " of %s", pgps->list->menuname, pgps->line_no,
286 (void) strcpy(pgps->list->file, line + offset);
290 msg_format("malformed dimensions for tile set, %s, at line"
291 " %d of %s", pgps->list->menuname, pgps->line_no,
297 case GFPARSE_HAVE_PREF:
298 if (strlen(line + offset) >= sizeof(pgps->list->pref)) {
300 msg_format("preference file name is too long for tile set, %s, "
301 "at line %d of %s", pgps->list->menuname, pgps->line_no,
303 } else if (line[offset] == '\0') {
305 msg_format("empty preference file name for tile set, %s, "
306 "at line %d of %s", pgps->list->menuname, pgps->line_no,
309 strcpy(pgps->list->pref, line + offset);
313 case GFPARSE_HAVE_EXTRA:
315 unsigned int alpha, startdbl, enddbl;
318 if (sscanf(line + offset, "%u:%u:%u%n",
319 &alpha, &startdbl, &enddbl, &nscan) == 3 &&
320 (line[offset + nscan] == '\0' ||
321 strchr(whitespc, line[offset + nscan]) != 0 ||
322 line[offset + nscan] == '#')) {
323 if (startdbl > 255 || enddbl > 255) {
325 msg_format("overdrawMax or overdrawRow is greater than"
326 " 255 for tile set, %s, at line %d of %s",
327 pgps->list->menuname, pgps->line_no,
329 } else if (enddbl < startdbl) {
331 msg_format("overdrawMax less than overdrawRow for tile"
332 "set, %s, at line %d of %s",
333 pgps->list->menuname, pgps->line_no,
336 pgps->list->alphablend = (alpha != 0);
337 pgps->list->overdrawRow = startdbl;
338 pgps->list->overdrawMax = enddbl;
342 msg_format("malformed data for tile set, %s, at line %d of"
343 " %s", pgps->list->menuname, pgps->line_no,
349 case GFPARSE_HAVE_GRAF:
350 if (strlen(line + offset) >= sizeof(pgps->list->graf)) {
352 msg_format("graf string is too long for tile set, %s, at line %d"
353 " of %s", pgps->list->menuname, pgps->line_no,
355 } else if (line[offset] == '\0') {
357 msg_format("empty graf string for tile set, %s, at line %d of %s",
358 pgps->list->menuname, pgps->line_no, pgps->file_name);
360 strcpy(pgps->list->graf, line + offset);
365 if (pgps->result == 0) {
366 pgps->stage |= stage;
371 static void finish_parse_grafmode(GrafModeParserState* pgps,
372 int transfer_results) {
374 * Check what was read for the last mode parsed, since parse_line did
377 if (transfer_results) {
378 if (pgps->list == 0 || pgps->stage == GFPARSE_HAVE_NOTHING) {
379 msg_format("no graphics modes in %s", pgps->file_name);
381 if (check_last_mode(pgps) != 0) {
382 transfer_results = 0;
388 if (transfer_results) {
389 graphics_mode *mode = pgps->list;
390 int max = GRAPHICS_NONE;
392 graphics_mode *new_list;
395 if (mode->grafID > max) {
402 /* Assemble the modes into a contiguous block of memory. */
403 new_list = (graphics_mode *)
404 malloc(sizeof(graphics_mode) * (count + 1));
409 for (i = count - 1; i >= 0; --i, mode = mode->pNext) {
410 memcpy(&(new_list[i]), mode, sizeof(graphics_mode));
411 new_list[i].pNext = &(new_list[i + 1]);
414 /* Hardcode the no graphics option. */
415 new_list[count].pNext = NULL;
416 new_list[count].grafID = GRAPHICS_NONE;
417 new_list[count].alphablend = 0;
418 new_list[count].overdrawRow = 0;
419 new_list[count].overdrawMax = 0;
421 new_list[count].pref, "none", sizeof(new_list[count].pref));
423 new_list[count].path, "", sizeof(new_list[count].path));
425 new_list[count].file, "", sizeof(new_list[count].file));
427 new_list[count].menuname,
429 sizeof(new_list[count].menuname)
432 new_list[count].graf, "ascii", sizeof(new_list[count].graf));
434 /* Release the old global state. */
435 close_graphics_modes();
437 graphics_modes = new_list;
438 graphics_mode_high_id = max;
439 /* Set the default graphics mode to be no graphics */
440 current_graphics_mode = &(graphics_modes[count]);
443 msg_print("failed memory allocation for new graphics modes");
447 /* Release the memory allocated for parsing the file. */
448 while (pgps->list != 0) {
449 graphics_mode *mode = pgps->list;
451 pgps->list = mode->pNext;
457 bool init_graphics_modes(void) {
460 GrafModeParserState gps = { 0, buf, 0, 0, GFPARSE_HAVE_NOTHING, 0 };
461 std::filesystem::path p;
464 /* Build the filename */
465 p = path_build(ANGBAND_DIR_XTRA, "graf");
466 angband_strcpy(line, p.native().data(), sizeof(line));
467 gps.dir_len = strlen(line);
468 p = path_build(p, "list.txt");
469 angband_strcpy(buf, p.native().data(), sizeof(buf));
471 f = angband_fopen(buf, FileOpenMode::READ);
473 msg_format("Cannot open '%s'.", buf);
476 while (angband_fgets(f, line, sizeof line) == 0) {
478 parse_line(&gps, line);
479 if (gps.result != 0) {
484 finish_parse_grafmode(&gps, gps.result == 0);
489 return gps.result == 0;
493 void close_graphics_modes(void) {
494 if (graphics_modes) {
495 free(graphics_modes);
496 graphics_modes = NULL;
498 current_graphics_mode = NULL;
502 graphics_mode *get_graphics_mode(byte id) {
503 graphics_mode *test = graphics_modes;
505 if (test->grafID == id) {