diff --git a/README.md b/README.md index e0596b917..16ae934ac 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,6 @@ sm64coopdx is moddable via Lua, similar to Roblox and Garry's Mod's Lua APIs. To ## Wiki The wiki is made using GitHub's wiki feature, you can go to the wiki tab or click [here](https://github.com/coop-deluxe/sm64coopdx/wiki). + +## Community +We have an official Discord server open to the public [here](https://discord.gg/TJVKHS4). diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 2f2b72d9f..b082b51ad 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -8271,7 +8271,9 @@ HOOK_ON_FIND_WATER_LEVEL = 63 --- @type LuaHookedEventType HOOK_ON_FIND_POISON_GAS_LEVEL = 64 --- @type LuaHookedEventType HOOK_ON_FIND_SURFACE_ON_RAY = 65 --- @type LuaHookedEventType HOOK_ON_DYNOS_PACK_TOGGLED = 66 --- @type LuaHookedEventType -HOOK_MAX = 67 --- @type LuaHookedEventType +HOOK_BEFORE_PLAY_MODE_UPDATE = 67 --- @type LuaHookedEventType +HOOK_ON_PLAY_MODE_UPDATE = 68 --- @type LuaHookedEventType +HOOK_MAX = 69 --- @type LuaHookedEventType --- @alias LuaHookedEventType --- | `HOOK_UPDATE` @@ -8341,6 +8343,8 @@ HOOK_MAX = 67 --- @type LuaHookedEventType --- | `HOOK_ON_FIND_POISON_GAS_LEVEL` --- | `HOOK_ON_FIND_SURFACE_ON_RAY` --- | `HOOK_ON_DYNOS_PACK_TOGGLED` +--- | `HOOK_BEFORE_PLAY_MODE_UPDATE` +--- | `HOOK_ON_PLAY_MODE_UPDATE` --- | `HOOK_MAX` --- @type integer @@ -11241,6 +11245,9 @@ ANIM_FLAG_7 = (1 << 7) --- @type integer ANIM_FLAG_BONE_TRANS = (1 << 8) +--- @type integer +ANIM_FLAG_BONE_SCALE = (1 << 9) + --- @type integer OBJECT_MAX_BHV_STACK = 16 diff --git a/data/dynos_mgr_actor.cpp b/data/dynos_mgr_actor.cpp index e47ae6823..6c8ef05ab 100644 --- a/data/dynos_mgr_actor.cpp +++ b/data/dynos_mgr_actor.cpp @@ -1,17 +1,25 @@ -#include -#include #include #include "dynos.cpp.h" extern "C" { -#include "object_fields.h" -#include "game/level_update.h" #include "game/object_list_processor.h" #include "pc/configfile.h" #include "pc/lua/smlua_hooks.h" #include "pc/mods/mod_fs.h" } +static ObjectList sObjectListsToOverride[] = { + OBJ_LIST_PLAYER, + OBJ_LIST_DESTRUCTIVE, + OBJ_LIST_GENACTOR, + OBJ_LIST_PUSHABLE, + OBJ_LIST_LEVEL, + OBJ_LIST_DEFAULT, + OBJ_LIST_SURFACE, + OBJ_LIST_POLELIKE, + OBJ_LIST_UNIMPORTANT +}; + // Static maps/arrays static std::map& DynosValidActors() { static std::map sDynosValidActors; @@ -25,13 +33,11 @@ static std::vector> &DynosCustomActors() { static std::map sModifiedGraphNodes; -// TODO: the cleanup/refactor didn't really go as planned. -// clean up the actor management code more - std::map &DynOS_Actor_GetValidActors() { return DynosValidActors(); } +// Only used for mods with custom actors. bool DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFilename, const char *aActorName) { const void* georef = DynOS_Builtin_Actor_GetFromName(aActorName); @@ -51,17 +57,14 @@ bool DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil return false; } - // Alloc and init the actors gfx list + // Load the graph node u32 id = 0; - ActorGfx actorGfx = { }; - actorGfx.mGfxData = _GfxData; - actorGfx.mPackIndex = MOD_PACK_INDEX; - actorGfx.mGraphNode = (GraphNode *) DynOS_Model_LoadGeo(&id, MODEL_POOL_SESSION, geoLayout, true); - if (!actorGfx.mGraphNode) { + GraphNode *graphNode = (GraphNode *) DynOS_Model_LoadGeo(&id, MODEL_POOL_SESSION, geoLayout, true); + if (!graphNode) { PrintError(" ERROR: Couldn't load graph node for \"%s\"", actorName.c_str()); return false; } - actorGfx.mGraphNode->georef = georef; + graphNode->georef = georef; // Add to custom actors if (georef == NULL) { @@ -69,6 +72,13 @@ bool DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil georef = geoLayout; } + // Alloc and init the actors gfx list + ActorGfx actorGfx = { + .mGfxData = _GfxData, + .mGraphNode = graphNode, + .mPackIndex = MOD_PACK_INDEX, + }; + // Add to list DynOS_Actor_Valid(georef, actorGfx); return true; @@ -121,44 +131,28 @@ const void *DynOS_Actor_GetLayoutFromName(const char *aActorName) { return NULL; } +bool DynOS_Actor_GetModIndexAndTokenFromGfxData(const GfxData *aGfxData, u32 aTokenIndex, s32 *outModIndex, s32 *outModFileIndex, const char **outToken) { + if (aGfxData) { + if (outModIndex) { *outModIndex = aGfxData->mModIndex; } + if (outModFileIndex) { *outModFileIndex = aGfxData->mModFileIndex; } + if (outToken) { + if (!aTokenIndex || aTokenIndex > aGfxData->mLuaTokenList.Count()) { + return false; + } + *outToken = aGfxData->mLuaTokenList[aTokenIndex - 1].begin(); // token index is 1-indexed + } + return true; + } + return false; +} + bool DynOS_Actor_GetModIndexAndToken(const GraphNode *aGraphNode, u32 aTokenIndex, s32 *outModIndex, s32 *outModFileIndex, const char **outToken) { ActorGfx *_ActorGfx = DynOS_Actor_GetActorGfx(aGraphNode); if (_ActorGfx) { - GfxData *_GfxData = _ActorGfx->mGfxData; - if (_GfxData) { - if (outModIndex) { - *outModIndex = _GfxData->mModIndex; - } - if (outModFileIndex) { - *outModFileIndex = _GfxData->mModFileIndex; - } - if (outToken) { - if (!aTokenIndex || aTokenIndex > _GfxData->mLuaTokenList.Count()) { - return false; - } - *outToken = _GfxData->mLuaTokenList[aTokenIndex - 1].begin(); // token index is 1-indexed - } - return true; - } + return DynOS_Actor_GetModIndexAndTokenFromGfxData(_ActorGfx->mGfxData, aTokenIndex, outModIndex, outModFileIndex, outToken); } else { // try the active level - GfxData *_GfxData = DynOS_Lvl_GetActiveGfx(); - if (_GfxData) { - if (outModIndex) { - *outModIndex = _GfxData->mModIndex; - } - if (outModFileIndex) { - *outModFileIndex = _GfxData->mModFileIndex; - } - if (outToken) { - if (!aTokenIndex || aTokenIndex > _GfxData->mLuaTokenList.Count()) { - return false; - } - *outToken = _GfxData->mLuaTokenList[aTokenIndex - 1].begin(); // token index is 1-indexed - } - return true; - } + return DynOS_Actor_GetModIndexAndTokenFromGfxData(DynOS_Lvl_GetActiveGfx(), aTokenIndex, outModIndex, outModFileIndex, outToken); } - return false; } ActorGfx* DynOS_Actor_GetActorGfx(const GraphNode* aGraphNode) { @@ -214,13 +208,15 @@ void DynOS_Actor_Override(struct Object* obj, void** aSharedChild) { if (it == _ValidActors.end()) { return; } // Check if the behavior uses a character specific model - if (obj && (obj->behavior == bhvMario || - obj->behavior == smlua_override_behavior(bhvNormalCap) || - obj->behavior == smlua_override_behavior(bhvWingCap) || - obj->behavior == smlua_override_behavior(bhvMetalCap) || - obj->behavior == smlua_override_behavior(bhvVanishCap))) { + if (obj && ( + obj->behavior == smlua_override_behavior(bhvMario) || + obj->behavior == smlua_override_behavior(bhvNormalCap) || + obj->behavior == smlua_override_behavior(bhvWingCap) || + obj->behavior == smlua_override_behavior(bhvMetalCap) || + obj->behavior == smlua_override_behavior(bhvVanishCap) + )) { struct NetworkPlayer* np = network_player_from_global_index(obj->globalPlayerIndex); - if (np && np->localIndex > 0 && configDynosLocalPlayerModelOnly && it->second.mPackIndex != MOD_PACK_INDEX) { + if (np != NULL && np->localIndex > 0 && configDynosLocalPlayerModelOnly && it->second.mPackIndex != MOD_PACK_INDEX) { return; } } @@ -228,10 +224,12 @@ void DynOS_Actor_Override(struct Object* obj, void** aSharedChild) { *aSharedChild = (void*)it->second.mGraphNode; } +// Used for both DynOS packs and actors from mods, only overrides existing actors void DynOS_Actor_Override_All(void) { if (!gObjectLists) { return; } + // Loop through all object lists - for (s32 list : { OBJ_LIST_PLAYER, OBJ_LIST_DESTRUCTIVE, OBJ_LIST_GENACTOR, OBJ_LIST_PUSHABLE, OBJ_LIST_LEVEL, OBJ_LIST_DEFAULT, OBJ_LIST_SURFACE, OBJ_LIST_POLELIKE, OBJ_LIST_UNIMPORTANT }) { + for (ObjectList list : sObjectListsToOverride) { struct Object *_Head = (struct Object *) &gObjectLists[list]; for (struct Object *_Object = (struct Object *) _Head->header.next; _Object != _Head; _Object = (struct Object *) _Object->header.next) { if (_Object->activeFlags && _Object->header.gfx.sharedChild != NULL) { diff --git a/data/dynos_mgr_anim.cpp b/data/dynos_mgr_anim.cpp index 21db5f690..58690eb55 100644 --- a/data/dynos_mgr_anim.cpp +++ b/data/dynos_mgr_anim.cpp @@ -45,8 +45,16 @@ static s32 RetrieveCurrentAnimationIndex(struct Object *aObject) { void DynOS_Anim_Swap(void *aPtr) { if (!aPtr) { return; } - static Animation *pDefaultAnimation = NULL; - static Animation sGfxDataAnimation; + // Must support nested calls (held objects render inside other object render) + // and interleaving objects without corrupting swap state. + struct AnimSwapFrame { + struct Object *obj; + Animation *defaultAnim; + Animation gfxDataAnim; + }; + + static AnimSwapFrame sAnimSwapFrames[32] = { 0 }; + static s32 sCurrAnimSwapIndex = 0; // Does the object have a model? struct Object *_Object = (struct Object *) aPtr; @@ -54,9 +62,20 @@ void DynOS_Anim_Swap(void *aPtr) { return; } + // Determine if this call is the "swap" phase or "restore" phase. + // The engine calls DynOS_Anim_Swap twice around geo_set_animation_globals. + const bool restoring = (sCurrAnimSwapIndex > 0 && sAnimSwapFrames[sCurrAnimSwapIndex - 1].obj == _Object); + // Swap the current animation with the one from the Gfx data - if (!pDefaultAnimation) { - pDefaultAnimation = _Object->header.gfx.animInfo.curAnim; + if (!restoring) { + if (sCurrAnimSwapIndex >= (s32) ARRAY_COUNT(sAnimSwapFrames)) { + return; + } + + AnimSwapFrame *frame = &sAnimSwapFrames[sCurrAnimSwapIndex]; + frame->obj = _Object; + frame->defaultAnim = _Object->header.gfx.animInfo.curAnim; + sCurrAnimSwapIndex++; // ActorGfx data ActorGfx* _ActorGfx = DynOS_Actor_GetActorGfx(_Object->header.gfx.sharedChild); @@ -100,23 +119,26 @@ void DynOS_Anim_Swap(void *aPtr) { // Animation data const AnimData *_AnimData = (const AnimData *) _GfxData->mAnimationTable[_AnimIndex].second; if (_AnimData) { - sGfxDataAnimation.flags = _AnimData->mFlags; - sGfxDataAnimation.animYTransDivisor = _AnimData->mUnk02; - sGfxDataAnimation.startFrame = _AnimData->mUnk04; - sGfxDataAnimation.loopStart = _AnimData->mUnk06; - sGfxDataAnimation.loopEnd = _AnimData->mUnk08; - sGfxDataAnimation.unusedBoneCount = _AnimData->mUnk0A.second; - sGfxDataAnimation.values = (u16*)_AnimData->mValues.second.begin(); - sGfxDataAnimation.index = (u16*)_AnimData->mIndex.second.begin(); - sGfxDataAnimation.valuesLength = _AnimData->mValues.second.Count(); - sGfxDataAnimation.indexLength = _AnimData->mIndex.second.Count(); - sGfxDataAnimation.length = _AnimData->mLength; - _Object->header.gfx.animInfo.curAnim = &sGfxDataAnimation; + frame->gfxDataAnim.flags = _AnimData->mFlags; + frame->gfxDataAnim.animYTransDivisor = _AnimData->mUnk02; + frame->gfxDataAnim.startFrame = _AnimData->mUnk04; + frame->gfxDataAnim.loopStart = _AnimData->mUnk06; + frame->gfxDataAnim.loopEnd = _AnimData->mUnk08; + frame->gfxDataAnim.unusedBoneCount = _AnimData->mUnk0A.second; + frame->gfxDataAnim.values = (u16*) _AnimData->mValues.second.begin(); + frame->gfxDataAnim.index = (u16*) _AnimData->mIndex.second.begin(); + frame->gfxDataAnim.valuesLength = _AnimData->mValues.second.Count(); + frame->gfxDataAnim.indexLength = _AnimData->mIndex.second.Count(); + frame->gfxDataAnim.length = _AnimData->mLength; + _Object->header.gfx.animInfo.curAnim = &frame->gfxDataAnim; } // Restore the default animation } else { - _Object->header.gfx.animInfo.curAnim = pDefaultAnimation; - pDefaultAnimation = NULL; + sCurrAnimSwapIndex--; + AnimSwapFrame *frame = &sAnimSwapFrames[sCurrAnimSwapIndex]; + _Object->header.gfx.animInfo.curAnim = frame->defaultAnim; + frame->obj = NULL; + frame->defaultAnim = NULL; } } diff --git a/docs/lua/constants.md b/docs/lua/constants.md index 5f151d0ba..0ad8c394f 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -3590,7 +3590,9 @@ | HOOK_ON_FIND_POISON_GAS_LEVEL | 64 | | HOOK_ON_FIND_SURFACE_ON_RAY | 65 | | HOOK_ON_DYNOS_PACK_TOGGLED | 66 | -| HOOK_MAX | 67 | +| HOOK_BEFORE_PLAY_MODE_UPDATE | 67 | +| HOOK_ON_PLAY_MODE_UPDATE | 68 | +| HOOK_MAX | 69 | - MAX_HOOKED_BEHAVIORS [:arrow_up_small:](#) @@ -4751,6 +4753,7 @@ - ANIM_FLAG_6 - ANIM_FLAG_7 - ANIM_FLAG_BONE_TRANS +- ANIM_FLAG_BONE_SCALE - OBJECT_MAX_BHV_STACK - OBJECT_NUM_REGULAR_FIELDS - OBJECT_NUM_CUSTOM_FIELDS diff --git a/docs/lua/guides/hooks.md b/docs/lua/guides/hooks.md index eca0d222b..22fdcbeef 100644 --- a/docs/lua/guides/hooks.md +++ b/docs/lua/guides/hooks.md @@ -143,7 +143,7 @@ The lua functions sent to `hook_event()` will be automatically called by SM64 wh | HOOK_ON_GEO_PROCESS | Called when a GeoLayout is processed **Note:** You must set the `hookProcess` field of the graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex | | HOOK_BEFORE_GEO_PROCESS | Called before a GeoLayout is processed **Note:** You must set the `hookProcess` field of the graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex | | HOOK_ON_GEO_PROCESS_CHILDREN | Called when the children of a GeoLayout node is processed **Note:** You must set the `hookProcess` field of the parent graph node to a non-zero value | [GraphNode](../structs.md#GraphNode) graphNode, `integer` matStackIndex | -| HOOK_MARIO_OVERRIDE_GEOMETRY_INPUTS | Called before running Mario's geometry input logic, return `false` to not run it. | [MarioState](../structs.md) m | +| HOOK_MARIO_OVERRIDE_GEOMETRY_INPUTS | Called before running Mario's geometry input logic, return `false` to not run it. | [MarioState](../structs.md) m | | HOOK_ON_INTERACTIONS | Called when the Mario interactions are processed | [MarioState](../structs.md#MarioState) mario | | HOOK_ALLOW_FORCE_WATER_ACTION | Called when executing a non-water action while under the water's surface, or vice versa. Return `false` to prevent the player from being forced out of the action at the water's surface | [MarioState](../structs.md#MarioState) mario, `boolean` isInWaterAction | | HOOK_BEFORE_WARP | Called before the local player warps. Return a table with `destLevel`, `destArea`, `destWarpNode`, to override the warp | `integer` destLevel, `integer` destArea, `integer` destWarpNode, `integer` arg | @@ -158,6 +158,8 @@ The lua functions sent to `hook_event()` will be automatically called by SM64 wh | HOOK_ON_FIND_POISON_GAS_LEVEL | Called after poison gas level detection completes. Return a number to override the gas level | `number` x, `number` z, `number` gasLevel | | HOOK_ON_FIND_SURFACE_ON_RAY | Called after ray-surface intersection completes. Return `surface` to override the hit surface, or `surface, hitPos` to override both | `Vec3f` orig, `Vec3f` dir, [Surface](../structs.md#Surface) hitSurface, `Vec3f` hitPos | | HOOK_ON_DYNOS_PACK_TOGGLED | Called after a DynOS pack is toggled | `string` dynosPackName, `boolean` enabled | +| HOOK_BEFORE_PLAY_MODE_UPDATE | Called before the play mode is ran. Return a number to override the play mode to be ran. | `number` playMode | +| HOOK_ON_PLAY_MODE_UPDATE | Called after the play mode is ran. Return a number to override the change level. | `number` playMode | ### Parameters diff --git a/include/types.h b/include/types.h index 006531203..a8bd4c792 100644 --- a/include/types.h +++ b/include/types.h @@ -112,6 +112,7 @@ struct VblankHandler #define ANIM_FLAG_6 (1 << 6) // 0x40 #define ANIM_FLAG_7 (1 << 7) // 0x80 #define ANIM_FLAG_BONE_TRANS (1 << 8) +#define ANIM_FLAG_BONE_SCALE (1 << 9) struct Animation { // TODO: Optimize this later if possible. diff --git a/src/game/area.c b/src/game/area.c index c22fa22eb..9de060aa6 100644 --- a/src/game/area.c +++ b/src/game/area.c @@ -448,13 +448,15 @@ void play_transition_after_delay(s16 transType, s16 time, u8 red, u8 green, u8 b void render_game(void) { dynos_update_gfx(); - if (gCurrentArea != NULL && !gWarpTransition.pauseRendering) { - geo_process_root(gCurrentArea->root, gViewportOverride, gViewportClip, gFBSetColor); + if (gCurrentArea != NULL) { + if (!gWarpTransition.pauseRendering) { + geo_process_root(gCurrentArea->root, gViewportOverride, gViewportClip, gFBSetColor); - gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&gViewportFullscreen)); + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&gViewportFullscreen)); - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, - SCREEN_HEIGHT - BORDER_HEIGHT); + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, + SCREEN_HEIGHT - BORDER_HEIGHT); + } if (!gDjuiDisabled) { djui_reset_hud_params(); @@ -466,41 +468,45 @@ void render_game(void) { smlua_call_event_hooks(HOOK_ON_HUD_RENDER_BEHIND, djui_reset_hud_params); djui_gfx_displaylist_end(); } - render_hud(); - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - render_text_labels(); - do_cutscene_handler(); - if (!gDjuiInMainMenu) { - print_displaying_credits_entry(); - } - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, - SCREEN_HEIGHT - BORDER_HEIGHT); - gPauseScreenMode = render_menus_and_dialogs(); + if (!gWarpTransition.pauseRendering) { + render_hud(); - if (gPauseScreenMode != 0) { - gSaveOptSelectIndex = gPauseScreenMode; - } - - if (gViewportClip != NULL) { - make_viewport_clip_rect(gViewportClip); - } else + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + render_text_labels(); + do_cutscene_handler(); + if (!gDjuiInMainMenu) { + print_displaying_credits_entry(); + } gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - BORDER_HEIGHT); + gPauseScreenMode = render_menus_and_dialogs(); - if (gWarpTransition.isActive) { - if (gWarpTransDelay == 0) { - gWarpTransition.isActive = !render_screen_transition(0, gWarpTransition.type, gWarpTransition.time, - &gWarpTransition.data); - if (!gWarpTransition.isActive) { - if (gWarpTransition.type & 1) { - gWarpTransition.pauseRendering = TRUE; - } else { - set_warp_transition_rgb(0, 0, 0); - } - } + if (gPauseScreenMode != 0) { + gSaveOptSelectIndex = gPauseScreenMode; + } + + if (gViewportClip != NULL) { + make_viewport_clip_rect(gViewportClip); } else { - gWarpTransDelay--; + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, + SCREEN_HEIGHT - BORDER_HEIGHT); + } + + if (gWarpTransition.isActive) { + if (gWarpTransDelay == 0) { + gWarpTransition.isActive = !render_screen_transition(0, gWarpTransition.type, gWarpTransition.time, + &gWarpTransition.data); + if (!gWarpTransition.isActive) { + if (gWarpTransition.type & 1) { + gWarpTransition.pauseRendering = TRUE; + } else { + set_warp_transition_rgb(0, 0, 0); + } + } + } else { + gWarpTransDelay--; + } } } } else { diff --git a/src/game/behaviors/bbh_tilting_trap.inc.c b/src/game/behaviors/bbh_tilting_trap.inc.c index 3a810522b..3643854e6 100644 --- a/src/game/behaviors/bbh_tilting_trap.inc.c +++ b/src/game/behaviors/bbh_tilting_trap.inc.c @@ -22,6 +22,7 @@ void bhv_bbh_tilting_trap_platform_loop(void) { u8 playersTouched = 0; for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; y += gMarioStates[i].marioObj->oPosY; diff --git a/src/game/behaviors/controllable_platform.inc.c b/src/game/behaviors/controllable_platform.inc.c index e1b8583ff..b93bbb6f0 100644 --- a/src/game/behaviors/controllable_platform.inc.c +++ b/src/game/behaviors/controllable_platform.inc.c @@ -183,6 +183,7 @@ void controllable_platform_tilt_from_mario(void) { f32 z = 0; for (s32 i = 0; i < MAX_PLAYERS; i++) { + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform == o || gMarioStates[i].marioObj->platform == cur_obj_nearest_object_with_behavior(bhvControllablePlatformSub)) { x += gMarioStates[i].pos[0]; z += gMarioStates[i].pos[2]; diff --git a/src/game/behaviors/dorrie.inc.c b/src/game/behaviors/dorrie.inc.c index 3625267db..74001e4e7 100644 --- a/src/game/behaviors/dorrie.inc.c +++ b/src/game/behaviors/dorrie.inc.c @@ -136,6 +136,7 @@ void dorrie_act_raise_head(void) { for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform != o) { continue; } s32 dist = dist_between_objects(o, gMarioStates[0].marioObj); if (dist <= 780.0f) { continue; } diff --git a/src/game/behaviors/floating_platform.inc.c b/src/game/behaviors/floating_platform.inc.c index af16b85d1..2abb5617f 100644 --- a/src/game/behaviors/floating_platform.inc.c +++ b/src/game/behaviors/floating_platform.inc.c @@ -23,6 +23,7 @@ void floating_platform_act_0(void) { u8 playersTouched = 0; for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; z += gMarioStates[i].marioObj->oPosZ; diff --git a/src/game/behaviors/platform_on_track.inc.c b/src/game/behaviors/platform_on_track.inc.c index 6aa588fcc..c27653b64 100644 --- a/src/game/behaviors/platform_on_track.inc.c +++ b/src/game/behaviors/platform_on_track.inc.c @@ -291,6 +291,7 @@ static void platform_on_track_rock_ski_lift(void) { struct Object* player = NULL; for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform != o) { continue; } player = gMarioStates[i].marioObj; break; diff --git a/src/game/behaviors/seesaw_platform.inc.c b/src/game/behaviors/seesaw_platform.inc.c index c603bcf58..9c120ab0c 100644 --- a/src/game/behaviors/seesaw_platform.inc.c +++ b/src/game/behaviors/seesaw_platform.inc.c @@ -51,6 +51,7 @@ void bhv_seesaw_platform_update(void) { u8 playersTouched = 0; for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform == o) { x += gMarioStates[i].marioObj->oPosX; y += gMarioStates[i].marioObj->oPosY; diff --git a/src/game/behaviors/tilting_inverted_pyramid.inc.c b/src/game/behaviors/tilting_inverted_pyramid.inc.c index 7e3aff26f..0ef886414 100644 --- a/src/game/behaviors/tilting_inverted_pyramid.inc.c +++ b/src/game/behaviors/tilting_inverted_pyramid.inc.c @@ -87,6 +87,7 @@ void bhv_tilting_inverted_pyramid_loop(void) { u8 playersTouched = 0; for (s32 i = 0; i < MAX_PLAYERS; i++) { if (!is_player_active(&gMarioStates[i])) { continue; } + if (gMarioStates[i].marioObj == NULL) { continue; } if (gMarioStates[i].marioObj->platform != o) { continue; } x += gMarioStates[i].marioObj->oPosX; y += gMarioStates[i].marioObj->oPosY; @@ -135,7 +136,7 @@ void bhv_tilting_inverted_pyramid_loop(void) { o->oTiltingPyramidMarioOnPlatform = FALSE; } - // Approach the normals by 0.01f towards the new goal, then create a transform matrix and orient the object. + // Approach the normals by 0.01f towards the new goal, then create a transform matrix and orient the object. // Outside of the other conditionals since it needs to tilt regardless of whether Mario is on. o->oTiltingPyramidNormalX = approach_by_increment(dx, o->oTiltingPyramidNormalX, 0.01f); o->oTiltingPyramidNormalY = approach_by_increment(dy, o->oTiltingPyramidNormalY, 0.01f); diff --git a/src/game/level_update.c b/src/game/level_update.c index 23e0c579c..4f7da4280 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -1503,6 +1503,44 @@ UNUSED static s32 play_mode_unused(void) { return 0; } +s32 update_current_play_mode() { + s32 changeLevel = 0; + + s16 hookPlaymode = sCurrPlayMode; + if (smlua_call_event_hooks(HOOK_BEFORE_PLAY_MODE_UPDATE, sCurrPlayMode, &hookPlaymode)) { + sCurrPlayMode = hookPlaymode; + } + + switch (sCurrPlayMode) { + case PLAY_MODE_NORMAL: + changeLevel = play_mode_normal(); + break; + case PLAY_MODE_PAUSED: + if (!network_check_singleplayer_pause()) { + changeLevel = play_mode_normal(); + } + + if (sCurrPlayMode == PLAY_MODE_PAUSED) { + changeLevel = play_mode_paused(); + } + break; + case PLAY_MODE_CHANGE_AREA: + changeLevel = play_mode_change_area(); + break; + case PLAY_MODE_CHANGE_LEVEL: + changeLevel = play_mode_change_level(); + break; + case PLAY_MODE_FRAME_ADVANCE: + changeLevel = play_mode_frame_advance(); + break; + } + s32 hookChangeLevel = changeLevel; + if (smlua_call_event_hooks(HOOK_ON_PLAY_MODE_UPDATE, sCurrPlayMode, &hookChangeLevel)) { + changeLevel = hookChangeLevel; + } + return changeLevel; +} + void update_menu_level(void) { // figure out level s32 curLevel = 0; @@ -1730,29 +1768,7 @@ s32 update_level(void) { gCurrentArea->localAreaTimer++; } - switch (sCurrPlayMode) { - case PLAY_MODE_NORMAL: - changeLevel = play_mode_normal(); - break; - case PLAY_MODE_PAUSED: - if (!network_check_singleplayer_pause()) { - changeLevel = play_mode_normal(); - } - - if (sCurrPlayMode == PLAY_MODE_PAUSED) { - changeLevel = play_mode_paused(); - } - break; - case PLAY_MODE_CHANGE_AREA: - changeLevel = play_mode_change_area(); - break; - case PLAY_MODE_CHANGE_LEVEL: - changeLevel = play_mode_change_level(); - break; - case PLAY_MODE_FRAME_ADVANCE: - changeLevel = play_mode_frame_advance(); - break; - } + changeLevel = update_current_play_mode(); if (changeLevel) { reset_volume(); diff --git a/src/game/rendering_graph_node.c b/src/game/rendering_graph_node.c index 43c3c1f0e..f1042a21c 100644 --- a/src/game/rendering_graph_node.c +++ b/src/game/rendering_graph_node.c @@ -383,7 +383,6 @@ void patch_mtx_interpolated(f32 delta) { /** * Graph node interpolation */ - static void *sGraphNodeInterpDataMap = NULL; struct GraphNodeInterpData *geo_get_interp_data(void *node, struct GraphNodeObject *obj) { @@ -1059,7 +1058,7 @@ static void geo_process_background(struct GraphNodeBackground *node) { } } -static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 animFrame, u16 **animAttribute) { +static void anim_process(Vec3f translation, Vec3s rotation, Vec3f scale, u8 *animType, s16 animFrame, u16 **animAttribute) { if (*animType == ANIM_TYPE_TRANSLATION) { translation[0] += retrieve_animation_value(gCurAnim, animFrame, animAttribute) * gCurAnimTranslationMultiplier; translation[1] += retrieve_animation_value(gCurAnim, animFrame, animAttribute) * gCurAnimTranslationMultiplier; @@ -1090,6 +1089,19 @@ static void anim_process(Vec3f translation, Vec3s rotation, u8 *animType, s16 an rotation[0] += retrieve_animation_value(gCurAnim, animFrame, animAttribute); rotation[1] += retrieve_animation_value(gCurAnim, animFrame, animAttribute); rotation[2] += retrieve_animation_value(gCurAnim, animFrame, animAttribute); + + if (gCurAnim->flags & ANIM_FLAG_BONE_SCALE) { + s16 scaleX = retrieve_animation_value(gCurAnim, animFrame, animAttribute); + s16 scaleY = retrieve_animation_value(gCurAnim, animFrame, animAttribute); + s16 scaleZ = retrieve_animation_value(gCurAnim, animFrame, animAttribute); + + if (scale != NULL) { + scale[0] *= ((f32) scaleX) / 256.0f; + scale[1] *= ((f32) scaleY) / 256.0f; + scale[2] *= ((f32) scaleZ) / 256.0f; + } + } + if (gCurAnim->flags & ANIM_FLAG_BONE_TRANS) { *animType = ANIM_TYPE_TRANSLATION; } @@ -1108,6 +1120,7 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { Mat4 matrix; Vec3s rotation; Vec3f translation; + Vec3f scale; // Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\. if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; } @@ -1118,8 +1131,10 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { // current frame vec3s_copy(rotation, gVec3sZero); vec3s_to_vec3f(translation, node->translation); - anim_process(translation, rotation, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); + vec3f_copy(scale, gVec3fOne); + anim_process(translation, rotation, scale, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); mtxf_rotate_xyz_and_translate(matrix, translation, rotation); + mtxf_scale_vec3f(matrix, matrix, scale); mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); // previous frame @@ -1130,8 +1145,10 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) { node->translation ); vec3s_copy(rotation, gVec3sZero); - anim_process(translation, rotation, &animType, gPrevAnimFrame, &animAttribute); + vec3f_copy(scale, gVec3fOne); + anim_process(translation, rotation, scale, &animType, gPrevAnimFrame, &animAttribute); mtxf_rotate_xyz_and_translate(matrix, translation, rotation); + mtxf_scale_vec3f(matrix, matrix, scale); mtxf_mul(gMatStackPrev[gMatStackIndex + 1], matrix, gMatStackPrev[gMatStackIndex]); ); @@ -1420,8 +1437,12 @@ static s32 obj_is_in_view(struct GraphNodeObject *node, Mat4 matrix) { static void geo_sanitize_object_gfx(void) { geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE); + geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE_DECAL); + geo_append_display_list(obj_sanitize_gfx, LAYER_OPAQUE_INTER); geo_append_display_list(obj_sanitize_gfx, LAYER_ALPHA); geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT); + geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT_DECAL); + geo_append_display_list(obj_sanitize_gfx, LAYER_TRANSPARENT_INTER); } static struct MarioBodyState *get_mario_body_state_from_mario_object(struct Object *marioObj) { @@ -1688,6 +1709,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { node->fnNode.func(GEO_CONTEXT_HELD_OBJ, &node->fnNode.node, (struct DynamicPool *) gMatStack[gMatStackIndex + 1]); } + s32 savedMatStackIndex = gMatStackIndex; // Increment the matrix stack, If we fail to do so. Just return. if (!increment_mat_stack()) { return; } @@ -1708,7 +1730,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { } geo_sanitize_object_gfx(); + // While rendering the held object's geo tree, ensure "current object" globals + // refer to the held object, otherwise Lua geo callbacks can accidentally + // mutate the holder's render state (e.g. make Wario limbs disappear). + struct GraphNodeObject *savedCurGraphNodeObject = gCurGraphNodeObject; + gCurGraphNodeObject = &node->objNode->header.gfx; geo_process_node_and_siblings(node->objNode->header.gfx.sharedChild); + gCurGraphNodeObject = savedCurGraphNodeObject; gCurGraphNodeHeldObject = NULL; gCurAnimType = gGeoTempState.type; gCurAnimEnabled = gGeoTempState.enabled; @@ -1717,7 +1745,13 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) { gCurrAnimAttribute = gGeoTempState.attribute; gCurAnim = gGeoTempState.anim; gPrevAnimFrame = gGeoTempState.prevFrame; - gMatStackIndex--; + // Force-restore matrix stack index to avoid any imbalance caused by + // held object geo trees (including Lua geo callbacks). + gMatStackIndex = savedMatStackIndex; + + // Reset any render-state changes performed by the held object's geo tree + // so the holder's remaining body parts render correctly. + geo_sanitize_object_gfx(); } if (node->fnNode.node.children != NULL) { @@ -1748,7 +1782,7 @@ static void geo_process_bone(struct GraphNodeBone *node) { vec3s_copy(rotation, node->rotation); vec3s_to_vec3f(translation, node->translation); vec3f_copy(scale, node->scale); - anim_process(translation, rotation, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); + anim_process(translation, rotation, scale, &gCurAnimType, gCurrAnimFrame, &gCurrAnimAttribute); mtxf_rotate_xyz_and_translate(matrix, translation, rotation); mtxf_scale_vec3f(matrix, matrix, scale); mtxf_mul(gMatStack[gMatStackIndex + 1], matrix, gMatStack[gMatStackIndex]); @@ -1764,7 +1798,7 @@ static void geo_process_bone(struct GraphNodeBone *node) { vec3s_to_vec3f(translation, node->translation); vec3f_copy(scale, node->scale); } - anim_process(translation, rotation, &animType, gPrevAnimFrame, &animAttribute); + anim_process(translation, rotation, scale, &animType, gPrevAnimFrame, &animAttribute); mtxf_rotate_xyz_and_translate(matrix, translation, rotation); mtxf_scale_vec3f(matrix, matrix, scale); mtxf_mul(gMatStackPrev[gMatStackIndex + 1], matrix, gMatStackPrev[gMatStackIndex]); diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index 6e032d25b..dcf7a3ee4 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -3575,7 +3575,9 @@ char gSmluaConstants[] = "" "HOOK_ON_FIND_POISON_GAS_LEVEL=64\n" "HOOK_ON_FIND_SURFACE_ON_RAY=65\n" "HOOK_ON_DYNOS_PACK_TOGGLED=66\n" -"HOOK_MAX=67\n" +"HOOK_BEFORE_PLAY_MODE_UPDATE=67\n" +"HOOK_ON_PLAY_MODE_UPDATE=68\n" +"HOOK_MAX=69\n" "MAX_HOOKED_BEHAVIORS=1024\n" "HUD_DISPLAY_LIVES=0\n" "HUD_DISPLAY_COINS=1\n" @@ -4675,6 +4677,7 @@ char gSmluaConstants[] = "" "ANIM_FLAG_6=(1 << 6)\n" "ANIM_FLAG_7=(1 << 7)\n" "ANIM_FLAG_BONE_TRANS=(1 << 8)\n" +"ANIM_FLAG_BONE_SCALE=(1 << 9)\n" "OBJECT_MAX_BHV_STACK=16\n" "OBJECT_NUM_REGULAR_FIELDS=0x50\n" "OBJECT_NUM_CUSTOM_FIELDS=0x40\n" diff --git a/src/pc/lua/smlua_hook_events.inl b/src/pc/lua/smlua_hook_events.inl index 7641c516e..1326ff4ba 100644 --- a/src/pc/lua/smlua_hook_events.inl +++ b/src/pc/lua/smlua_hook_events.inl @@ -65,3 +65,5 @@ SMLUA_EVENT_HOOK(HOOK_ON_FIND_WATER_LEVEL, _, f32 x, f32 z, f32 *waterLevel) // SMLUA_EVENT_HOOK(HOOK_ON_FIND_POISON_GAS_LEVEL, _, f32 x, f32 z, f32 *gasLevel) // Manually defined hook SMLUA_EVENT_HOOK(HOOK_ON_FIND_SURFACE_ON_RAY, _, Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos) // Manually defined hook SMLUA_EVENT_HOOK(HOOK_ON_DYNOS_PACK_TOGGLED, HOOK_RETURN_NEVER, const char *dynosPackName, bool enabled) +SMLUA_EVENT_HOOK(HOOK_BEFORE_PLAY_MODE_UPDATE, HOOK_RETURN_NEVER, s16 playmode, OUTPUT s16 *overridePlaymode) +SMLUA_EVENT_HOOK(HOOK_ON_PLAY_MODE_UPDATE, HOOK_RETURN_NEVER, s16 playmode, OUTPUT s32 *changeLevel) \ No newline at end of file diff --git a/src/pc/lua/smlua_hook_events_autogen.inl b/src/pc/lua/smlua_hook_events_autogen.inl index b344200b7..caeb63c87 100644 --- a/src/pc/lua/smlua_hook_events_autogen.inl +++ b/src/pc/lua/smlua_hook_events_autogen.inl @@ -1893,3 +1893,67 @@ bool smlua_call_event_hooks_HOOK_ON_DYNOS_PACK_TOGGLED(const char *dynosPackName } return hookResult; } + +bool smlua_call_event_hooks_HOOK_BEFORE_PLAY_MODE_UPDATE(s16 playmode, s16 *overridePlaymode) { + lua_State *L = gLuaState; + if (L == NULL) { return false; } + bool hookResult = false; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_BEFORE_PLAY_MODE_UPDATE]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push playmode + lua_pushinteger(L, playmode); + + // call the callback + if (0 != smlua_call_hook(L, 1, 1, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s - '%s/%s'", sLuaHookedEventTypeName[HOOK_BEFORE_PLAY_MODE_UPDATE], hook->mod[i]->relativePath, hook->modFile[i]->relativePath); + continue; + } + hookResult = true; + + // return overridePlaymode + if (lua_type(L, -1) == LUA_TNUMBER) { + *overridePlaymode = smlua_to_integer(L, -1); + } + + lua_settop(L, prevTop); + } + return hookResult; +} + +bool smlua_call_event_hooks_HOOK_ON_PLAY_MODE_UPDATE(s16 playmode, s32 *changeLevel) { + lua_State *L = gLuaState; + if (L == NULL) { return false; } + bool hookResult = false; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_PLAY_MODE_UPDATE]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push playmode + lua_pushinteger(L, playmode); + + // call the callback + if (0 != smlua_call_hook(L, 1, 1, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s - '%s/%s'", sLuaHookedEventTypeName[HOOK_ON_PLAY_MODE_UPDATE], hook->mod[i]->relativePath, hook->modFile[i]->relativePath); + continue; + } + hookResult = true; + + // return changeLevel + if (lua_type(L, -1) == LUA_TNUMBER) { + *changeLevel = smlua_to_integer(L, -1); + } + + lua_settop(L, prevTop); + } + return hookResult; +} diff --git a/src/pc/lua/smlua_hooks.h b/src/pc/lua/smlua_hooks.h index fdf6f8d35..67c795b85 100644 --- a/src/pc/lua/smlua_hooks.h +++ b/src/pc/lua/smlua_hooks.h @@ -83,6 +83,8 @@ enum LuaHookedEventType { HOOK_ON_FIND_POISON_GAS_LEVEL, HOOK_ON_FIND_SURFACE_ON_RAY, HOOK_ON_DYNOS_PACK_TOGGLED, + HOOK_BEFORE_PLAY_MODE_UPDATE, + HOOK_ON_PLAY_MODE_UPDATE, HOOK_MAX, };