diff --git a/_posts/2024-02-29-creating-the-perfect-modding-language.md b/_posts/2024-02-29-creating-the-perfect-modding-language.md index 0a449a5..275e926 100644 --- a/_posts/2024-02-29-creating-the-perfect-modding-language.md +++ b/_posts/2024-02-29-creating-the-perfect-modding-language.md @@ -157,6 +157,8 @@ helper_spawn_sparkles() { The `helper_spawn_sparkles` function is a helper function, which the game can't call, but the `on_` functions in this file can. +For a full example, I recommend downloading/cloning the [grug terminal game repository](https://github.com/MyNameIsTrez/grug-terminal-game) locally, so you can step through the code of the game and grug.c with a debugger. + ## The game can allow grug entities to edit each other's data The game could be responsible for giving every entity a map (think hash maps/Lua tables/JavaScript objects/Python dictionaries), where mods can then read from and write to each other's maps: @@ -230,157 +232,6 @@ There are three test categories: A fuzzer is basically a neural network that generates a random string, throws it into the fuzzed program, and gets a reward if it walked a new path through the fuzzed program's code. If it got a reward, it uses the knowledge that there is a good chance it'll get more rewards if it tries similar strings. In this manner it quickly finds most possible paths through the fuzzed program's code, also finding a few inputs that crashed `grug.c`, which I of course patched. -## How a game developer might use grug - -The below snippets are based on the [grug terminal game repository](https://github.com/MyNameIsTrez/grug-terminal-game), so if anything confuses you, feel free to check out the full program. - -Games typically have an update loop, so by calling `grug_regenerate_modified_mods()` in there you can tell grug to recompile any modified mods, where the function returns `true` if there was an error: - -```bettercpp -int main() { - while (true) { - if (grug_regenerate_modified_mods()) { - if (grug_error.has_changed) { - printf( - "%s:%d: %s (detected in grug.c:%d)\n", - grug_error.path, - grug_error.line_number, - grug_error.msg, - grug_error.grug_c_line_number - ); - } - continue; - } - - if (grug_mod_had_runtime_error()) { - fprintf(stderr, "Runtime error: %s\n", grug_get_runtime_error_reason()); - fprintf( - stderr, - "Error occurred when the game called %s(), from %s\n", - grug_on_fn_name, - grug_on_fn_path - ); - continue; - } - - static bool initialized = false; - if (!initialized) { - initialized = true; - init(); - } - - reload_modified_entities(); - - // Since this is a terminal game, there are no PNGs/MP3s/etc. - // reload_modified_resources(); - - update(); - } -} -``` - -The call to `reload_modified_entities()` in the main function loops over all regenerated mods, reinitializing the tool's globals and using the new `on_` fns: - -```bettercpp -void reload_modified_entities() { - // For every reloaded grug file - for (size_t reload_idx = 0; reload_idx < grug_reloads_size; reload_idx++) { - struct grug_modified reload = grug_reloads[reload_idx]; - - // For the player and opponent tools - for (size_t i = 0; i < 2; i++) { - - // If the reloaded grug file has the same tool type - if (reload.old_dll == data.tool_dlls[i]) { - data.tool_dlls[i] = reload.new_dll; - - // Reinitialize the tool's globals - free(data.tool_globals[i]); - data.tool_globals[i] = malloc(reload.globals_size); - reload.init_globals_fn(data.tool_globals[i]); - - // Use the new on fns - data.tools[i].on_fns = reload.on_fns; - } - } - } -} -``` - -The call to `init()` in the main function gives the player tool 0, and the opponent tool 1 from the `mods/` directory: - -```bettercpp -void init() { - pick_tool(0, PLAYER_INDEX); - pick_tool(1, OPPONENT_INDEX); -} - -void pick_tool(size_t chosen_tool_index, size_t human_index) { - struct grug_file *tool_files = get_type_files("tool"); - - struct grug_file file = tool_files[chosen_tool_index]; - - // This calls the grug file's define fn, which calls our game_fn_define_tool() - file.define_fn(); - - // The previous file.define_fn() line caused tool_definition to be filled - tool tool = tool_definition; - - tool.on_fns = file.on_fns; - - data.tools[human_index] = tool; - data.tool_dlls[human_index] = file.dll; - - // Initialize the tool's globals - free(data.tool_globals[human_index]); - data.tool_globals[human_index] = malloc(file.globals_size); - file.init_globals_fn(data.tool_globals[human_index]); -} - -struct tool tool_definition; - -// This gets called by the define() function in grug mods -void game_fn_define_tool(string name, i32 damage) { - tool_definition = (struct tool){ - .name = name, - .damage = damage, - }; -} -``` - -Finally, the call to `update()` in the main function is the gameplay logic. The most important line is `use(player_tool_globals, PLAYER_INDEX);`, which calls the tool's `on_use()` grug function: - -```bettercpp -void update() { - void *player_tool_globals = data.tool_globals[PLAYER_INDEX]; - void *opponent_tool_globals = data.tool_globals[OPPONENT_INDEX]; - - tool *player_tool = &data.tools[PLAYER_INDEX]; - tool *opponent_tool = &data.tools[OPPONENT_INDEX]; - - printf("You have %d health\n", player->health); - printf("The opponent has %d health\n\n", opponent->health); - - printf("You use your %s against your opponent\n", player_tool->name); - typeof(on_tool_use) *use = player_tool->on_fns->use; - use(player_tool_globals, PLAYER_INDEX); - - if (opponent->health <= 0) { - printf("The opponent died!\n"); - exit(0); - } - - printf("The opponent uses their %s against the player\n", opponent_tool->name); - use = opponent_tool->on_fns->use; - use(opponent_tool_globals, OPPONENT_INDEX); - - if (player->health <= 0) { - printf("You died!\n"); - exit(0); - } -} -``` - ## Why grug Like any good programming language, grug was born from frustration. Specifically, over 4 years of frustration keeping the configuration and Lua files of nearly 200 old Cortex Command mods up-to-date with the game.