diff --git a/docs/lua/examples/behavior-add-to-goomba.lua b/docs/lua/examples/behavior-add-to-goomba.lua new file mode 100644 index 00000000..5bf8ddda --- /dev/null +++ b/docs/lua/examples/behavior-add-to-goomba.lua @@ -0,0 +1,14 @@ +-- name: Goomba Leaps +-- description: Makes goombas leap instead of just jump as an example. + +function bhv_custom_goomba_loop(obj) + -- make goombas leap instead of just jump + if obj.oGoombaJumpCooldown >= 9 then + obj.oGoombaJumpCooldown = 8 + obj.oVelY = obj.oVelY + 20 + obj.oForwardVel = 20 + end +end + +-- hook the behavior +id_bhvCustomGoomba = hook_behavior(id_bhvGoomba, OBJ_LIST_PUSHABLE, false, nil, bhv_custom_goomba_loop) diff --git a/docs/lua/examples/behavior-ball.lua b/docs/lua/examples/behavior-ball.lua index 2c91b520..ed6d7676 100644 --- a/docs/lua/examples/behavior-ball.lua +++ b/docs/lua/examples/behavior-ball.lua @@ -81,7 +81,7 @@ function bhv_ball_loop(obj) end end -id_bhvBall = hook_behavior(0, OBJ_LIST_DEFAULT, bhv_ball_init, bhv_ball_loop) +id_bhvBall = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_ball_init, bhv_ball_loop) function mario_update_local(m) if (m.controller.buttonPressed & D_JPAD) ~= 0 then diff --git a/docs/lua/examples/behavior-goomba.lua b/docs/lua/examples/behavior-replace-goomba.lua similarity index 98% rename from docs/lua/examples/behavior-goomba.lua rename to docs/lua/examples/behavior-replace-goomba.lua index 90e44c8c..175beb5b 100644 --- a/docs/lua/examples/behavior-goomba.lua +++ b/docs/lua/examples/behavior-replace-goomba.lua @@ -92,4 +92,4 @@ function bhv_custom_goomba_loop(obj) end -- hook the behavior -id_bhvCustomGoomba = hook_behavior(id_bhvGoomba, OBJ_LIST_PUSHABLE, bhv_custom_goomba_init, bhv_custom_goomba_loop) +id_bhvCustomGoomba = hook_behavior(id_bhvGoomba, OBJ_LIST_PUSHABLE, true, bhv_custom_goomba_init, bhv_custom_goomba_loop) diff --git a/docs/lua/functions.md b/docs/lua/functions.md index a5eeddab..7243d9b1 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -685,7 +685,9 @@ ## [define_custom_obj_fields](#define_custom_obj_fields) -Defines a custom set of overlapping object fields. The `fieldTable` table's keys must start with the letter `o` and the values must be either `u32`, `s32`, or `f32`. +Defines a custom set of overlapping object fields. + +The `fieldTable` table's keys must start with the letter `o` and the values must be either `u32`, `s32`, or `f32`. ### Lua Example `define_custom_obj_fields({ oCustomField1 = 'u32', oCustomField2 = 's32', oCustomField3 = 'f32' })` diff --git a/docs/lua/hooks.md b/docs/lua/hooks.md index 879f23c8..90a738a6 100644 --- a/docs/lua/hooks.md +++ b/docs/lua/hooks.md @@ -19,8 +19,9 @@ Hooks are a way for SM64 to trigger Lua code, whereas the functions listed in [f | Field | Type | Notes | | ----- | ---- | ----- | -| behaviorId | [enum BehaviorId](constants.md#enum-BehaviorId) | Set to `0` to create a new behavior | +| behaviorId | [enum BehaviorId](constants.md#enum-BehaviorId) | Set to `nil` to create a new behavior | | objectList | [enum ObjectList](constants.md#enum-ObjectList) | | +| 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 | diff --git a/docs/lua/lua.md b/docs/lua/lua.md index e7d1a467..5228fda1 100644 --- a/docs/lua/lua.md +++ b/docs/lua/lua.md @@ -49,4 +49,5 @@ All of this is a holdover from when there were only two players. It was a reason - [HUD Rendering](examples/hud.lua) - [Object Spawning](examples/spawn-stuff.lua) - [Custom Ball Behavior](examples/behavior-ball.lua) -- [Replace Goomba Behavior](examples/behavior-goomba.lua) +- [Replace Goomba Behavior](examples/behavior-replace-goomba.lua) +- [Add to Goomba Behavior](examples/behavior-add-to-goomba.lua) diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c index 5301f955..8f16f150 100644 --- a/src/engine/behavior_script.c +++ b/src/engine/behavior_script.c @@ -1023,7 +1023,7 @@ cur_obj_update_begin:; // Execute the behavior script. gCurBhvCommand = gCurrentObject->curBhvCommand; - u8 skipBehavior = smlua_call_behavior_hook(&gCurBhvCommand, gCurrentObject); + u8 skipBehavior = smlua_call_behavior_hook(&gCurBhvCommand, gCurrentObject, true); if (!skipBehavior) { do { @@ -1031,6 +1031,8 @@ cur_obj_update_begin:; bhvProcResult = bhvCmdProc(); } while (bhvProcResult == BHV_PROC_CONTINUE); } + + smlua_call_behavior_hook(&gCurBhvCommand, gCurrentObject, false); gCurrentObject->curBhvCommand = gCurBhvCommand; // Increment the object's timer. diff --git a/src/game/spawn_object.c b/src/game/spawn_object.c index 41329ab6..c1016ecb 100644 --- a/src/game/spawn_object.c +++ b/src/game/spawn_object.c @@ -335,13 +335,12 @@ struct Object *create_object(const BehaviorScript *bhvScript) { s32 objListIndex; struct Object *obj; struct ObjectNode *objList; - bhvScript = smlua_override_behavior(bhvScript); - const BehaviorScript *behavior = bhvScript; + const BehaviorScript *behavior = smlua_override_behavior(bhvScript); // If the first behavior script command is "begin ", then // extract the object list from it - if ((bhvScript[0] >> 24) == 0) { - objListIndex = (bhvScript[0] >> 16) & 0xFFFF; + if ((behavior[0] >> 24) == 0) { + objListIndex = (behavior[0] >> 16) & 0xFFFF; } else { objListIndex = OBJ_LIST_DEFAULT; } diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 3048315d..ce14315f 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -307,8 +307,10 @@ struct LuaHookedBehavior { u32 behaviorId; u32 overrideId; BehaviorScript behavior[2]; + const BehaviorScript* originalBehavior; int initReference; int loopReference; + bool replace; struct ModListEntry* entry; }; @@ -340,7 +342,7 @@ const BehaviorScript* get_lua_behavior_from_id(enum BehaviorId id) { int smlua_hook_behavior(lua_State* L) { if (L == NULL) { return 0; } - if (!smlua_functions_valid_param_count(L, 4)) { return 0; } + if (!smlua_functions_valid_param_count(L, 5)) { return 0; } if (gLuaLoadingEntry == NULL) { LOG_LUA("hook_behavior() can only be called on load."); @@ -353,7 +355,9 @@ int smlua_hook_behavior(lua_State* L) { return 0; } - lua_Integer overrideBehaviorId = smlua_to_integer(L, 1); + bool noOverrideId = (lua_type(L, 1) == LUA_TNIL); + gSmLuaConvertSuccess = true; + lua_Integer overrideBehaviorId = noOverrideId ? 0xFFFFFF : smlua_to_integer(L, 1); if (!gSmLuaConvertSuccess) { LOG_LUA("Hook behavior: tried to override invalid behavior: %lld, %u", overrideBehaviorId, gSmLuaConvertSuccess); smlua_logline(); @@ -367,13 +371,24 @@ int smlua_hook_behavior(lua_State* L) { return 0; } + bool replaceBehavior = smlua_to_boolean(L, 3); + if (!gSmLuaConvertSuccess) { + LOG_LUA("Hook behavior: could not parse replaceBehavior"); + smlua_logline(); + return 0; + } + const BehaviorScript* originalBehavior = noOverrideId ? NULL : get_behavior_from_id(overrideBehaviorId); + if (originalBehavior == NULL) { + replaceBehavior = true; + } + int initReference = 0; - int initReferenceType = lua_type(L, 3); + int initReferenceType = lua_type(L, 4); if (initReferenceType == LUA_TNIL) { // nothing } else if (initReferenceType == LUA_TFUNCTION) { // get reference - lua_pushvalue(L, 3); + lua_pushvalue(L, 4); initReference = luaL_ref(L, LUA_REGISTRYINDEX); } else { LOG_LUA("Hook behavior: tried to reference non-function for init"); @@ -382,12 +397,12 @@ int smlua_hook_behavior(lua_State* L) { } int loopReference = 0; - int loopReferenceType = lua_type(L, 4); + int loopReferenceType = lua_type(L, 5); if (loopReferenceType == LUA_TNIL) { // nothing } else if (loopReferenceType == LUA_TFUNCTION) { // get reference - lua_pushvalue(L, 4); + lua_pushvalue(L, 5); loopReference = luaL_ref(L, LUA_REGISTRYINDEX); } else { LOG_LUA("Hook behavior: tried to reference non-function for loop"); @@ -398,11 +413,13 @@ int smlua_hook_behavior(lua_State* L) { struct LuaHookedBehavior* hooked = &sHookedBehaviors[sHookedBehaviorsCount]; u16 customBehaviorId = (sHookedBehaviorsCount & 0xFFFF) | LUA_BEHAVIOR_FLAG; hooked->behaviorId = customBehaviorId; - hooked->overrideId = (overrideBehaviorId == 0) ? customBehaviorId : overrideBehaviorId; + hooked->overrideId = noOverrideId ? customBehaviorId : overrideBehaviorId; hooked->behavior[0] = (((unsigned int) (((unsigned int)(0x00) & ((0x01 << (8)) - 1)) << (24))) | ((unsigned int) (((unsigned int)(objectList) & ((0x01 << (8)) - 1)) << (16)))); // gross. this is BEGIN(objectList) hooked->behavior[1] = (((unsigned int) (((unsigned int)(0x39) & ((0x01 << (8)) - 1)) << (24))) | ((unsigned int) (((unsigned int)(customBehaviorId) & ((0x01 << (16)) - 1)) << (0)))); // gross. this is ID(customBehaviorId) + hooked->originalBehavior = originalBehavior ? originalBehavior : hooked->behavior; hooked->initReference = initReference; hooked->loopReference = loopReference; + hooked->replace = replaceBehavior; hooked->entry = gLuaActiveEntry; sHookedBehaviorsCount++; @@ -413,7 +430,7 @@ int smlua_hook_behavior(lua_State* L) { return 1; } -bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object) { +bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object, bool before) { lua_State* L = gLuaState; if (L == NULL) { return false; } for (int i = 0; i < sHookedBehaviorsCount; i++) { @@ -424,9 +441,17 @@ bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* ob continue; } + // figure out whether to run before or after + if (before && !hooked->replace) { + return false; + } + if (!before && hooked->replace) { + return false; + } + // retrieve and remember first run - bool firstRun = (*behavior == hooked->behavior); - if (firstRun) { *behavior = &hooked->behavior[1]; } + bool firstRun = (object->curBhvCommand == hooked->originalBehavior); + if (firstRun && hooked->replace) { *behavior = &hooked->behavior[1]; } // get function and null check it int reference = firstRun ? hooked->initReference : hooked->loopReference; @@ -447,12 +472,13 @@ bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* ob return true; } - return true; + return hooked->replace; } return false; } + ///////////////////////// // hooked chat command // ///////////////////////// @@ -672,8 +698,10 @@ static void smlua_clear_hooks(void) { hooked->behaviorId = 0; hooked->behavior[0] = 0; hooked->behavior[1] = 0; + hooked->originalBehavior = NULL; hooked->initReference = 0; hooked->loopReference = 0; + hooked->replace = false; hooked->entry = NULL; } sHookedBehaviorsCount = 0; diff --git a/src/pc/lua/smlua_hooks.h b/src/pc/lua/smlua_hooks.h index 9f0d144f..a65b7b76 100644 --- a/src/pc/lua/smlua_hooks.h +++ b/src/pc/lua/smlua_hooks.h @@ -39,7 +39,7 @@ void smlua_call_event_hooks_interact_params(enum LuaHookedEventType hookType, st const BehaviorScript* smlua_override_behavior(const BehaviorScript* behavior); const BehaviorScript* get_lua_behavior_from_id(enum BehaviorId id); -bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object); +bool smlua_call_behavior_hook(const BehaviorScript** behavior, struct Object* object, bool before); bool smlua_call_action_hook(struct MarioState* m, s32* returnValue); u32 smlua_get_action_interaction_type(struct MarioState* m);