diff --git a/mods/lakituCam.lua b/mods/lakituCam.lua new file mode 100644 index 00000000..b40441bc --- /dev/null +++ b/mods/lakituCam.lua @@ -0,0 +1,129 @@ +-- name: Visible Lakitu +-- description: You can now see everyone's Lakitu camera like in the Mirror room.\n\nYou may use this code for your own mods.\n\nBy Isaac + +-- define our variables to hold the global id of each Lakitu's owner, and its blink timer +define_custom_obj_fields({ oLakituOwner = 'u32', oLakituBlinkTimer = 's32' }) + +-- for some reason Lua doesn't treat booleans as 1/0 numbers +local boolToNumber = { [true] = 1, [false] = 0 } + +local function obj_update_blinking(o, timer, base, range, length) + -- update our timer + if timer > 0 then timer = timer - 1 + else timer = base + (range * math.random()) end + + -- set Lakitu's blink state depending on what our timer is at + o.oAnimState = boolToNumber[(timer <= length)] + return timer +end + +local function is_current_area_sync_valid() + -- check all connected players to see if their area sync is valid + for i = 0, (MAX_PLAYERS - 1) do + local np = gNetworkPlayers[i] + if np ~= nil and np.connected and (not np.currLevelSyncValid or not np.currAreaSyncValid) then + return false + end + end + return true +end + +local function active_player(m, np) + -- check if this player is connected and in the same level + if not np.connected or np.currCourseNum ~= gNetworkPlayers[0].currCourseNum or np.currActNum ~= gNetworkPlayers[0].currActNum or np.currLevelNum ~= gNetworkPlayers[0].currLevelNum or + np.currAreaIndex ~= gNetworkPlayers[0].currAreaIndex then + return false + end + return is_player_active(m) +end + +local function obj_mark_for_deletion_on_sync(o) + -- delete this Lakitu if the area's sync status is valid + if gNetworkPlayers[0].currAreaSyncValid then obj_mark_for_deletion(o) end +end + +local function bhv_custom_lakitu_init(o) + -- set up Lakitu's flags + o.oFlags = (OBJ_FLAG_COMPUTE_ANGLE_TO_MARIO | OBJ_FLAG_COMPUTE_DIST_TO_MARIO | OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE) + -- use the default Lakitu animations + o.oAnimations = gObjectAnimations.lakitu_seg6_anims_060058F8 + cur_obj_init_animation(0) + + -- spawn Lakitu's cloud if this isn't the local player's Lakitu + if network_local_index_from_global(o.oLakituOwner) ~= 0 then + spawn_non_sync_object(id_bhvCloud, E_MODEL_MIST, o.oPosX, o.oPosY, o.oPosZ, + function(obj) + -- make the cloud a child of Lakitu + obj.parentObj = o + -- sm64 knows this is a Lakitu cloud if oBehParams2ndByte is set to 1 + -- if oBehParams2ndByte is 0, the cloud will behave as a Fwoosh + obj.oBehParams2ndByte = 1 + -- make the cloud twice the size size of a normal cloud (all Lakitu clouds do this) + obj_scale(obj, 2) + end) + end + + -- init the networked Lakitu + network_init_object(o, true, { "oLakituOwner", "oFaceAngleYaw", "oFaceAnglePitch" }) +end + +local function bhv_custom_lakitu(o) + -- get the gNetworkPlayers table for the player that owns this Lakitu + local np = network_player_from_global_index(o.oLakituOwner) + -- this isn't a valid network player, delete this Lakitu + if np == nil then + obj_mark_for_deletion_on_sync(o) + return + end + + -- get the mario state of the player that owns this Lakitu + local m = gMarioStates[np.localIndex] + + -- don't update this Lakitu if it isn't our Lakitu + if m.playerIndex ~= 0 then + -- delete this Lakitu if it's owner isn't active + if not active_player(m, np) then + obj_mark_for_deletion_on_sync(o) + return + end + -- show the Lakitu for other players + cur_obj_unhide() + + -- determine whether Lakitu should blink + o.oLakituBlinkTimer = obj_update_blinking(o, o.oLakituBlinkTimer, 20, 40, 4) + return + else + -- the local player cannot see it's own Lakitu + cur_obj_hide() + end + + -- set the Lakitu position to the camera position of that player + o.oPosX = gLakituState.curPos.x + o.oPosY = gLakituState.curPos.y + o.oPosZ = gLakituState.curPos.z + + -- look at Mario + o.oHomeX = gLakituState.curFocus.x + o.oHomeZ = gLakituState.curFocus.z + + o.oFaceAngleYaw = cur_obj_angle_to_home() + o.oFaceAnglePitch = atan2s(cur_obj_lateral_dist_to_home(), o.oPosY - gLakituState.curFocus.y) + + -- send the current state of our Lakitu to other players if the area sync is valild + if is_current_area_sync_valid() then + network_send_object(o, false) + end +end + +local bhvPlayerLakitu = hook_behavior(nil, OBJ_LIST_DEFAULT, true, bhv_custom_lakitu_init, bhv_custom_lakitu) + +-- spawn the local player's Lakitu when the area's sync state is valid (every time the player warps areas) +local function update_lakitu() + -- spawn Lakitu with our custom Lakitu behavior and the default Lakitu model; and mark it as a sync object + spawn_sync_object(bhvPlayerLakitu, E_MODEL_LAKITU, 0, 0, 0, function(o) + -- save the global id of the player that owns this Lakitu + o.oLakituOwner = gNetworkPlayers[0].globalIndex + end) +end + +hook_event(HOOK_ON_SYNC_VALID, update_lakitu)