-- name: .Day Night Cycle DX -- incompatible: light -- description: Day Night Cycle DX v2.0\nBy \\#ec7731\\Agent X\n\n\\#dcdcdc\\This mod adds a fully featured day night cycle system with night, sunrise, day and sunset to sm64coopdx. Days last 24 minutes and you can switch to and from 24 hour time with /ampm\n\nSpecial thanks to \\#00ffff\\AngelicMiracles \\#dcdcdc\\for the sunset, sunrise and night time skyboxes -- deluxe: true --- @diagnostic disable: undefined-global if SM64COOPDX_VERSION == nil then return end SECOND = 30 MINUTE = SECOND * 60 HOUR_SUNRISE_START = 4 HOUR_SUNRISE_END = 5 HOUR_SUNRISE_DURATION = HOUR_SUNRISE_END - HOUR_SUNRISE_START HOUR_SUNSET_START = 19 HOUR_SUNSET_END = 20 HOUR_SUNSET_DURATION = HOUR_SUNSET_END - HOUR_SUNSET_START HOUR_DAY_START = 6 HOUR_NIGHT_START = 21 local DIR_DARK = 0.6 local DIR_BRIGHT = 1 local COLOR_NIGHT = { r = 70, g = 75, b = 100 } local COLOR_SUNRISE = { r = 255, g = 255, b = 200 } local COLOR_DAY = { r = 255, g = 255, b = 255 } local COLOR_SUNSET = { r = 255, g = 155, b = 100 } local FOG_COLOR_NIGHT = { r = 30, g = 30, b = 50 } local COLOR_DISPLAY_DARK = { r = 48, g = 90, b = 200 } local COLOR_DISPLAY_BRIGHT = { r = 255, g = 255, b = 80 } local SKYBOX_SCALE = 200 local REAL_MINUTE = 1 / 60 gGlobalSyncTable.dncEnabled = true local dncDisplayTime = true -- localize functions to improve performance local network_is_moderator,network_is_server,set_override_envfx,set_lighting_dir,set_lighting_color,get_skybox,obj_get_first_with_behavior_id,spawn_non_sync_object,obj_scale,clampf,djui_hud_set_resolution,djui_hud_set_font,hud_is_hidden,djui_hud_get_screen_width,djui_hud_measure_text,djui_hud_get_screen_height,djui_hud_set_color,djui_hud_print_text,djui_chat_message_create,math_floor = network_is_moderator,network_is_server,set_override_envfx,set_lighting_dir,set_lighting_color,get_skybox,obj_get_first_with_behavior_id,spawn_non_sync_object,obj_scale,clampf,djui_hud_set_resolution,djui_hud_set_font,hud_is_hidden,djui_hud_get_screen_width,djui_hud_measure_text,djui_hud_get_screen_height,djui_hud_set_color,djui_hud_print_text,djui_chat_message_create,math.floor --- @param enable boolean --- @return nil --- Globally enables or disables Day Night Cycle local function enable_day_night_cycle(enable) if not network_is_server() and not network_is_moderator() then return end if type(enable) ~= "boolean" then return end gGlobalSyncTable.dncEnabled = enable djui_popup_create("Day Night Cycle has been " .. if_then_else(gGlobalSyncTable.dncEnabled, "enabled.", "disabled."), 2) end --- Returns whether or not DNC will display the time on the HUD local function get_display_time() return dncDisplayTime end --- @param enable boolean --- @return nil --- Sets whether or not DNC will display the time on the HUD local function set_display_time(enable) if type(enable) ~= "boolean" then return end dncDisplayTime = enable end local function update() if not gGlobalSyncTable.dncEnabled then set_override_envfx(-1) set_lighting_dir(1, 0) set_lighting_dir(2, 0) set_lighting_color(0, 255) set_lighting_color(1, 255) set_lighting_color(2, 255) set_vertex_color(0, 255) set_vertex_color(1, 255) set_vertex_color(2, 255) set_fog_color(0, 255) set_fog_color(1, 255) set_fog_color(2, 255) set_fog_intensity(1) return end time_tick() handle_night_music() -- spawn skyboxes local skybox = get_skybox() if obj_get_first_with_behavior_id(bhvSkybox) == nil and skybox ~= -1 then if show_day_night_cycle() then -- spawn day, sunset and night skyboxes for i = 0, 2 do local model = 0 if i == 0 then model = gVanillaSkyboxModels[skybox] or E_MODEL_SKYBOX_OCEAN_SKY else model = if_then_else(i == 1, E_MODEL_SKYBOX_SUNSET, E_MODEL_SKYBOX_NIGHT) end spawn_non_sync_object( bhvSkybox, model, 0, 0, 0, --- @param o Object function(o) o.oBehParams2ndByte = i obj_scale(o, SKYBOX_SCALE + 1 * i) end ) end else -- spawn static skybox spawn_non_sync_object( bhvSkybox, gVanillaSkyboxModels[skybox] or E_MODEL_SKYBOX_OCEAN_SKY, 0, 0, 0, --- @param o Object function(o) o.oBehParams2ndByte = 0 obj_scale(o, SKYBOX_SCALE) end ) end end local minutes = (gGlobalSyncTable.time / MINUTE) % 24 local actSelector = obj_get_first_with_behavior_id(id_bhvActSelector) if actSelector == nil and (show_day_night_cycle() or in_vanilla_level(LEVEL_DDD) or in_vanilla_level(LEVEL_TTM)) then -- DDD has a subarea connected by instant warps and TTM has a subarea with sunlight coming through it -- blizzard effect at night in snow levels if (minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_END) and gMarioStates[0].area.terrainType == TERRAIN_SNOW then set_override_envfx(ENVFX_SNOW_BLIZZARD) else set_override_envfx(-1) end -- calculate lighting color local color = COLOR_DAY if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then color = color_lerp(COLOR_NIGHT, COLOR_SUNRISE, (minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION) elseif minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then color = color_lerp(COLOR_SUNRISE, COLOR_DAY, (minutes - HOUR_SUNRISE_END) / HOUR_SUNRISE_DURATION) elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then color = color_lerp(COLOR_DAY, COLOR_SUNSET, (minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION) elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then color = color_lerp(COLOR_SUNSET, COLOR_NIGHT, (minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION) elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then color = COLOR_NIGHT elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then color = COLOR_DAY end -- calculate fog color local fogColor = COLOR_DAY if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then fogColor = color_lerp(FOG_COLOR_NIGHT, COLOR_SUNRISE, (minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION) elseif minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then fogColor = color_lerp(COLOR_SUNRISE, COLOR_DAY, (minutes - HOUR_SUNRISE_END) / HOUR_SUNRISE_DURATION) elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then fogColor = color_lerp(COLOR_DAY, COLOR_SUNSET, (minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION) elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then fogColor = color_lerp(COLOR_SUNSET, FOG_COLOR_NIGHT, (minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION) elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then fogColor = FOG_COLOR_NIGHT elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then fogColor = COLOR_DAY end -- calculate lighting direction local dir = DIR_BRIGHT if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then dir = lerp(DIR_DARK, DIR_BRIGHT, clampf((minutes - HOUR_SUNRISE_START) / (HOUR_SUNRISE_DURATION), 0, 1)) elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_NIGHT_START then dir = lerp(DIR_BRIGHT, DIR_DARK, clampf((minutes - HOUR_SUNSET_START) / (HOUR_NIGHT_START - HOUR_SUNSET_START), 0, 1)) elseif minutes < HOUR_SUNRISE_START or minutes > HOUR_NIGHT_START then dir = DIR_DARK elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then dir = DIR_BRIGHT end -- calculate fog intensity local intensity = 1 if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then intensity = lerp(1.02, 1, clampf((minutes - HOUR_SUNRISE_START) / (HOUR_SUNRISE_DURATION), 0, 1)) elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_NIGHT_START then intensity = lerp(1, 1.02, clampf((minutes - HOUR_SUNSET_START) / (HOUR_NIGHT_START - HOUR_SUNSET_START), 0, 1)) elseif minutes < HOUR_SUNRISE_START or minutes > HOUR_NIGHT_START then intensity = 1.02 elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then intensity = 1 end set_lighting_dir(1, -(1 - dir)) set_lighting_dir(2, -(1 - dir)) set_lighting_color(0, color.r) set_lighting_color(1, color.g) set_lighting_color(2, color.b) set_vertex_color(0, color.r) set_vertex_color(1, color.g) set_vertex_color(2, color.b) set_fog_color(0, fogColor.r) set_fog_color(1, fogColor.g) set_fog_color(2, fogColor.b) set_fog_intensity(intensity) else set_override_envfx(-1) set_lighting_dir(1, 0) set_lighting_dir(2, 0) set_lighting_color(0, 255) set_lighting_color(1, 255) set_lighting_color(2, 255) set_vertex_color(0, 255) set_vertex_color(1, 255) set_vertex_color(2, 255) set_fog_color(0, 255) set_fog_color(1, 255) set_fog_color(2, 255) set_fog_intensity(1) end end local function on_hud_render_behind() if not gGlobalSyncTable.dncEnabled or not dncDisplayTime then return end -- api checks if obj_get_first_with_behavior_id(id_bhvActSelector) ~= nil or gNetworkPlayers[0].currActNum == 99 then return end -- game checks djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_font(FONT_TINY) local scale = 1 local text = get_time_string() local hidden = hud_is_hidden() local x = if_then_else(hidden, (djui_hud_get_screen_width() * 0.5) - (djui_hud_measure_text(text) * (0.5 * scale)), 24) local y = if_then_else(hidden, (djui_hud_get_screen_height() - 20), 32) local minutes = (gGlobalSyncTable.time / MINUTE) % 24 -- outlined text djui_hud_set_color(0, 0, 0, 255) djui_hud_print_text(text, x - 1, y, scale) djui_hud_print_text(text, x + 1, y, scale) djui_hud_print_text(text, x, y - 1, scale) djui_hud_print_text(text, x, y + 1, scale) if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then local color = color_lerp(COLOR_DISPLAY_DARK, COLOR_DISPLAY_BRIGHT, (minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION) djui_hud_set_color(color.r, color.g, color.b, 255) elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then local color = color_lerp(COLOR_DISPLAY_BRIGHT, COLOR_DISPLAY_DARK, (minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION) djui_hud_set_color(color.r, color.g, color.b, 255) elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then djui_hud_set_color(COLOR_DISPLAY_DARK.r, COLOR_DISPLAY_DARK.g, COLOR_DISPLAY_DARK.b, 255) elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_END then djui_hud_set_color(COLOR_DISPLAY_BRIGHT.r, COLOR_DISPLAY_BRIGHT.g, COLOR_DISPLAY_BRIGHT.b, 255) end djui_hud_print_text(text, x, y, scale) end local function on_level_init() if not gGlobalSyncTable.dncEnabled then return end playingNightMusic = false if gNetworkPlayers[0].currLevelNum == LEVEL_CASTLE_GROUNDS and gNetworkPlayers[0].currActNum == 99 then if gMarioStates[0].action ~= ACT_END_WAVING_CUTSCENE then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * (HOUR_SUNSET_START - 0.75)) gGlobalSyncTable.timeScale = 1 else gGlobalSyncTable.time = (get_day_count() + 1) * (MINUTE * 24) + (MINUTE * HOUR_SUNRISE_END) end end end local function on_warp() if not gGlobalSyncTable.dncEnabled then return end if network_is_server() then save_time() end end local function on_exit() if network_is_server() then save_time() end end local function on_set_command(msg) if msg == "" then djui_chat_message_create("/time \\#00ffff\\set\\#ffff00\\ [TIME]\\#dcdcdc\\ to set the time") return true end if msg == "morning" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 6) elseif msg == "day" or msg == "noon" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 12) elseif msg == "night" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 21) elseif msg == "midnight" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) elseif msg == "sunrise" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 5) elseif msg == "sunset" then gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 20) else local amount = tonumber(msg) if amount ~= nil then gGlobalSyncTable.time = amount * SECOND end end djui_chat_message_create("Time set to " .. math_floor(gGlobalSyncTable.time / SECOND)) if network_is_server() then save_time() end end local function on_add_command(msg) local amount = tonumber(msg) if amount == nil then djui_chat_message_create("/time \\#00ffff\\add\\#ffff00\\ [AMOUNT]\\#dcdcdc\\ to add to the time") return end gGlobalSyncTable.time = gGlobalSyncTable.time + (amount * SECOND) djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND)) if network_is_server() then save_time() end end local function on_scale_command(msg) local scale = tonumber(msg) if scale == nil then djui_chat_message_create("/time \\#00ffff\\scale\\#ffff00\\ [SCALE]\\#dcdcdc\\ to scale the rate at which time passes") return end gGlobalSyncTable.timeScale = scale djui_chat_message_create("[Day Night Cycle] Time scale set to " .. scale) if network_is_server() then save_time() end end local function on_query_command() djui_chat_message_create(string.format("Time is %d (%s), day %d", math_floor(gGlobalSyncTable.time / SECOND), get_time_string(), get_day_count())) end local function on_24h_command() use24h = not use24h mod_storage_save_bool("24h", use24h) end local function on_sync_command() djui_chat_message_create("[Day Night Cycle] Attempting to sync in-game time with real world time...") local dateTime = get_date_and_time() gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * dateTime.hour) + (SECOND * dateTime.minute) gGlobalSyncTable.timeScale = REAL_MINUTE if network_is_server() then save_time() end end local function on_time_command(msg) local args = split(msg) local perms = network_is_server() or network_is_moderator() if args[1] == "set" then if not perms then djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time set") else on_set_command(args[2] or "") end elseif args[1] == "add" then if not perms then djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time add") else on_add_command(args[2] or "") end elseif args[1] == "scale" then if not perms then djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time scale") else on_scale_command(args[2] or "") end elseif args[1] == "query" then on_query_command() elseif args[1] == "24h" then on_24h_command() elseif args[1] == "sync" then if not perms then djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time sync") else on_sync_command() end else if not perms then djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to enable or disable Day Night Cycle") else gGlobalSyncTable.dncEnabled = not gGlobalSyncTable.dncEnabled djui_chat_message_create("[Day Night Cycle] Status: " .. on_or_off(gGlobalSyncTable.dncEnabled)) end end return true end _G.dayNightCycleApi = { enable_day_night_cycle = enable_day_night_cycle, get_display_time = get_display_time, set_display_time = set_display_time, get_day_count = get_day_count, get_time_string = get_time_string, get_raw_time = get_raw_time, set_raw_time = set_raw_time, } night_music_register(SEQ_LEVEL_GRASS, "03_level_grass") night_music_register(SEQ_LEVEL_WATER, "05_level_water") night_music_register(SEQ_LEVEL_HOT, "06_level_hot") night_music_register(SEQ_LEVEL_SNOW, "08_level_snow") hook_event(HOOK_UPDATE, update) hook_event(HOOK_ON_HUD_RENDER_BEHIND, on_hud_render_behind) hook_event(HOOK_ON_LEVEL_INIT, on_level_init) hook_event(HOOK_ON_WARP, on_warp) hook_event(HOOK_ON_EXIT, on_exit) hook_chat_command("time", "\\#00ffff\\[set|add|scale|query|24h|sync]\\#7f7f7f\\ (leave blank to toggle Day Night Cycle on or off)", on_time_command)