-- Cammy - An advanced camera mod for SM64CoopDX. CAMMY_SM64CIRCLE = 0x10000 -- a full circle in super mario 64. The actual number is 65536. CAMMY_CAMERA_TOP_BOUND = CAMMY_SM64CIRCLE/4 - 16.01 -- how far up the camera can be rotated. CAMMY_CAMERA_BOTTOM_BOUND = -CAMMY_SM64CIRCLE/4 + 16.01 -- how far down the camera can be rotated. local distances = {} local lastEvent _G.Cammy = { Camera = { -- Properties Enabled = true, -- Whether the camera is overriding the original game's camera. Position = { --Not the position of the camera itself; instead, it's the position of the pivot point. x = 0, y = 0, z = 0 }, Rotation = { --The rotation around the pivot point. x = 0, y = 0, z = 0, }, Distance = 0, -- The distance from the pivot point. DoCollision = false, -- Whether the camera should collide with walls. CollisionPrecision = 1, -- How many extra sets of 4 raycasts should be fired between the initial 4. CollisionRadius = 30, -- The Size of the Collision Radius. DoAutoDisable = false, -- Whether the camera should automatically be disabled in situations where it would otherwise softlock the game. -- Read-Only, meant to be overwritten by the library itself and should not be messed with. realPosition = { --The actual position of the camera. x = 0, y = 0, z = 0, }, id = 0, -- unused for now. active = true, -- whether the camera is currently allowed to override the game camera. oldDistance = 0 -- the distance that the camera used to be at. } } local DEACTIVATE_ACTIONS = { ACT_READING_NPC_DIALOG, ACT_READING_AUTOMATIC_DIALOG, ACT_WAITING_FOR_DIALOG, ACT_IN_CANNON } local CUTSCENES = { CUTSCENE_ENTER_CANNON, CUTSCENE_ENTER_PAINTING, CUTSCENE_DEATH_EXIT, CUTSCENE_INTRO_PEACH, CUTSCENE_DANCE_ROTATE, CUTSCENE_ENTER_BOWSER_ARENA, CUTSCENE_0F_UNUSED, CUTSCENE_UNUSED_EXIT, CUTSCENE_PREPARE_CANNON, CUTSCENE_UNLOCK_KEY_DOOR, CUTSCENE_STANDING_DEATH, CUTSCENE_DEATH_ON_STOMACH, CUTSCENE_DEATH_ON_BACK, CUTSCENE_QUICKSAND_DEATH, CUTSCENE_SUFFOCATION_DEATH, CUTSCENE_EXIT_BOWSER_SUCC, CUTSCENE_EXIT_BOWSER_DEATH, CUTSCENE_WATER_DEATH, CUTSCENE_EXIT_PAINTING_SUCC, CUTSCENE_CAP_SWITCH_PRESS, CUTSCENE_DIALOG, CUTSCENE_RACE_DIALOG, CUTSCENE_ENTER_PYRAMID_TOP, CUTSCENE_DANCE_FLY_AWAY, CUTSCENE_DANCE_CLOSEUP, CUTSCENE_KEY_DANCE, CUTSCENE_SSL_PYRAMID_EXPLODE, CUTSCENE_EXIT_SPECIAL_SUCC, CUTSCENE_NONPAINTING_DEATH, CUTSCENE_ENDING, CUTSCENE_STAR_SPAWN, CUTSCENE_GRAND_STAR, CUTSCENE_DANCE_DEFAULT, CUTSCENE_RED_COIN_STAR_SPAWN, CUTSCENE_END_WAVING, CUTSCENE_CREDITS, CUTSCENE_EXIT_WATERFALL, CUTSCENE_EXIT_FALL_WMOTR, CUTSCENE_PALETTE_EDITOR, } local CAMERA_EVENTS = { -- Generic events where the camera should be disabled. CAM_EVENT_CANNON } local EDGE_CASES = { -- specific scenarios that require workarounds function () local m = gMarioStates[0] if m.statusForCamera.cameraEvent == CAM_EVENT_BOWSER_INIT and lastEvent ~= CAM_EVENT_BOWSER_JUMP then return true end return false end } local function reset_camera_positions() -- A camera fix for specific situations. local m = gMarioStates[0] vec3f_copy(m.area.camera.pos, m.pos) vec3f_copy(m.area.camera.focus, m.pos) end local function check_edge_cases() for i, v in ipairs(EDGE_CASES) do if v() then return true end end return false end local function check_value_existence(table, value) for i, v in ipairs(table) do if v == value then return true end end return false end local function get_smallest_of_table(table) local smallest = math.huge for i, v in ipairs(table) do smallest = minf(smallest, v) end return smallest end local function get_collision_direction(x, y, z) return { x = coss(x) * sins(y), y = -sins(x), z = coss(x) * coss(y), } end local function fire_extra_ray_in_direction(x, y, distance) local collisionDirection = get_collision_direction( Cammy.Camera.Rotation.x - x * sins(Cammy.Camera.Rotation.y) - y * coss(Cammy.Camera.Rotation.y), Cammy.Camera.Rotation.y - (x * coss(Cammy.Camera.Rotation.x)) + y * sins(Cammy.Camera.Rotation.x), Cammy.Camera.Rotation.z - x * sins(Cammy.Camera.Rotation.x) * coss(Cammy.Camera.Rotation.y) + y * coss(Cammy.Camera.Rotation.x) * sins(Cammy.Camera.Rotation.y) ) local collision = collision_find_surface_on_ray(Cammy.Camera.Position.x, Cammy.Camera.Position.y, Cammy.Camera.Position.z, collisionDirection.x * distance, collisionDirection.y * distance, collisionDirection.z * distance) local collisionDistance = vec3f_get_dist_and_angle(Cammy.Camera.Position, collision.hitPos) if collision.surface then table.insert(distances, collisionDistance) end end ---@description Detects whether the camera should be disabled depending on the context. ---@return boolean function Cammy.detect_camera_context() local m = gMarioStates[0] if get_first_person_enabled() then return true end if check_value_existence(DEACTIVATE_ACTIONS, m.action) then return true end if check_value_existence(CUTSCENES, get_cutscene_from_mario_status(m.area.camera)) then return true end if check_value_existence(CAMERA_EVENTS, m.statusForCamera.cameraEvent) then return true end if check_edge_cases() then return true end return false end ---@description Sets up the camera and prepares relevant settings. Run this once when the player loads in. function Cammy.initalize_camera() local m = gMarioStates[0] camera_config_enable_free_cam(false) camera_config_enable_collisions(false) camera_freeze() vec3f_zero(gLakituState.pos) vec3f_set(gLakituState.focus, 0, 0, -1) vec3f_zero(Cammy.Camera.Position) vec3f_zero(Cammy.Camera.Rotation) end ---@description Updates the Camera Transforms and ensures the camera is at its proper place. function Cammy.update_camera() if Cammy.Camera.active and Cammy.Camera.Enabled then local m = gMarioStates[0] local angleFoc = { x = 0, y = 0, z = 0 } gLakituState.mode = CAMERA_MODE_RADIAL vec3f_set_dist_and_angle(Cammy.Camera.Position, angleFoc, 100, Cammy.Camera.Rotation.x, Cammy.Camera.Rotation.y + CAMMY_SM64CIRCLE/2) vec3f_set_dist_and_angle(Cammy.Camera.Position, Cammy.Camera.realPosition, Cammy.Camera.Distance, -Cammy.Camera.Rotation.x, Cammy.Camera.Rotation.y) if Cammy.Camera.Distance > 0 then Cammy.fix_camera_collision() end vec3f_copy(gLakituState.focus, angleFoc) vec3f_set(gLakituState.pos, Cammy.Camera.realPosition.x, Cammy.Camera.realPosition.y, Cammy.Camera.realPosition.z) vec3f_set(m.area.camera.pos, Cammy.Camera.realPosition.x, Cammy.Camera.realPosition.y, Cammy.Camera.realPosition.z) vec3f_copy(m.area.camera.focus, angleFoc) m.area.camera.yaw = Cammy.Camera.Rotation.y gFirstPersonCamera.pitch = Cammy.Camera.Rotation.x gLakituState.roll = Cammy.Camera.Rotation.z gLakituState.oldPitch = Cammy.Camera.Rotation.x gFirstPersonCamera.pitch = Cammy.Camera.Rotation.x set_camera_mode_fixed(m.area.camera, Cammy.Camera.realPosition.x, Cammy.Camera.realPosition.y, Cammy.Camera.realPosition.z) vec3s_copy(m.statusForCamera.faceAngle, Cammy.Camera.Rotation) update_mario_sound_and_camera(m) end end ---@description Rotates the camera and updates the camera to match. ---@param x integer ---@param y integer ---@param z integer function Cammy.rotate_camera(x, y, z) Cammy.Camera.Rotation.x = math.clamp(x, CAMMY_CAMERA_BOTTOM_BOUND, CAMMY_CAMERA_TOP_BOUND) Cammy.Camera.Rotation.y = y Cammy.Camera.Rotation.z = z Cammy.update_camera() end ---@description Positions the camera and updates the camera to match. ---@param x integer ---@param y integer ---@param z integer function Cammy.set_camera_position(x, y, z) Cammy.Camera.Position.x = x Cammy.Camera.Position.y = y Cammy.Camera.Position.z = z Cammy.update_camera() end ---@description Sets the distance of the camera and updates the camera to match. ---@param distance integer function Cammy.set_camera_distance(distance) Cammy.oldDistance = Cammy.Camera.Distance Cammy.Camera.Distance = distance end ---@description fires raycasts to move the camera closer if it's in a wall. function Cammy.fix_camera_collision() --initial camera positioning. distances = {} local baseCollisionDirection = get_collision_direction(Cammy.Camera.Rotation.x, Cammy.Camera.Rotation.y, Cammy.Camera.Rotation.z) local rayLength = (Cammy.Camera.Distance + Cammy.Camera.CollisionRadius) local baseCollision = collision_find_surface_on_ray(Cammy.Camera.Position.x, Cammy.Camera.Position.y, Cammy.Camera.Position.z, baseCollisionDirection.x * rayLength, baseCollisionDirection.y * rayLength, baseCollisionDirection.z * rayLength) local baseCollisionResult = baseCollision.hitPos local baseCollisionDistance = vec3f_get_dist_and_angle(Cammy.Camera.Position, baseCollisionResult) - Cammy.Camera.CollisionRadius local baseNormal if baseCollision.surface then baseNormal = baseCollision.surface.normal end if baseNormal then FloorTan = { x = baseNormal.x / baseNormal.y, z = baseNormal.z / baseNormal.y } end --fix collisions around the radius. local angleDifference = atan2s(rayLength - Cammy.Camera.CollisionRadius, Cammy.Camera.CollisionRadius) local distanceCorrection = math.sqrt((rayLength - Cammy.Camera.CollisionRadius) ^ 2 + Cammy.Camera.CollisionRadius ^ 2) local lengthDifference = distanceCorrection - (rayLength - Cammy.Camera.CollisionRadius) local radiusCheckPasses = (4 + 4 * Cammy.Camera.CollisionPrecision) local radiusCheckAngle = 0 for i = 1, radiusCheckPasses, 1 do fire_extra_ray_in_direction(angleDifference * sins(radiusCheckAngle), angleDifference * coss(radiusCheckAngle), distanceCorrection) radiusCheckAngle = radiusCheckAngle + CAMMY_SM64CIRCLE/radiusCheckPasses end if #distances > 0 then vec3f_set_dist_and_angle(Cammy.Camera.Position, Cammy.Camera.realPosition, get_smallest_of_table(distances) + lengthDifference, -Cammy.Camera.Rotation.x, Cammy.Camera.Rotation.y) else vec3f_set_dist_and_angle(Cammy.Camera.Position, Cammy.Camera.realPosition, baseCollisionDistance, -Cammy.Camera.Rotation.x, Cammy.Camera.Rotation.y) end end hook_event(HOOK_MARIO_UPDATE, function (m) if m.playerIndex == 0 then if Cammy.Camera.Enabled then if Cammy.detect_camera_context() and Cammy.Camera.DoAutoDisable then camera_unfreeze() if Cammy.Camera.active then reset_camera_positions() end Cammy.Camera.active = false else camera_freeze() Cammy.Camera.active = true end else camera_unfreeze() end lastEvent = m.statusForCamera.cameraEvent end end) hook_event(HOOK_ON_SET_CAMERA_MODE, function (c, mode) if Cammy.Camera.Enabled and Cammy.Camera.active then return false end end) hook_event(HOOK_ON_WARP, function (...) Cammy.Camera.active = false end)