From 082c15ed39606fbb06b233fed6ad5f6056fb3dc8 Mon Sep 17 00:00:00 2001 From: Kestrel Gregorich-Trevor Date: Fri, 19 Jan 2024 13:04:23 -0600 Subject: [PATCH] Appearance shuffling. A roguelike classic! This lets us shuffle appearances and colors for actors read in from json files. This commit also ensures that actor arrays are properly freed when the game finishes. I'm quite happy with this refactoring. --- data/creature/boxers.json | 116 ++++++++++++++++++---------------- data/creature/characters.json | 36 ++++++----- data/creature/debug.json | 30 +++++---- data/creature/employees.json | 36 ++++++----- data/item/weapons.json | 64 ++++++++++--------- include/parser.h | 2 +- include/register.h | 7 +- src/main.c | 29 ++++++--- src/parser.c | 93 +++++++++++++++++---------- src/save.c | 1 - src/spawn.c | 2 +- windows/curses/windows.c | 1 - 12 files changed, 241 insertions(+), 176 deletions(-) diff --git a/data/creature/boxers.json b/data/creature/boxers.json index 466d78b..556a37d 100644 --- a/data/creature/boxers.json +++ b/data/creature/boxers.json @@ -1,58 +1,64 @@ { - "flyweight": { - "name": "flyweight", - "lv": 1, - "appearance": "boxer", - "chr": "B", - "color": "yellow", - "hp": 12, - "speed": 40, - "attacks": [ - {"damage": 6, "accuracy": 80, "types": ["Mid"]} - ], - "ai": { "seekdef": 3 } - }, - "bantamweight": { - "name": "bantamweight", - "lv": 5, - "appearance": "boxer", - "chr": "B", - "color": "white", - "hp": 20, - "speed": 40, - "attacks": [ - {"damage": 6, "accuracy": 80, "types": ["Mid"]}, - {"damage": 8, "accuracy": 60, "types": ["Mid"]} - ], - "ai": { "seekdef": 3 } - }, - "featherweight": { - "name": "featherweight", - "lv": 10, - "appearance": "boxer", - "chr": "B", - "color": "blue", - "hp": 40, - "speed": 40, - "attacks": [ - {"damage": 8, "accuracy": 80, "types": ["Mid"]}, - {"damage": 10, "kb":1, "accuracy": 70, "types": ["Mid"]} - ], - "ai": { "seekdef": 3 } - }, - "lightweight": { - "name": "lightweight", - "lv": 15, - "appearance": "boxer", - "chr": "B", - "color": "magenta", - "hp": 80, - "speed": 40, - "attacks": [ - {"damage": 8, "kb": 1, "accuracy": 80, "types": ["Mid"]}, - {"damage": 10, "kb": 1, "accuracy": 80, "types": ["Mid"]}, - {"damage": 12, "accuracy": 60, "types": ["High"]} - ], - "ai": { "seekdef": 3 } + "shuffle": { + "color": 1, + "appearance": 0 + }, + "actors": { + "flyweight": { + "name": "flyweight", + "lv": 1, + "appearance": "boxer", + "chr": "B", + "color": "yellow", + "hp": 12, + "speed": 40, + "attacks": [ + {"damage": 6, "accuracy": 80, "types": ["Mid"]} + ], + "ai": { "seekdef": 3 } + }, + "bantamweight": { + "name": "bantamweight", + "lv": 5, + "appearance": "boxer", + "chr": "B", + "color": "white", + "hp": 20, + "speed": 40, + "attacks": [ + {"damage": 6, "accuracy": 80, "types": ["Mid"]}, + {"damage": 8, "accuracy": 60, "types": ["Mid"]} + ], + "ai": { "seekdef": 3 } + }, + "featherweight": { + "name": "featherweight", + "lv": 10, + "appearance": "boxer", + "chr": "B", + "color": "blue", + "hp": 40, + "speed": 40, + "attacks": [ + {"damage": 8, "accuracy": 80, "types": ["Mid"]}, + {"damage": 10, "kb":1, "accuracy": 70, "types": ["Mid"]} + ], + "ai": { "seekdef": 3 } + }, + "lightweight": { + "name": "lightweight", + "lv": 15, + "appearance": "boxer", + "chr": "B", + "color": "magenta", + "hp": 80, + "speed": 40, + "attacks": [ + {"damage": 8, "kb": 1, "accuracy": 80, "types": ["Mid"]}, + {"damage": 10, "kb": 1, "accuracy": 80, "types": ["Mid"]}, + {"damage": 12, "accuracy": 60, "types": ["High"]} + ], + "ai": { "seekdef": 3 } + } } } \ No newline at end of file diff --git a/data/creature/characters.json b/data/creature/characters.json index 576e1c6..0664217 100644 --- a/data/creature/characters.json +++ b/data/creature/characters.json @@ -1,18 +1,24 @@ { - "Zenzi": { - "name": "Zenzi", - "lv": 1, - "chr": "@", - "color": "white", - "hp": 40, - "speed": 40, - "unique": 1, - "attacks": [ - { "damage": 6, "stun": 40, "accuracy": 70, "types": ["Low"] }, - { "damage": 8, "stun": 80, "accuracy": 95, "types": ["Mid"] }, - { "damage": 12, "stun": 160, "recovery": 80, "accuracy": 50, "kb": 1, "types": ["High"] }, - { "damage": 6, "stun": 80, "kb": 2, "accuracy": 70, "types": ["Grab"] } - ], - "equip" : {} + "shuffle": { + "color": 0, + "appearance": 0 + }, + "actors" :{ + "Zenzi": { + "name": "Zenzi", + "lv": 1, + "chr": "@", + "color": "yellow", + "hp": 40, + "speed": 40, + "unique": 1, + "attacks": [ + { "damage": 6, "stun": 40, "accuracy": 70, "types": ["Low"] }, + { "damage": 8, "stun": 80, "accuracy": 95, "types": ["Mid"] }, + { "damage": 12, "stun": 160, "recovery": 80, "accuracy": 50, "kb": 1, "types": ["High"] }, + { "damage": 6, "stun": 80, "kb": 2, "accuracy": 70, "types": ["Grab"] } + ], + "equip" : {} + } } } \ No newline at end of file diff --git a/data/creature/debug.json b/data/creature/debug.json index a43a856..57611b9 100644 --- a/data/creature/debug.json +++ b/data/creature/debug.json @@ -1,15 +1,21 @@ { - "sandbag": { - "name": "sandbag", - "lv": 0, - "appearance": "punching bag", - "chr": "S", - "color": "blue", - "hp": 100, - "speed": 40, - "attacks": [ - {"damage": 6, "accuracy": 0, "types": ["Mid"]} - ], - "ai": { "seekdef": 10 } + "shuffle": { + "color": 0, + "appearance": 0 + }, + "actors": { + "sandbag": { + "name": "sandbag", + "lv": 0, + "appearance": "punching bag", + "chr": "S", + "color": "blue", + "hp": 100, + "speed": 40, + "attacks": [ + {"damage": 6, "accuracy": 0, "types": ["Mid"]} + ], + "ai": { "seekdef": 10 } + } } } \ No newline at end of file diff --git a/data/creature/employees.json b/data/creature/employees.json index 5f467b8..86ba4a0 100644 --- a/data/creature/employees.json +++ b/data/creature/employees.json @@ -1,18 +1,24 @@ { - "flunky": { - "name": "flunky", - "lv": 1, - "appearance": "exhausted employee", - "chr": "@", - "color": "green", - "hp": 12, - "speed": 40, - "attacks": [ - { "damage": 4, "accuracy": 30, "types": ["Low"] } - ], - "ai": { - "seekdef": 5 - }, - "equip": {} + "shuffle": { + "color": 1, + "appearance": 0 + }, + "actors": { + "flunky": { + "name": "flunky", + "lv": 1, + "appearance": "exhausted employee", + "chr": "@", + "color": "green", + "hp": 12, + "speed": 40, + "attacks": [ + { "damage": 4, "accuracy": 30, "types": ["Low"] } + ], + "ai": { + "seekdef": 5 + }, + "equip": {} + } } } \ No newline at end of file diff --git a/data/item/weapons.json b/data/item/weapons.json index 05f299e..0f57d9f 100644 --- a/data/item/weapons.json +++ b/data/item/weapons.json @@ -1,34 +1,40 @@ { - "knife": { - "name": "combat knife", - "lv": 0, - "appearance": "knife", - "chr": "/", - "color": "white", - "hp": 4, - "speed": 50, - "attacks": [ - { "damage": 5, "kb": 0, "accuracy": 70, "types": ["Mid"] } - ], - "item": { - "pref_slot": "weapon" - }, - "description": "A combat knife. Standard issue for somebody." + "shuffle": { + "color": 0, + "appearance": 0 }, - "pipe": { - "name": "lead pipe", - "lv": 0, - "appearance": "pipe", - "chr": "/", - "color": "cyan", - "hp": 10, - "speed": 100, - "attacks": [ - { "damage": 12, "kb": 1, "accuracy": 80, "types": ["High"] } - ], - "item": { - "pref_slot": "weapon" + "actors": { + "knife": { + "name": "combat knife", + "lv": 0, + "appearance": "knife", + "chr": "/", + "color": "white", + "hp": 4, + "speed": 50, + "attacks": [ + { "damage": 5, "kb": 0, "accuracy": 70, "types": ["Mid"] } + ], + "item": { + "pref_slot": "weapon" + }, + "description": "A combat knife. Standard issue for somebody." }, - "description": "A trusty pipe. Polaris really shouldn't leave these lying around." + "pipe": { + "name": "lead pipe", + "lv": 0, + "appearance": "pipe", + "chr": "/", + "color": "cyan", + "hp": 10, + "speed": 100, + "attacks": [ + { "damage": 12, "kb": 1, "accuracy": 80, "types": ["High"] } + ], + "item": { + "pref_slot": "weapon" + }, + "description": "A trusty pipe. Polaris really shouldn't leave these lying around." + } } } \ No newline at end of file diff --git a/include/parser.h b/include/parser.h index 4ee52dd..6a9efc2 100644 --- a/include/parser.h +++ b/include/parser.h @@ -7,7 +7,7 @@ /* Function Prototypes */ struct wfc_image parse_wfc_json(char *infile); struct actor *actor_from_file(const char *); -void json_to_monster_list(const char *); +void json_to_actor_array(const char *, int *, struct actor **); void json_to_item_list(const char *); #endif \ No newline at end of file diff --git a/include/register.h b/include/register.h index cf21d97..f11ef12 100644 --- a/include/register.h +++ b/include/register.h @@ -24,16 +24,15 @@ /* Func Proto */ void setup_term_dimensions(int, int, int, int); -#define MAX_MONSTERS 200 -#define MAX_ITEMS 200 +#define MAX_ACTORS 200 /* Ridin' High! Monsters and items are counted seperately. */ /* Persistent data which is saved and loaded. */ typedef struct global { char userbuf[MAX_USERSZ]; struct tile levmap[MAPW][MAPH]; int heatmap[NUM_HEATMAPS][MAPW][MAPH]; - struct actor *monsters[MAX_MONSTERS]; - struct actor *items[MAX_ITEMS]; + struct actor *monsters[MAX_ACTORS]; + struct actor *items[MAX_ACTORS]; struct actor *player; /* Assume player is first NPC */ struct actor *target; struct actor *active_attacker; diff --git a/src/main.c b/src/main.c index b6f89d8..8bcf08c 100644 --- a/src/main.c +++ b/src/main.c @@ -44,17 +44,26 @@ error_t parse_args(int, char *, struct argp_state *); * */ void handle_exit(void) { - int freed; + int freed, i; + cleanup_screen(); if (g.debug) printf("Freeing message list...\n"); freed = free_message_list(g.msg_list); if (g.debug) { printf("Freed %d messages.\n", freed); - printf("Freeing actor list...\n"); + printf("Freeing actors on map...\n"); } freed = free_actor_list(g.player); - if (g.debug) + if (g.debug) { printf("Freed %d actors.\n", freed); + printf("Freeing creature and item arrays...\n"); + } + for (i = 0; i < g.total_monsters; i++) { + free_actor(g.monsters[i]); + } + for (i = 0; i < g.total_items; i++) { + free_actor(g.items[i]); + } if (term.saved_locale != NULL) { if (g.debug) printf("Restoring locale...\n"); setlocale (LC_ALL, term.saved_locale); @@ -74,7 +83,6 @@ void handle_sigwinch(int sig) { (void) sig; if (g.turns) save_game(); - cleanup_screen(); exit(0); return; } @@ -104,11 +112,13 @@ void handle_sigsegv(int sig) { * */ void new_game(void) { - json_to_monster_list("data/creature/characters.json"); - json_to_monster_list("data/creature/boxers.json"); - json_to_monster_list("data/creature/employees.json"); - json_to_monster_list("data/creature/debug.json"); - json_to_item_list("data/item/weapons.json"); + /* Parse creatures */ + json_to_actor_array("data/creature/characters.json", &g.total_monsters, g.monsters); + json_to_actor_array("data/creature/boxers.json", &g.total_monsters, g.monsters); + json_to_actor_array("data/creature/employees.json", &g.total_monsters, g.monsters); + json_to_actor_array("data/creature/debug.json", &g.total_monsters, g.monsters); + /* Parse items */ + json_to_actor_array("data/item/weapons.json", &g.total_items, g.items); if (g.practice || g.debug) { logm("The high score list is disabled due to the game mode."); } @@ -231,7 +241,6 @@ int main(int argc, char **argv) { cur_actor = g.player; } } - cleanup_screen(); exit(0); return 0; } \ No newline at end of file diff --git a/src/parser.c b/src/parser.c index 34b4fe7..3e7c034 100644 --- a/src/parser.c +++ b/src/parser.c @@ -26,6 +26,7 @@ #include "spawn.h" struct cJSON* json_from_file(const char *); +void shuffle_attributes(struct actor **, int, int, int, int); struct actor *actor_from_json(cJSON *); struct ai *ai_from_json(struct ai *, cJSON *); struct item *item_from_json(struct item *, cJSON *); @@ -122,55 +123,83 @@ struct actor *actor_from_json(cJSON *actor_json) { return actor; } -void json_to_monster_list(const char *fname) { - // comb through every file in the folder via looking through the master file +/** + * @brief parse a json file into an array of actors + * + * @param fname the file to parse + * @param total_actors pointer to the count of total actors + * @param actor_array pointer to the array to be written to + */ +void json_to_actor_array(const char *fname, int *total_actors, struct actor **actor_array) { cJSON *all_json = json_from_file(fname); - cJSON *actor_json; + cJSON *all_actors_json = NULL; + cJSON *shuffle_json = NULL; + cJSON *actor_json = NULL; struct actor *new_actor; + int appearance, color = 0; + int end; + int start = *total_actors; if (!all_json) { - panik("Could not read creatures: %s\n", fname); + panik("Could not read json file: %s\n", fname); return; } + all_actors_json = cJSON_GetObjectItemCaseSensitive(all_json, "actors"); + shuffle_json = cJSON_GetObjectItemCaseSensitive(all_json, "shuffle"); // read each one into the actor array. must be freed at a later point. - cJSON_ArrayForEach(actor_json, all_json) { - if (g.total_monsters >= MAX_MONSTERS) { - logm_warning("MAX_MONSTERS exceeded. Termination of game is recommended."); + cJSON_ArrayForEach(actor_json, all_actors_json) { + if (*total_actors >= MAX_ACTORS) { + logm_warning("MAX_ACTORS exceeded. Termination of game is recommended."); break; } new_actor = actor_from_json(actor_json); - new_actor->id = g.total_monsters; - g.monsters[g.total_monsters] = new_actor; - g.total_monsters++; + new_actor->id = *total_actors; + actor_array[*total_actors] = new_actor; + (*total_actors)++; } + + /* Shuffle the subsection of the array that we read in (if needed) */ + appearance = cJSON_GetObjectItemCaseSensitive(shuffle_json, "appearance")->valueint; + color = cJSON_GetObjectItemCaseSensitive(shuffle_json, "color")->valueint; + end = *total_actors; + if (appearance || color) + shuffle_attributes(actor_array, start, end, appearance, color); + cJSON_Delete(actor_json); cJSON_Delete(all_json); } -void json_to_item_list(const char *fname) { - cJSON *all_json = json_from_file(fname); - cJSON *actor_json; - struct actor *new_actor; - - if (!all_json) { - panik("Could not read items: %s\n", fname); - return; - } - - // read each one into the actor array. must be freed at a later point. - cJSON_ArrayForEach(actor_json, all_json) { - if (g.total_items >= MAX_ITEMS) { - logm_warning("MAX_ITEMS exceeded. Termination of game is recommended."); - break; +/** + * @brief shuffle the attributes of an array of actors + * + * @param actor_array the array of actors to act upon + * @param start (inclusive) where to start shuffling + * @param end (exclusive) where to stop shuffling + * @param appearance if != 0 then shuffle appearance + * @param color if != 0 then shuffle color + */ +void shuffle_attributes(struct actor **actor_array, int start, int end, int appearance, int color) { + int swap_index; + unsigned char temp_color; + char temp[MAXNAMESIZ]; + for (int i = start; i < end; i++) { + swap_index = rndrng(i, end); + if (i != swap_index) { + /* Appearance */ + if (appearance) { + strncpy(temp, actor_array[i]->name->appearance, sizeof(actor_array[i]->name->appearance)); + strncpy(actor_array[i]->name->appearance, actor_array[swap_index]->name->appearance, sizeof(actor_array[swap_index]->name->appearance)); + strncpy(actor_array[swap_index]->name->appearance, temp, sizeof(temp)); + } + /* Color */ + if (color) { + temp_color = actor_array[i]->color; + actor_array[i]->color = actor_array[swap_index]->color; + actor_array[swap_index]->color = temp_color; + } } - new_actor = actor_from_json(actor_json); - new_actor->id = g.total_items; - g.items[g.total_items] = new_actor; - g.total_items++; } - //cJSON_Delete(all_json); - cJSON_Delete(actor_json); } /** @@ -388,6 +417,6 @@ struct wfc_image parse_wfc_json(char *infile) { .width = width, .height = height }; - cJSON_Delete(test_json); + cJSON_Delete(wfc_json); return image; } \ No newline at end of file diff --git a/src/save.c b/src/save.c index 31e0182..3956475 100644 --- a/src/save.c +++ b/src/save.c @@ -36,7 +36,6 @@ int save_exit(void) { if (!yn_prompt("Save and exit?", 0)) return 0; save_game(); - cleanup_screen(); exit(0); return 0; } diff --git a/src/spawn.c b/src/spawn.c index 6b217a3..a2d82da 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -116,7 +116,7 @@ struct actor *spawn_named_actor(const char *name, int x, int y) { struct actor *actor = NULL; int i = 0; - for (int j = 0; j < MAX_MONSTERS; j++) { + for (int j = 0; j < MAX_ACTORS; j++) { if (!g.monsters[j]) break; if (!strcmp(actor_name(g.monsters[j], NAME_EX), name)) actor = g.monsters[j]; diff --git a/windows/curses/windows.c b/windows/curses/windows.c index 72ea3be..28ffad7 100644 --- a/windows/curses/windows.c +++ b/windows/curses/windows.c @@ -115,7 +115,6 @@ void title_screen(void) { case 'q': menu_destroy(selector); cleanup_win(background_container); - cleanup_screen(); exit(0); } }