validate.c (6792B)
1 #include "engine/validate.h" 2 #include "game/content.h" 3 4 #include <stdint.h> 5 #include <stdio.h> 6 #include <string.h> 7 8 #define CHECK(test) do { if (!(test)) { \ 9 fprintf(stderr, "validation test failed at line %d: %s\n", __LINE__, #test); \ 10 return 1; \ 11 } } while (0) 12 13 static int 14 ph_reject(const PhWorldSetup *setup, const char *expected) 15 { 16 char error[256]; 17 18 if (ph_validate_world(setup, error, sizeof(error)) < 0 && strstr(error, expected)) return 1; 19 fprintf(stderr, "expected '%s', got '%s'\n", expected, error); 20 return 0; 21 } 22 23 /* Write a fixture in the same format as mapc, without changing production maps. */ 24 static int 25 ph_write_map(const char *path, const PhArea *area) 26 { 27 struct { 28 uint32_t magic, version, width, height, markers; 29 char name[PH_AREA_NAME_MAX]; 30 } header = { PH_MAP_MAGIC, PH_MAP_VERSION, area->width, area->height, 31 area->marker_count, "Validation fixture" }; 32 FILE *fp = fopen(path, "wb"); 33 int i, ok; 34 size_t count = (size_t)area->width * area->height; 35 36 if (!fp) return -1; 37 ok = fwrite(&header, sizeof(header), 1, fp) == 1 && fwrite(area->tiles, 1, count, fp) == count; 38 for (i = 0; i < area->marker_count && ok; ++i) { 39 uint32_t marker[] = { area->markers[i].symbol, area->markers[i].tile_x, area->markers[i].tile_y }; 40 ok = fwrite(marker, sizeof(marker), 1, fp) == 1; 41 } 42 if (fclose(fp)) ok = 0; 43 return ok ? 0 : -1; 44 } 45 46 static int 47 ph_map_tests(PhWorldSetup *setup, PhAreaDef *defs) 48 { 49 const char *path = "obj/validation-fixture.phmap"; 50 PhArea area; 51 PhAreaMarker saved; 52 const char *original = defs[0].path; 53 unsigned char tile; 54 int result = 1; 55 int i; 56 57 if (ph_area_load(&area, original) < 0) return 1; 58 defs[0].path = path; 59 tile = area.tiles[0]; 60 area.tiles[0] = 255; 61 if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "invalid tile index 255")) goto done; 62 area.tiles[0] = tile; 63 saved = area.markers[0]; 64 area.markers[0].symbol = '?'; 65 if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "undefined marker")) goto done; 66 area.markers[0] = saved; 67 area.markers[0].tile_x = area.width; 68 if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "cannot load map")) goto done; 69 area.markers[0] = saved; 70 area.markers[1] = saved; 71 if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "overlapping markers")) goto done; 72 ph_area_free(&area); 73 if (ph_area_load(&area, original) < 0) goto done; 74 /* Dropping the starting marker must be diagnosed before play. */ 75 for (i = 0; i < area.marker_count; ++i) { 76 const PhMarkerDef *marker = ph_area_catalog_marker(setup->areas, defs[0].id, area.markers[i].symbol); 77 if (marker->kind != PH_MARKER_PLAYER) continue; 78 area.markers[i] = area.markers[--area.marker_count]; 79 break; 80 } 81 if (ph_write_map(path, &area) < 0 || !ph_reject(setup, "invalid spawn counts")) goto done; 82 result = 0; 83 done: 84 defs[0].path = original; 85 ph_area_free(&area); 86 remove(path); 87 return result; 88 } 89 90 int 91 main(void) 92 { 93 PhWorldSetup setup = ph_game_world; 94 PhAreaCatalog catalog = *setup.areas; 95 PhEntityDef entities[PH_ENTITY_DEF_COUNT]; 96 PhItemDef items[PH_ITEM_DEF_COUNT]; 97 PhAreaDef areas[PH_AREA_COUNT]; 98 PhTileDef tiles[PH_TILE_DEF_COUNT]; 99 PhMarkerDef markers[PH_MARKER_DEF_COUNT + 1]; 100 PhSpellDef spells[PH_SPELL_DEF_COUNT]; 101 PhSpellBook book = ph_player_spellbook; 102 char error[256]; 103 int equipment = 9999; 104 int i; 105 106 if (ph_game_validate_content(error, sizeof(error)) < 0) { 107 fprintf(stderr, "content: %s\n", error); 108 return 1; 109 } 110 memcpy(entities, ph_entity_defs, sizeof(entities)); 111 memcpy(items, ph_item_defs, sizeof(items)); 112 memcpy(areas, ph_area_defs, sizeof(areas)); 113 memcpy(tiles, ph_tile_defs, sizeof(tiles)); 114 memcpy(markers, ph_marker_defs, sizeof(ph_marker_defs)); 115 memcpy(spells, ph_spell_defs, sizeof(spells)); 116 setup.areas = &catalog; 117 setup.content.entities = entities; 118 setup.content.items = items; 119 catalog.areas = areas; 120 catalog.tiles = tiles; 121 catalog.markers = markers; 122 book.defs = spells; 123 CHECK(ph_validate_world(&setup, error, sizeof(error)) == 0); 124 CHECK(ph_reject(NULL, "missing setup")); 125 catalog.tiles = NULL; 126 CHECK(ph_reject(&setup, "missing arrays")); 127 catalog.tiles = tiles; 128 catalog.marker_count = -1; 129 CHECK(ph_reject(&setup, "invalid area catalog counts")); 130 catalog.marker_count = PH_MARKER_DEF_COUNT; 131 markers[1] = markers[0]; 132 CHECK(ph_reject(&setup, "duplicate definition")); 133 markers[1] = ph_marker_defs[1]; 134 entities[1].animation_frames = PH_ANIMATION_FRAMES + 1; 135 CHECK(ph_reject(&setup, "invalid definition")); 136 entities[1] = ph_entity_defs[1]; 137 entities[1].id = entities[0].id; 138 CHECK(ph_reject(&setup, "duplicate ID")); 139 entities[1] = ph_entity_defs[1]; 140 entities[1].reward.item_id = 9999; 141 CHECK(ph_reject(&setup, "invalid reward item 9999")); 142 entities[1] = ph_entity_defs[1]; 143 items[1].id = items[0].id; 144 CHECK(ph_reject(&setup, "duplicate ID")); 145 items[1] = ph_item_defs[1]; 146 tiles[1].symbol = tiles[0].symbol; 147 CHECK(ph_reject(&setup, "duplicate symbol")); 148 tiles[1] = ph_tile_defs[1]; 149 areas[1].id = areas[0].id; 150 CHECK(ph_reject(&setup, "duplicate ID")); 151 areas[1] = ph_area_defs[1]; 152 areas[1].path = "obj/no-such-validation-map.phmap"; 153 CHECK(ph_reject(&setup, "cannot load map")); 154 areas[1] = ph_area_defs[1]; 155 setup.start_area_id = 9999; 156 CHECK(ph_reject(&setup, "unknown start area 9999")); 157 setup.start_area_id = ph_game_world.start_area_id; 158 setup.equipment = &equipment; 159 setup.equipment_count = 1; 160 CHECK(ph_reject(&setup, "invalid item 9999")); 161 setup.equipment = ph_game_world.equipment; 162 setup.equipment_count = ph_game_world.equipment_count; 163 markers[0].content_id = 9999; 164 CHECK(ph_reject(&setup, "invalid entity 9999")); 165 markers[0] = ph_marker_defs[0]; 166 for (i = 0; markers[i].kind != PH_MARKER_PORTAL; ++i) {} 167 markers[i].destination_area_id = 9999; 168 CHECK(ph_reject(&setup, "invalid destination 9999")); 169 markers[i] = ph_marker_defs[i]; 170 markers[PH_MARKER_DEF_COUNT] = (PhMarkerDef){ 171 .area_id = markers[i].destination_area_id, .symbol = '?', .tile_symbol = '.', 172 .kind = PH_MARKER_ARRIVAL }; 173 ++catalog.marker_count; 174 markers[i].destination_symbol = '?'; 175 CHECK(ph_reject(&setup, "needs one arrival '?' (found 0)")); 176 --catalog.marker_count; 177 markers[i] = ph_marker_defs[i]; 178 CHECK(ph_map_tests(&setup, areas) == 0); 179 spells[1].id = spells[0].id; 180 CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0 && strstr(error, "duplicate ID")); 181 spells[1] = ph_spell_defs[1]; 182 book.learned[0] = 9999; 183 CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0 && strstr(error, "unknown learned spell 9999")); 184 book = ph_player_spellbook; 185 book.learned_count = PH_SPELL_MAX + 1; 186 CHECK(ph_validate_spells(&book, error, sizeof(error)) < 0); 187 CHECK(ph_validate_world(&setup, NULL, 0) == 0); 188 CHECK(ph_validate_world(NULL, error, 1) < 0 && error[0] == '\0'); 189 puts("content check: catalogs, all maps, and invalid-data regression tests passed"); 190 return 0; 191 }