Skip to content

Commit

Permalink
Update 2024-02-29-creating-the-perfect-modding-language.md
Browse files Browse the repository at this point in the history
  • Loading branch information
MyNameIsTrez authored Feb 22, 2025
1 parent 973fbc2 commit f39654a
Showing 1 changed file with 2 additions and 151 deletions.
153 changes: 2 additions & 151 deletions _posts/2024-02-29-creating-the-perfect-modding-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ helper_spawn_sparkles() {

The <span style="color:#82AAFF">`helper_spawn_sparkles`</span> function is a helper function, which the game can't call, but the <span style="color:#C3E88D">`on_`</span> 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:
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit f39654a

Please sign in to comment.