Expose globally custom behaviors defined with hook_behavior (#345)

* Expose globally custom behaviors defined with hook_behavior; macros for mod strings max length

* fixes

* made customBehaviorIndex a mod field to be more relevant
This commit is contained in:
PeachyPeach 2023-04-18 06:54:55 +02:00 committed by GitHub
parent df4226fdd7
commit 999ea1dd42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 78 additions and 17 deletions

View file

@ -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

View file

@ -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:](#)

View file

@ -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];

View file

@ -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: bhv<ModName>Custom<Index>
// - <ModName> is the mod name in CamelCase format, alphanumeric chars only
// - <Index> 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);

View file

@ -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);
}
}

View file

@ -5,6 +5,10 @@
#include <types.h>
#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);

View file

@ -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 {