diff --git a/autogen/lua_definitions/manual.lua b/autogen/lua_definitions/manual.lua index bab2a3eb..568a0cb7 100644 --- a/autogen/lua_definitions/manual.lua +++ b/autogen/lua_definitions/manual.lua @@ -61,8 +61,9 @@ gServerSettings = {} --- @param replaceBehavior boolean --- @param initFunction fun(obj:Object) --- @param loopFunction fun(obj:Object) +--- @param behaviorName string --- @return BehaviorId -function hook_behavior(behaviorId, objectList, replaceBehavior, initFunction, loopFunction) +function hook_behavior(behaviorId, objectList, replaceBehavior, initFunction, loopFunction, behaviorName) -- ... end diff --git a/docs/lua/guides/hooks.md b/docs/lua/guides/hooks.md index 9fc7c71e..61f6f005 100644 --- a/docs/lua/guides/hooks.md +++ b/docs/lua/guides/hooks.md @@ -24,6 +24,7 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f | replaceBehavior | `bool` | Prevents the original behavior code from running | | initFunction | `Lua Function` ([Object](structs.md#Object) obj) | Runs once per object | | loopFunction | `Lua Function` ([Object](structs.md#Object) obj) | Runs once per frame per object | +| behaviorName | `string` | Optional, name to give to the behavior | ### Returns - [enum BehaviorId](constants.md#enum-BehaviorId) @@ -40,7 +41,7 @@ function bhv_example_loop(obj) obj.oPosY = obj.oPosY + 1 end -id_bhvExample = hook_behavior(0, OBJ_LIST_DEFAULT, bhv_example_init, bhv_example_loop) +id_bhvExample = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_example_init, bhv_example_loop, "bhvExample") ``` [:arrow_up_small:](#) diff --git a/src/pc/lua/smlua.c b/src/pc/lua/smlua.c index 7c0836d0..4e6af359 100644 --- a/src/pc/lua/smlua.c +++ b/src/pc/lua/smlua.c @@ -163,6 +163,7 @@ void smlua_init(void) { gLuaLoadingMod = mod; gLuaActiveMod = mod; gLuaLastHookMod = mod; + gLuaLoadingMod->customBehaviorIndex = 0; gPcDebug.lastModRun = gLuaActiveMod; for (int j = 0; j < mod->fileCount; j++) { struct ModFile* file = &mod->files[j]; diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index e18ebd64..cac0e2eb 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -964,6 +964,7 @@ int smlua_hook_custom_bhv(BehaviorScript *bhvScript, const char *bhvName) { if (L != NULL) { lua_pushinteger(L, customBehaviorId); lua_setglobal(L, bhvName); + LOG_INFO("Registered custom behavior: %04hX - %s", customBehaviorId, bhvName); } return 1; @@ -971,13 +972,15 @@ int smlua_hook_custom_bhv(BehaviorScript *bhvScript, const char *bhvName) { int smlua_hook_behavior(lua_State* L) { if (L == NULL) { return 0; } - if (!smlua_functions_valid_param_count(L, 5)) { return 0; } + if (!smlua_functions_valid_param_range(L, 5, 6)) { return 0; } if (gLuaLoadingMod == NULL) { LOG_LUA_LINE("hook_behavior() can only be called on load."); return 0; } + int paramCount = lua_gettop(L); + if (sHookedBehaviorsCount >= MAX_HOOKED_BEHAVIORS) { LOG_LUA_LINE("Hooked behaviors exceeded maximum references!"); return 0; @@ -1033,6 +1036,49 @@ int smlua_hook_behavior(lua_State* L) { return 0; } + const char *bhvName = NULL; + if (paramCount >= 6) { + int bhvNameType = lua_type(L, 6); + if (bhvNameType == LUA_TNIL) { + // nothing + } else if (bhvNameType == LUA_TSTRING) { + bhvName = smlua_to_string(L, 6); + if (!bhvName || !gSmLuaConvertSuccess) { + LOG_LUA_LINE("Hook behavior: could not parse bhvName"); + return 0; + } + } else { + LOG_LUA_LINE("Hook behavior: invalid type passed for argument bhvName: %u", bhvNameType); + return 0; + } + } + + // If not provided, generate generic behavior name: bhvCustom + // - is the mod name in CamelCase format, alphanumeric chars only + // - is in 3-digit numeric format, ranged from 001 to 256 + // For example, the 4th unnamed behavior of the mod "my-great_MOD" will be named "bhvMyGreatMODCustom004" + if (!bhvName) { + static char sGenericBhvName[MOD_NAME_MAX_LENGTH + 16]; + s32 i = 3; + snprintf(sGenericBhvName, 4, "bhv"); + for (char caps = TRUE, *c = gLuaLoadingMod->name; *c && i < MOD_NAME_MAX_LENGTH + 3; ++c) { + if ('0' <= *c && *c <= '9') { + sGenericBhvName[i++] = *c; + caps = TRUE; + } else if ('A' <= *c && *c <= 'Z') { + sGenericBhvName[i++] = *c; + caps = FALSE; + } else if ('a' <= *c && *c <= 'z') { + sGenericBhvName[i++] = *c + (caps ? 'A' - 'a' : 0); + caps = FALSE; + } else { + caps = TRUE; + } + } + snprintf(sGenericBhvName + i, 12, "Custom%03u", (u32) (gLuaLoadingMod->customBehaviorIndex++) + 1); + bhvName = sGenericBhvName; + } + struct LuaHookedBehavior* hooked = &sHookedBehaviors[sHookedBehaviorsCount]; u16 customBehaviorId = (sHookedBehaviorsCount & 0xFFFF) | LUA_BEHAVIOR_FLAG; hooked->behavior = calloc(3, sizeof(BehaviorScript)); @@ -1051,6 +1097,12 @@ int smlua_hook_behavior(lua_State* L) { sHookedBehaviorsCount++; + // We want to push the behavior into the global LUA state. So mods can access it. + // It's also used for some things that would normally access a LUA behavior instead. + lua_pushinteger(L, customBehaviorId); + lua_setglobal(L, bhvName); + LOG_INFO("Registered custom behavior: %04hX - %s", customBehaviorId, bhvName); + // return behavior ID lua_pushinteger(L, customBehaviorId); diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index e7ba2f36..138f2f2c 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -391,9 +391,10 @@ static void mod_extract_fields(struct Mod* mod) { mod->description = NULL; // read line-by-line - char buffer[512] = { 0 }; + #define BUFFER_SIZE MAX(MAX(MOD_NAME_MAX_LENGTH, MOD_INCOMPATIBLE_MAX_LENGTH), MOD_DESCRIPTION_MAX_LENGTH) + char buffer[BUFFER_SIZE] = { 0 }; while (!feof(f)) { - file_get_line(buffer, 512, f); + file_get_line(buffer, BUFFER_SIZE, f); // no longer in header if (buffer[0] != '-' || buffer[1] != '-') { @@ -404,18 +405,18 @@ static void mod_extract_fields(struct Mod* mod) { // extract the field char* extracted = NULL; if (mod->name == NULL && (extracted = extract_lua_field("-- name:", buffer))) { - mod->name = calloc(33, sizeof(char)); - if (snprintf(mod->name, 32, "%s", extracted) < 0) { + mod->name = calloc(MOD_NAME_MAX_LENGTH + 1, sizeof(char)); + if (snprintf(mod->name, MOD_NAME_MAX_LENGTH, "%s", extracted) < 0) { LOG_INFO("Truncated mod name field '%s'", mod->name); } } else if (mod->incompatible == NULL && (extracted = extract_lua_field("-- incompatible:", buffer))) { - mod->incompatible = calloc(257, sizeof(char)); - if (snprintf(mod->incompatible, 256, "%s", extracted) < 0) { + mod->incompatible = calloc(MOD_INCOMPATIBLE_MAX_LENGTH + 1, sizeof(char)); + if (snprintf(mod->incompatible, MOD_INCOMPATIBLE_MAX_LENGTH, "%s", extracted) < 0) { LOG_INFO("Truncated mod incompatible field '%s'", mod->incompatible); } } else if (mod->description == NULL && (extracted = extract_lua_field("-- description:", buffer))) { - mod->description = calloc(513, sizeof(char)); - if (snprintf(mod->description, 512, "%s", extracted) < 0) { + mod->description = calloc(MOD_DESCRIPTION_MAX_LENGTH + 1, sizeof(char)); + if (snprintf(mod->description, MOD_DESCRIPTION_MAX_LENGTH, "%s", extracted) < 0) { LOG_INFO("Truncated mod description field '%s'", mod->description); } } diff --git a/src/pc/mods/mod.h b/src/pc/mods/mod.h index 62774dd4..e5b56cfb 100644 --- a/src/pc/mods/mod.h +++ b/src/pc/mods/mod.h @@ -5,6 +5,10 @@ #include #include "src/pc/platform.h" +#define MOD_NAME_MAX_LENGTH 32 +#define MOD_INCOMPATIBLE_MAX_LENGTH 256 +#define MOD_DESCRIPTION_MAX_LENGTH 512 + struct Mods; struct ModFile { @@ -30,6 +34,7 @@ struct Mod { bool enabled; bool selectable; size_t size; + u8 customBehaviorIndex; }; void mod_activate(struct Mod* mod); diff --git a/src/pc/network/packets/packet_mod_list.c b/src/pc/network/packets/packet_mod_list.c index dfe32c5e..c5e0790d 100644 --- a/src/pc/network/packets/packet_mod_list.c +++ b/src/pc/network/packets/packet_mod_list.c @@ -57,12 +57,12 @@ void network_send_mod_list(void) { struct Mod* mod = gActiveMods.entries[i]; u16 nameLength = strlen(mod->name); - if (nameLength > 31) { nameLength = 31; } + if (nameLength > MOD_NAME_MAX_LENGTH) { nameLength = MOD_NAME_MAX_LENGTH; } u16 incompatibleLength = 0; if (mod->incompatible) { incompatibleLength = strlen(mod->incompatible); - if (incompatibleLength > 31) { incompatibleLength = 31; } + if (incompatibleLength > MOD_INCOMPATIBLE_MAX_LENGTH) { incompatibleLength = MOD_INCOMPATIBLE_MAX_LENGTH; } } u16 relativePathLength = strlen(mod->relativePath); @@ -188,27 +188,27 @@ void network_receive_mod_list_entry(struct Packet* p) { // get name length u16 nameLength = 0; packet_read(p, &nameLength, sizeof(u16)); - if (nameLength > 31) { + if (nameLength > MOD_NAME_MAX_LENGTH) { LOG_ERROR("Received name with invalid length!"); return; } // get name - char name[32] = { 0 }; + char name[MOD_NAME_MAX_LENGTH + 1] = { 0 }; packet_read(p, name, nameLength * sizeof(u8)); mod->name = strdup(name); // get incompatible length u16 incompatibleLength = 0; packet_read(p, &incompatibleLength, sizeof(u16)); - if (incompatibleLength > 31) { + if (incompatibleLength > MOD_INCOMPATIBLE_MAX_LENGTH) { LOG_ERROR("Received name with invalid length!"); return; } // get incompatible if (incompatibleLength > 0) { - char incompatible[32] = { 0 }; + char incompatible[MOD_INCOMPATIBLE_MAX_LENGTH + 1] = { 0 }; packet_read(p, incompatible, incompatibleLength * sizeof(u8)); mod->incompatible = strdup(incompatible); } else {