Update Day Night Cycle DX to v2.1

This commit is contained in:
Agent X 2024-06-30 15:38:31 -04:00
parent dfc55faa5b
commit 06a57eb367
27 changed files with 593 additions and 283 deletions

View file

@ -1,18 +1,25 @@
if SM64COOPDX_VERSION == nil then -- version
local first = false DNC_VERSION = "v2.1"
hook_event(HOOK_ON_LEVEL_INIT, function()
if not first then -- skybox constants
first = true E_MODEL_SKYBOX = smlua_model_util_get_id("dnc_skybox_geo")
play_sound(SOUND_MENU_CAMERA_BUZZ, gMarioStates[0].marioObj.header.gfx.cameraToObject)
djui_chat_message_create("\\#ff7f7f\\Day Night Cycle is no longer supported with sm64ex-coop\nas it uses sm64coopdx exclusive Lua functionality.\n\\#dcdcdc\\To play this mod, try out sm64coopdx at\n\\#7f7fff\\https://sm64coopdx.com") SKYBOX_SCALE = 600
end SKYBOX_DAY = 0
end) SKYBOX_SUNSET = 1
return SKYBOX_NIGHT = 2
end
-- background constants
BACKGROUND_NIGHT = 10
BACKGROUND_SUNRISE = 11
BACKGROUND_SUNSET = 12
BACKGROUND_BELOW_CLOUDS_NIGHT = 13
BACKGROUND_BELOW_CLOUDS_SUNRISE = 14
BACKGROUND_BELOW_CLOUDS_SUNSET = 15
-- time constants -- time constants
SECOND = 30 SECOND = 30 -- how many frames are in 1 second
MINUTE = SECOND * 60 MINUTE = SECOND * 60 -- how many frames are in 1 minutes
HOUR_SUNRISE_START = 4 HOUR_SUNRISE_START = 4
HOUR_SUNRISE_END = 5 HOUR_SUNRISE_END = 5
@ -31,40 +38,32 @@ REAL_MINUTE = 1 / 60
DIR_DARK = 0.6 DIR_DARK = 0.6
DIR_BRIGHT = 1 DIR_BRIGHT = 1
-- color constants -- fog intensity constants
COLOR_NIGHT = { r = 70, g = 75, b = 100 } FOG_INTENSITY_NORMAL = 1.0
COLOR_SUNRISE = { r = 255, g = 255, b = 200 } FOG_INTENSITY_DENSE = 1.02
COLOR_DAY = { r = 255, g = 255, b = 255 }
COLOR_SUNSET = { r = 255, g = 155, b = 100 }
FOG_COLOR_NIGHT = { r = 30, g = 30, b = 50 } -- colors
COLOR_NIGHT = { r = 90, g = 100, b = 130 }
COLOR_AMBIENT_NIGHT = { r = 60, g = 70, b = 110 }
COLOR_SUNRISE = { r = 255, g = 250, b = 150 }
COLOR_AMBIENT_SUNRISE = { r = 200, g = 200, b = 255 }
COLOR_DAY = { r = 255, g = 255, b = 255 }
COLOR_AMBIENT_DAY = { r = 255, g = 255, b = 255 }
COLOR_SUNSET = { r = 255, g = 140, b = 80 }
COLOR_AMBIENT_SUNSET = { r = 255, g = 140, b = 160 }
FOG_COLOR_NIGHT = { r = 5, g = 5, b = 10 }
COLOR_DISPLAY_DARK = { r = 48, g = 90, b = 200 } COLOR_DISPLAY_DARK = { r = 48, g = 90, b = 200 }
COLOR_DISPLAY_BRIGHT = { r = 255, g = 255, b = 80 } COLOR_DISPLAY_BRIGHT = { r = 255, g = 255, b = 80 }
-- skybox constants -- hook constants
SKYBOX_SCALE = 200 DNC_HOOK_SET_LIGHTING_COLOR = 0
DNC_HOOK_SET_AMBIENT_LIGHTING_COLOR = 1
SKYBOX_DAY = 0 DNC_HOOK_SET_LIGHTING_DIR = 2
SKYBOX_SUNSET = 1 DNC_HOOK_SET_FOG_COLOR = 3
SKYBOX_NIGHT = 2 DNC_HOOK_SET_FOG_INTENSITY = 4
DNC_HOOK_SET_DISPLAY_TIME_COLOR = 5
-- standard skyboxes DNC_HOOK_SET_DISPLAY_TIME_POS = 6
E_MODEL_SKYBOX_OCEAN_SKY = smlua_model_util_get_id("skybox_ocean_sky_geo") DNC_HOOK_DELETE_AT_DARK = 7
E_MODEL_SKYBOX_FLAMING_SKY = smlua_model_util_get_id("skybox_flaming_sky_geo") DNC_HOOK_SET_TIME = 8
E_MODEL_SKYBOX_UNDERWATER_CITY = smlua_model_util_get_id("skybox_underwater_city_geo")
E_MODEL_SKYBOX_BELOW_CLOUDS = smlua_model_util_get_id("skybox_below_clouds_geo")
E_MODEL_SKYBOX_SNOW_MOUNTAINS = smlua_model_util_get_id("skybox_snow_mountains_geo")
E_MODEL_SKYBOX_DESERT = smlua_model_util_get_id("skybox_desert_geo")
E_MODEL_SKYBOX_HAUNTED = smlua_model_util_get_id("skybox_haunted_geo")
E_MODEL_SKYBOX_GREEN_SKY = smlua_model_util_get_id("skybox_green_sky_geo")
E_MODEL_SKYBOX_ABOVE_CLOUDS = smlua_model_util_get_id("skybox_above_clouds_geo")
E_MODEL_SKYBOX_PURPLE_SKY = smlua_model_util_get_id("skybox_purple_sky_geo")
E_MODEL_SKYBOX_SUNRISE = smlua_model_util_get_id("skybox_sunrise_geo")
E_MODEL_SKYBOX_SUNSET = smlua_model_util_get_id("skybox_sunset_geo")
E_MODEL_SKYBOX_NIGHT = smlua_model_util_get_id("skybox_night_geo")
-- below clouds skyboxes
E_MODEL_SKYBOX_BELOW_CLOUDS_NIGHT = smlua_model_util_get_id("skybox_below_clouds_night_geo")
E_MODEL_SKYBOX_BELOW_CLOUDS_SUNRISE = smlua_model_util_get_id("skybox_below_clouds_sunrise_geo")
E_MODEL_SKYBOX_BELOW_CLOUDS_SUNSET = smlua_model_util_get_id("skybox_below_clouds_sunset_geo")

View file

@ -1,59 +1,121 @@
if SM64COOPDX_VERSION == nil then return end if SM64COOPDX_VERSION == nil then return end
-- localize functions to improve performance -- localize functions to improve performance
local play_sound,table_insert,get_skybox,level_is_vanilla_level,math_floor,math_ceil = play_sound,table.insert,get_skybox,level_is_vanilla_level,math.floor,math.ceil local string_format,table_insert,math_floor,math_ceil,level_is_vanilla_level,djui_hud_get_color,djui_hud_set_color,djui_hud_print_text,type,obj_get_first_with_behavior_id = string.format,table.insert,math.floor,math.ceil,level_is_vanilla_level,djui_hud_get_color,djui_hud_set_color,djui_hud_print_text,type,obj_get_first_with_behavior_id
--- @param cond boolean
--- Human readable ternary operator
function if_then_else(cond, ifTrue, ifFalse) function if_then_else(cond, ifTrue, ifFalse)
if cond then return ifTrue end if cond then return ifTrue end
return ifFalse return ifFalse
end end
--- @param s string
--- Splits a string into a table by spaces
function split(s) function split(s)
local result = {} local result = {}
for match in (s):gmatch(string.format("[^%s]+", " ")) do for match in (s):gmatch(string_format("[^%s]+", " ")) do
table_insert(result, match) table_insert(result, match)
end end
return result return result
end end
function lerp(a, b, t) return a * (1 - t) + b * t end --- @param x number
--- @return integer
--- Rounds up or down depending on the decimal position of `x`
function math_round(x)
return if_then_else(x - math_floor(x) >= 0.5, math_ceil(x), math_floor(x))
end
--- @param a number
--- @param b number
--- @param t number
--- Linearly interpolates between two points using a delta
function lerp(a, b, t)
return a * (1 - t) + b * t
end
--- @param a number
--- @param b number
--- @param t number
--- Linearly interpolates between two points using a delta but rounds the final value
function lerp_round(a, b, t)
return math_round(lerp(a, b, t))
end
--- @param a number
--- @param b number
--- @param t number
--- Linearly interpolates between two points using a delta but ceils the final value
function lerp_ceil(a, b, t)
return math_ceil(lerp(a, b, t))
end
--- @param a Color --- @param a Color
--- @param b Color --- @param b Color
--- @return Color --- @return Color
--- Linearly interpolates between two colors using a delta
function color_lerp(a, b, t) function color_lerp(a, b, t)
return { return {
r = lerp(a.r, b.r, t), r = lerp_round(a.r, b.r, t),
g = lerp(a.g, b.g, t), g = lerp_round(a.g, b.g, t),
b = lerp(a.b, b.b, t) b = lerp_round(a.b, b.b, t)
} }
end end
--- @param priority integer
--- @param seqId SeqId
function SEQUENCE_ARGS(priority, seqId) function SEQUENCE_ARGS(priority, seqId)
return ((priority << 8) | seqId) return ((priority << 8) | seqId)
end end
--- @param value boolean
--- Returns an on or off string depending on value
function on_or_off(value) function on_or_off(value)
if value then return "\\#00ff00\\ON" end if value then return "\\#00ff00\\ON" end
return "\\#ff0000\\OFF" return "\\#ff0000\\OFF"
end end
function show_day_night_cycle() --- @param levelNum LevelNum
local skybox = get_skybox() --- Returns whether or not the local player is in a vanilla level
return skybox ~= -1 and
skybox ~= BACKGROUND_CUSTOM and
skybox ~= BACKGROUND_FLAMING_SKY and
skybox ~= BACKGROUND_GREEN_SKY and
skybox ~= BACKGROUND_HAUNTED and
skybox ~= BACKGROUND_PURPLE_SKY and
skybox ~= BACKGROUND_UNDERWATER_CITY
end
function in_vanilla_level(levelNum) function in_vanilla_level(levelNum)
return gNetworkPlayers[0].currLevelNum == levelNum and level_is_vanilla_level(levelNum) return gNetworkPlayers[0].currLevelNum == levelNum and level_is_vanilla_level(levelNum)
end end
function lerp_round(a, b, t) --- @param message string
local x = lerp(a, b, t) --- @param x number
return x >= 0 and math_floor(x + 0.5) or math_ceil(x - 0.5) --- @param y number
--- @param scale number
--- @param outlineBrightness number
--- Prints outlined DJUI HUD text
function djui_hud_print_outlined_text(message, x, y, scale, outlineBrightness)
local color = djui_hud_get_color()
djui_hud_set_color(color.r * outlineBrightness, color.g * outlineBrightness, color.b * outlineBrightness, color.a)
djui_hud_print_text(message, x - 1, y, scale)
djui_hud_print_text(message, x + 1, y, scale)
djui_hud_print_text(message, x, y - 1, scale)
djui_hud_print_text(message, x, y + 1, scale)
djui_hud_set_color(color.r, color.g, color.b, color.a)
djui_hud_print_text(message, x, y, scale)
end
--- @param table table
--- Clones a table out of an existing one, useful for making referenceless tables
function table_clone(table)
local clone = {}
for key, value in pairs(table) do
if type(value) == "table" then
clone[key] = table_clone(value) -- recursive call for nested tables
else
clone[key] = value
end
end
return clone
end
function check_common_hud_render_cancels()
local action = gMarioStates[0].action
return obj_get_first_with_behavior_id(id_bhvActSelector) ~= nil or
gNetworkPlayers[0].currActNum == 99 or
action == ACT_END_PEACH_CUTSCENE or action == ACT_END_WAVING_CUTSCENE or action == ACT_CREDITS_CUTSCENE
end end

Binary file not shown.

View file

@ -1,19 +1,18 @@
function delete_at_dark() end
if SM64COOPDX_VERSION == nil then return end
gGlobalSyncTable.time = 0
gGlobalSyncTable.timeScale = 1
local sNightSequences = {} local sNightSequences = {}
-- localize functions to improve performance -- localize functions to improve performance
local math_floor,network_is_server,djui_hud_is_pause_menu_created,smlua_audio_utils_replace_sequence,fade_volume_scale,set_background_music,obj_mark_for_deletion = math.floor,network_is_server,djui_hud_is_pause_menu_created,smlua_audio_utils_replace_sequence,fade_volume_scale,set_background_music,obj_mark_for_deletion local mod_storage_remove,mod_storage_load_bool,math_floor,mod_storage_save_number,mod_storage_load_number,type,error,network_is_moderator,network_is_server,string_format,djui_hud_is_pause_menu_created,smlua_audio_utils_replace_sequence,fade_volume_scale,set_background_music,obj_mark_for_deletion = mod_storage_remove,mod_storage_load_bool,math.floor,mod_storage_save_number,mod_storage_load_number,type,error,network_is_moderator,network_is_server,string.format,djui_hud_is_pause_menu_created,smlua_audio_utils_replace_sequence,fade_volume_scale,set_background_music,obj_mark_for_deletion
-- purge legacy fields
mod_storage_remove("ampm") mod_storage_remove("ampm")
use24h = mod_storage_load_bool("24h") or false mod_storage_remove("night-music")
use24h = mod_storage_load_bool("24h")
local savedInMenu = false local savedInMenu = false
local autoSaveTimer = 0 local autoSaveTimer = 0
--- @type boolean
playNightMusic = if_then_else(mod_storage_load("night_music") == nil, true, mod_storage_load_bool("night_music"))
playingNightMusic = false playingNightMusic = false
--- Returns the amount of days that have passed --- Returns the amount of days that have passed
@ -22,6 +21,7 @@ function get_day_count()
end end
function save_time() function save_time()
if gNetworkPlayers[0].currActNum == 99 then return end
mod_storage_save_number("time", gGlobalSyncTable.time) mod_storage_save_number("time", gGlobalSyncTable.time)
print("Saving time to 'day-night-cycle.sav'") print("Saving time to 'day-night-cycle.sav'")
end end
@ -34,14 +34,62 @@ function load_time()
end end
return time return time
end end
gGlobalSyncTable.time = load_time()
--- Returns the time in frames
function get_raw_time()
return gGlobalSyncTable.time
end
--- @param time integer
--- Sets the time in frames
function set_raw_time(time)
if type(time) ~= "number" then
error("set_raw_time: Parameter 'time' must be a number")
return
end
if not network_is_server() and not network_is_moderator() then return end
gGlobalSyncTable.time = time
end
--- Returns the amount of time that has passed in the day in minutes
function get_time_minutes()
return (gGlobalSyncTable.time / MINUTE) % 24
end
--- Returns the time scale
function get_time_scale()
return gGlobalSyncTable.timeScale
end
--- @param scale number
--- Sets the time scale
function set_time_scale(scale)
if type(scale) ~= "number" then
error("set_time_scale: Parameter 'scale' must be a number")
return
end
if not network_is_server() and not network_is_moderator() then return end
gGlobalSyncTable.timeScale = scale
end
--- @param time number
--- @return string --- @return string
--- Returns the properly formatted time string --- Returns the properly formatted time string
function get_time_string() function get_time_string(time)
local minutes = (gGlobalSyncTable.time / MINUTE) % 24 if type(time) ~= "number" then
error("get_time_string: Parameter 'time' must be a number")
if use24h then
return "12:00 AM"
else
return "0:00"
end
end
local minutes = (time / MINUTE) % 24
local formattedMinutes = math_floor(minutes) local formattedMinutes = math_floor(minutes)
local seconds = math_floor(gGlobalSyncTable.time / SECOND) % 60 local seconds = math_floor(time / SECOND) % 60
if not use24h then if not use24h then
if formattedMinutes == 0 then if formattedMinutes == 0 then
@ -51,12 +99,10 @@ function get_time_string()
end end
end end
return math_floor(formattedMinutes) .. ":" .. string.format("%02d", seconds) .. if_then_else(not use24h, if_then_else(minutes < 12, " AM", " PM"), "") return string_format("%d:%02d%s", formattedMinutes, seconds, if_then_else(not use24h, if_then_else(minutes < 12, " AM", " PM"), ""))
end end
function time_tick() function time_tick()
if not network_is_server() then return end
gGlobalSyncTable.time = gGlobalSyncTable.time + gGlobalSyncTable.timeScale gGlobalSyncTable.time = gGlobalSyncTable.time + gGlobalSyncTable.timeScale
-- auto save every 30s -- auto save every 30s
@ -83,15 +129,26 @@ function night_music_register(sequenceId, m64Name)
end end
function handle_night_music() function handle_night_music()
if not show_day_night_cycle() or gNetworkPlayers[0].currActNum == 99 or gMarioStates[0].area == nil then return end if gNetworkPlayers[0].currActNum == 99 or gMarioStates[0].area == nil then return end
local seq = sNightSequences[gMarioStates[0].area.musicParam2] local musicParam = gMarioStates[0].area.musicParam2
if not playNightMusic or not dayNightCycleApi.playNightMusic then
if playingNightMusic then
playingNightMusic = false
fade_volume_scale(SEQ_PLAYER_LEVEL, 127, 1)
set_background_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, musicParam), 0)
end
return
end
local seq = sNightSequences[musicParam]
if seq == nil then return end if seq == nil then return end
fade_volume_scale(0, 127, 1) fade_volume_scale(0, 127, 1)
local minutes = (gGlobalSyncTable.time / MINUTE) % 24 local minutes = get_time_minutes()
if minutes >= HOUR_SUNSET_END + 0.75 and minutes <= HOUR_NIGHT_START then if minutes >= HOUR_SUNSET_END + 0.75 and minutes < HOUR_NIGHT_START then
local threshold = 1 - (minutes - (HOUR_SUNSET_END + 0.75)) * 4 -- multiply by 4 because four quarters make a whole local threshold = 1 - (minutes - (HOUR_SUNSET_END + 0.75)) * 4 -- multiply by 4 because four quarters make a whole
fade_volume_scale(SEQ_PLAYER_LEVEL, threshold * 127, 1) fade_volume_scale(SEQ_PLAYER_LEVEL, threshold * 127, 1)
elseif minutes >= HOUR_SUNRISE_START + 0.75 and minutes <= HOUR_SUNRISE_END then elseif minutes >= HOUR_SUNRISE_START + 0.75 and minutes <= HOUR_SUNRISE_END then
@ -102,33 +159,25 @@ function handle_night_music()
if (minutes >= HOUR_NIGHT_START or minutes < HOUR_SUNRISE_END) and not playingNightMusic then if (minutes >= HOUR_NIGHT_START or minutes < HOUR_SUNRISE_END) and not playingNightMusic then
playingNightMusic = true playingNightMusic = true
fade_volume_scale(SEQ_PLAYER_LEVEL, 127, 1) fade_volume_scale(SEQ_PLAYER_LEVEL, 127, 1)
set_background_music(0, SEQUENCE_ARGS(4, seq), 450) set_background_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, seq), 450)
elseif minutes >= HOUR_SUNRISE_END and minutes < HOUR_NIGHT_START and playingNightMusic then elseif minutes >= HOUR_SUNRISE_END and minutes < HOUR_NIGHT_START and playingNightMusic then
playingNightMusic = false playingNightMusic = false
fade_volume_scale(0, 127, 1) fade_volume_scale(SEQ_PLAYER_LEVEL, 127, 1)
set_background_music(0, SEQUENCE_ARGS(4, gMarioStates[0].area.musicParam2), 0) set_background_music(SEQ_PLAYER_LEVEL, SEQUENCE_ARGS(4, musicParam), 0)
end end
end end
--- @return number --- @param obj Object
--- Returns the time in frames function delete_at_dark(obj)
function get_raw_time() if obj == nil then
return gGlobalSyncTable.time error("delete_at_dark: Parameter 'obj' must be an Object")
end return
end
--- @param time number
--- @return nil
--- Sets the time in frames
function set_raw_time(time)
if type(time) ~= "number" then return end
gGlobalSyncTable.time = time
end
--- @param o Object
function delete_at_dark(o)
local minutes = gGlobalSyncTable.time / MINUTE % 24 local minutes = gGlobalSyncTable.time / MINUTE % 24
if minutes < HOUR_SUNRISE_START or minutes > HOUR_SUNSET_END then local delete = minutes < HOUR_SUNRISE_START or minutes > HOUR_SUNSET_END
obj_mark_for_deletion(o) overrideDelete = dnc_call_hook(DNC_HOOK_DELETE_AT_DARK, obj, delete)
end if overrideDelete ~= nil and type(overrideDelete) == "boolean" then delete = overrideDelete end
if delete then obj_mark_for_deletion(obj) end
end end

Binary file not shown.

Binary file not shown.

View file

@ -1,45 +1,78 @@
-- name: Day Night Cycle DX -- name: Day Night Cycle DX
-- incompatible: light -- incompatible: light day-night-cycle
-- description: Day Night Cycle DX v2.0.1\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 /time 24h\n\nSpecial thanks to \\#00ffff\\AngelicMiracles \\#dcdcdc\\for the sunset, sunrise and night time skyboxes -- description: Day Night Cycle DX v2.1\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. It includes an API and hook system for interfacing with several components of the mod externally. This mod was originally made for sm64ex-coop but has been practically rewritten for sm64coopdx.\n\nDays last 24 minutes and with the /time command, you can get/set the time or change your settings.\n\nThere is also now a new menu in the pause menu for Day Night Cycle DX!\n\nSpecial thanks to \\#00ffff\\AngelicMiracles\\#dcdcdc\\ for the sunset, sunrise and night time skyboxes.\nSpecial thanks to \\#344ee1\\eros71\\#dcdcdc\\ for salvaging\nthe mod files.
-- deluxe: true -- pausable: true
--- @diagnostic disable: undefined-global --- @class Vec2f
--- @field public x number
if SM64COOPDX_VERSION == nil then return end --- @field public y number
gGlobalSyncTable.dncEnabled = true gGlobalSyncTable.dncEnabled = true
local dncDisplayTime = true gGlobalSyncTable.time = if_then_else(network_is_server(), load_time(), HOUR_DAY_START)
gGlobalSyncTable.timeScale = tonumber(mod_storage_load("scale")) or 1.0
local init = true
-- localize functions to improve performance -- 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 local type,math_floor,error,table_insert,get_skybox,set_lighting_dir,set_lighting_color,set_vertex_color,set_fog_color,set_fog_intensity,network_check_singleplayer_pause,network_is_server,obj_get_first_with_behavior_id,spawn_non_sync_object,obj_scale,clampf,set_lighting_color_ambient,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_chat_message_create,tonumber,string_format,mod_storage_save_number,mod_storage_save_bool,get_date_and_time,math_tointeger = type,math.floor,error,table.insert,get_skybox,set_lighting_dir,set_lighting_color,set_vertex_color,set_fog_color,set_fog_intensity,network_check_singleplayer_pause,network_is_server,obj_get_first_with_behavior_id,spawn_non_sync_object,obj_scale,clampf,set_lighting_color_ambient,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_chat_message_create,tonumber,string.format,mod_storage_save_number,mod_storage_save_bool,get_date_and_time,math.tointeger
--- @param enable boolean local sDncHooks = {
--- @return nil [DNC_HOOK_SET_LIGHTING_COLOR] = {},
--- Globally enables or disables Day Night Cycle [DNC_HOOK_SET_AMBIENT_LIGHTING_COLOR] = {},
local function enable_day_night_cycle(enable) [DNC_HOOK_SET_LIGHTING_DIR] = {},
if not network_is_server() and not network_is_moderator() then return end [DNC_HOOK_SET_FOG_COLOR] = {},
if type(enable) ~= "boolean" then return end [DNC_HOOK_SET_FOG_INTENSITY] = {},
gGlobalSyncTable.dncEnabled = enable [DNC_HOOK_SET_DISPLAY_TIME_COLOR] = {},
djui_popup_create("Day Night Cycle has been " .. if_then_else(gGlobalSyncTable.dncEnabled, "enabled.", "disabled."), 2) [DNC_HOOK_SET_DISPLAY_TIME_POS] = {},
[DNC_HOOK_DELETE_AT_DARK] = {},
[DNC_HOOK_SET_TIME] = {}
}
--- @param hookEventType integer
--- @param func function
--- Hooks a function to the Day Night Cycle hook system
local function dnc_hook_event(hookEventType, func)
if type(hookEventType) ~= "number" or math_floor(hookEventType) ~= hookEventType then
error("dnc_hook_event: Parameter 'hookEventType' must be an integer")
return
end
if type(func) ~= "function" then
error("dnc_hook_event: Parameter 'func' must be a function")
return
end
if sDncHooks[hookEventType] == nil then return end
table_insert(sDncHooks[hookEventType], func)
end end
--- Returns whether or not DNC will display the time on the HUD --- @param hookEventType integer
local function get_display_time() function dnc_call_hook(hookEventType, ...)
return dncDisplayTime if sDncHooks[hookEventType] == nil then return end
local ret = nil
for hook in ipairs(sDncHooks[hookEventType]) do
ret = sDncHooks[hookEventType][hook](...)
end
return ret
end end
--- @param enable boolean --- Returns whether or not Day Night Cycle is globally enabled
--- @return nil function is_dnc_enabled()
--- Sets whether or not DNC will display the time on the HUD return gGlobalSyncTable.dncEnabled and dayNightCycleApi.enabled
local function set_display_time(enable) end
if type(enable) ~= "boolean" then return end
dncDisplayTime = enable --- Returns whether or not the game should visually show the day night cycle
function show_day_night_cycle()
local skybox = get_skybox()
return (skybox ~= -1 and
skybox ~= BACKGROUND_CUSTOM and
skybox ~= BACKGROUND_FLAMING_SKY and
skybox ~= BACKGROUND_GREEN_SKY and
skybox ~= BACKGROUND_PURPLE_SKY)
or in_vanilla_level(LEVEL_DDD) or in_vanilla_level(LEVEL_THI) or (in_vanilla_level(LEVEL_CASTLE) and gNetworkPlayers[0].currAreaIndex ~= 3) or in_vanilla_level(LEVEL_WDW)
end end
local function update() local function update()
if not gGlobalSyncTable.dncEnabled then if not is_dnc_enabled() then
set_override_envfx(-1)
set_lighting_dir(1, 0) set_lighting_dir(1, 0)
set_lighting_dir(2, 0) set_lighting_dir(2, 0)
set_lighting_color(0, 255) set_lighting_color(0, 255)
@ -56,59 +89,57 @@ local function update()
return return
end end
time_tick() if network_check_singleplayer_pause() then return end
handle_night_music()
if network_is_server() then time_tick() end
if not init then handle_night_music() end
-- spawn skyboxes -- spawn skyboxes
local skybox = get_skybox() local skybox = get_skybox()
if obj_get_first_with_behavior_id(bhvSkybox) == nil and skybox ~= -1 then if skybox >= BACKGROUND_CUSTOM then skybox = BACKGROUND_OCEAN_SKY end
if show_day_night_cycle() then if obj_get_first_with_behavior_id(bhvDNCSkybox) == nil and skybox ~= -1 and obj_get_first_with_behavior_id(bhvDNCNoSkybox) == nil then
if show_day_night_cycle() and skybox ~= BACKGROUND_HAUNTED then
-- spawn day, sunset and night skyboxes -- spawn day, sunset and night skyboxes
for i = 0, 2 do for i = SKYBOX_DAY, SKYBOX_NIGHT do
local model = 0 local thisSkybox = skybox
if i == 0 then if i == SKYBOX_SUNSET then
model = gVanillaSkyboxModels[skybox] or E_MODEL_SKYBOX_OCEAN_SKY thisSkybox = if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, BACKGROUND_BELOW_CLOUDS_SUNSET, BACKGROUND_SUNSET)
else elseif i == SKYBOX_NIGHT then
model = if_then_else(i == 1, E_MODEL_SKYBOX_SUNSET, E_MODEL_SKYBOX_NIGHT) thisSkybox = if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, BACKGROUND_BELOW_CLOUDS_NIGHT, BACKGROUND_NIGHT)
end end
spawn_non_sync_object( spawn_non_sync_object(
bhvSkybox, bhvDNCSkybox,
model, E_MODEL_SKYBOX,
0, 0, 0, 0, 0, 0,
--- @param o Object --- @param o Object
function(o) function(o)
o.oBehParams2ndByte = i o.oBehParams2ndByte = i
obj_scale(o, SKYBOX_SCALE + 1 * i) o.oAnimState = thisSkybox
obj_scale(o, SKYBOX_SCALE - 10 * i)
end end
) )
end end
else else
-- spawn static skybox -- spawn static skybox
spawn_non_sync_object( spawn_non_sync_object(
bhvSkybox, bhvDNCSkybox,
gVanillaSkyboxModels[skybox] or E_MODEL_SKYBOX_OCEAN_SKY, E_MODEL_SKYBOX,
0, 0, 0, 0, 0, 0,
--- @param o Object --- @param o Object
function(o) function(o)
o.oBehParams2ndByte = 0 o.oBehParams2ndByte = 0
o.oAnimState = skybox
obj_scale(o, SKYBOX_SCALE) obj_scale(o, SKYBOX_SCALE)
end end
) )
end end
end end
local minutes = (gGlobalSyncTable.time / MINUTE) % 24 local minutes = if_then_else(skybox ~= BACKGROUND_HAUNTED, get_time_minutes(), 12)
local actSelector = obj_get_first_with_behavior_id(id_bhvActSelector) 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 if actSelector == nil and show_day_night_cycle() then
-- 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 -- calculate lighting color
local color = COLOR_DAY local color = COLOR_DAY
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then
@ -124,6 +155,26 @@ local function update()
elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then
color = COLOR_DAY color = COLOR_DAY
end end
local overrideColor = dnc_call_hook(DNC_HOOK_SET_LIGHTING_COLOR, table_clone(color))
if overrideColor ~= nil and type(overrideColor) == "table" then color = overrideColor end
-- calculate ambient lighting color
local ambientColor = COLOR_AMBIENT_DAY
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then
ambientColor = color_lerp(COLOR_AMBIENT_NIGHT, COLOR_AMBIENT_SUNRISE, (minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION)
elseif minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then
ambientColor = color_lerp(COLOR_AMBIENT_SUNRISE, COLOR_AMBIENT_DAY, (minutes - HOUR_SUNRISE_END) / HOUR_SUNRISE_DURATION)
elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then
ambientColor = color_lerp(COLOR_AMBIENT_DAY, COLOR_AMBIENT_SUNSET, (minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION)
elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then
ambientColor = color_lerp(COLOR_AMBIENT_SUNSET, COLOR_AMBIENT_NIGHT, (minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION)
elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then
ambientColor = COLOR_AMBIENT_NIGHT
elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then
ambientColor = COLOR_AMBIENT_DAY
end
local overrideAmbientColor = dnc_call_hook(DNC_HOOK_SET_AMBIENT_LIGHTING_COLOR, table_clone(ambientColor))
if overrideAmbientColor ~= nil and type(overrideColor) == "table" then ambientColor = overrideColor end
-- calculate fog color -- calculate fog color
local fogColor = COLOR_DAY local fogColor = COLOR_DAY
@ -140,6 +191,9 @@ local function update()
elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then
fogColor = COLOR_DAY fogColor = COLOR_DAY
end end
fogColor = color_lerp(fogColor, ambientColor, 0.5)
local overrideFogColor = dnc_call_hook(DNC_HOOK_SET_FOG_COLOR, table_clone(fogColor))
if overrideFogColor ~= nil and type(overrideFogColor) == "table" then fogColor = overrideFogColor end
-- calculate lighting direction -- calculate lighting direction
local dir = DIR_BRIGHT local dir = DIR_BRIGHT
@ -152,34 +206,44 @@ local function update()
elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then
dir = DIR_BRIGHT dir = DIR_BRIGHT
end end
local overrideDir = dnc_call_hook(DNC_HOOK_SET_LIGHTING_DIR, dir)
if overrideDir ~= nil and type(overrideDir) == "number" then dir = overrideDir end
-- calculate fog intensity -- calculate fog intensity
local intensity = 1 local intensity = FOG_INTENSITY_NORMAL
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then 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)) intensity = lerp(FOG_INTENSITY_DENSE, FOG_INTENSITY_NORMAL, clampf((minutes - HOUR_SUNRISE_START) / (HOUR_SUNRISE_DURATION), 0, 1))
elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_NIGHT_START then 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)) intensity = lerp(FOG_INTENSITY_NORMAL, FOG_INTENSITY_DENSE, clampf((minutes - HOUR_SUNSET_START) / (HOUR_NIGHT_START - HOUR_SUNSET_START), 0, 1))
elseif minutes < HOUR_SUNRISE_START or minutes > HOUR_NIGHT_START then elseif minutes < HOUR_SUNRISE_START or minutes > HOUR_NIGHT_START then
intensity = 1.02 intensity = FOG_INTENSITY_DENSE
elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_START then
intensity = 1 intensity = FOG_INTENSITY_NORMAL
end end
local overrideIntensity = dnc_call_hook(DNC_HOOK_SET_FOG_INTENSITY, intensity)
if overrideIntensity ~= nil and type(overrideIntensity) == "number" then intensity = overrideIntensity end
set_lighting_dir(1, -(1 - dir)) set_lighting_dir(1, -(1 - dir))
set_lighting_dir(2, -(1 - dir)) set_lighting_dir(2, -(1 - dir))
-- make the castle still 25% ambient lit
if in_vanilla_level(LEVEL_CASTLE) then
color = color_lerp(COLOR_DAY, color, 0.75)
end
set_lighting_color(0, color.r) set_lighting_color(0, color.r)
set_lighting_color(1, color.g) set_lighting_color(1, color.g)
set_lighting_color(2, color.b) set_lighting_color(2, color.b)
set_vertex_color(0, color.r) set_lighting_color_ambient(0, ambientColor.r)
set_vertex_color(1, color.g) set_lighting_color_ambient(1, ambientColor.g)
set_vertex_color(2, color.b) set_lighting_color_ambient(2, ambientColor.b)
local mix = color_lerp(color, ambientColor, 0.5)
set_vertex_color(0, mix.r)
set_vertex_color(1, mix.g)
set_vertex_color(2, mix.b)
set_fog_color(0, fogColor.r) set_fog_color(0, fogColor.r)
set_fog_color(1, fogColor.g) set_fog_color(1, fogColor.g)
set_fog_color(2, fogColor.b) set_fog_color(2, fogColor.b)
set_fog_intensity(intensity) set_fog_intensity(intensity)
else else
set_override_envfx(-1)
set_lighting_dir(1, 0) set_lighting_dir(1, 0)
set_lighting_dir(2, 0) set_lighting_dir(2, 0)
set_lighting_color(0, 255) set_lighting_color(0, 255)
@ -193,73 +257,89 @@ local function update()
set_fog_color(2, 255) set_fog_color(2, 255)
set_fog_intensity(1) set_fog_intensity(1)
end end
init = false
end end
local function on_hud_render_behind() local function on_hud_render_behind()
if not gGlobalSyncTable.dncEnabled or not dncDisplayTime then return end -- api checks if not is_dnc_enabled() or not dayNightCycleApi.displayTime 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 if check_common_hud_render_cancels() then return end -- game checks
djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_resolution(RESOLUTION_N64)
djui_hud_set_font(FONT_TINY) djui_hud_set_font(FONT_NORMAL)
local scale = 1 local scale = 0.5
local text = get_time_string() local text = get_time_string(gGlobalSyncTable.time)
local hidden = hud_is_hidden() 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 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 y = if_then_else(hidden, (djui_hud_get_screen_height() - 20), 32)
local overridePos = dnc_call_hook(DNC_HOOK_SET_DISPLAY_TIME_POS, { x = x, y = y })
local minutes = (gGlobalSyncTable.time / MINUTE) % 24 if overridePos ~= nil and type(overridePos) == "table" then
x = overridePos.x
-- outlined text y = overridePos.y
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 end
djui_hud_print_text(text, x, y, scale)
local minutes = if_then_else(get_skybox() ~= BACKGROUND_HAUNTED, get_time_minutes(), 0)
local color = COLOR_DISPLAY_BRIGHT
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then
color = color_lerp(COLOR_DISPLAY_DARK, COLOR_DISPLAY_BRIGHT, (minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION)
elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then
color = color_lerp(COLOR_DISPLAY_BRIGHT, COLOR_DISPLAY_DARK, (minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION)
elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then
color = COLOR_DISPLAY_DARK
elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_END then
color = COLOR_DISPLAY_BRIGHT
end
local overrideColor = dnc_call_hook(DNC_HOOK_SET_DISPLAY_TIME_COLOR, table_clone(color))
if overrideColor ~= nil and type(overrideColor) == "table" then color = overrideColor end
djui_hud_set_color(color.r, color.g, color.b, 255)
djui_hud_print_outlined_text(text, x, y, scale, 0.0)
end end
local function on_level_init() local function on_level_init()
if not gGlobalSyncTable.dncEnabled then return end if not is_dnc_enabled() then return end
playingNightMusic = false playingNightMusic = false
if gNetworkPlayers[0].currLevelNum == LEVEL_CASTLE_GROUNDS and gNetworkPlayers[0].currActNum == 99 then if gNetworkPlayers[0].currActNum ~= 99 then
if gMarioStates[0].action ~= ACT_END_WAVING_CUTSCENE then if network_is_server() then
gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * (HOUR_SUNSET_START - 0.75)) save_time()
gGlobalSyncTable.timeScale = 1
else
gGlobalSyncTable.time = (get_day_count() + 1) * (MINUTE * 24) + (MINUTE * HOUR_SUNRISE_END)
end end
return
end
--- @type NetworkPlayer
local np = gNetworkPlayers[0]
if np.currLevelNum == LEVEL_CASTLE_GROUNDS and gMarioStates[0].action ~= ACT_END_WAVING_CUTSCENE then
gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * (HOUR_SUNSET_START - 0.7))
gGlobalSyncTable.timeScale = 1.0
elseif np.currLevelNum == LEVEL_THI and np.currAreaIndex == 1 then
gGlobalSyncTable.time = (get_day_count() + 1) * (MINUTE * 24) + (MINUTE * HOUR_SUNRISE_START)
end end
end end
local function on_warp() local function on_warp()
if not gGlobalSyncTable.dncEnabled then return end if not is_dnc_enabled() then return end
if network_is_server() then save_time() end if network_is_server() then save_time() end
playingNightMusic = false
end end
local function on_exit() local function on_exit()
if network_is_server() then save_time() end if network_is_server() then save_time() end
end end
--- @param msg string
local function on_set_command(msg) local function on_set_command(msg)
if msg == "" then if msg == "" then
djui_chat_message_create("/time \\#00ffff\\set\\#ffff00\\ [TIME]\\#dcdcdc\\ to set the time") djui_chat_message_create("/time \\#00ffff\\set\\#ffff00\\ [TIME]\\#dcdcdc\\ to set the time")
return true return true
end end
local oldTime = gGlobalSyncTable.time
if msg == "morning" then if msg == "morning" then
gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 6) gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * 6)
elseif msg == "day" or msg == "noon" then elseif msg == "day" or msg == "noon" then
@ -276,27 +356,33 @@ local function on_set_command(msg)
local amount = tonumber(msg) local amount = tonumber(msg)
if amount ~= nil then if amount ~= nil then
gGlobalSyncTable.time = amount * SECOND gGlobalSyncTable.time = amount * SECOND
djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
else
djui_chat_message_create(string.format("\\#ffa0a0\\[Day Night Cycle] Could not set time to '%s'", msg))
end end
end end
djui_chat_message_create("Time set to " .. math_floor(gGlobalSyncTable.time / SECOND)) dnc_call_hook(DNC_HOOK_SET_TIME, oldTime, gGlobalSyncTable.time)
save_time()
if network_is_server() then save_time() end
end end
--- @param msg string
local function on_add_command(msg) local function on_add_command(msg)
local amount = tonumber(msg) local amount = tonumber(msg)
if amount == nil then if amount == nil then
djui_chat_message_create("/time \\#00ffff\\add\\#ffff00\\ [AMOUNT]\\#dcdcdc\\ to add to the time") djui_chat_message_create("/time \\#00ffff\\add\\#ffff00\\ [AMOUNT]\\#dcdcdc\\ to add to the time")
return return
end end
local oldTime = gGlobalSyncTable.time
gGlobalSyncTable.time = gGlobalSyncTable.time + (amount * SECOND) gGlobalSyncTable.time = gGlobalSyncTable.time + (amount * SECOND)
dnc_call_hook(DNC_HOOK_SET_TIME, oldTime, gGlobalSyncTable.time)
djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND)) djui_chat_message_create("[Day Night Cycle] Time set to " .. math_floor(gGlobalSyncTable.time / SECOND))
if network_is_server() then save_time() end save_time()
end end
--- @param msg string
local function on_scale_command(msg) local function on_scale_command(msg)
local scale = tonumber(msg) local scale = tonumber(msg)
if scale == nil then if scale == nil then
@ -304,14 +390,15 @@ local function on_scale_command(msg)
return return
end end
gGlobalSyncTable.timeScale = scale gGlobalSyncTable.timeScale = scale
mod_storage_save_number("scale", scale)
djui_chat_message_create("[Day Night Cycle] Time scale set to " .. scale) djui_chat_message_create("[Day Night Cycle] Time scale set to " .. scale)
if network_is_server() then save_time() end save_time()
end end
local function on_query_command() 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())) djui_chat_message_create(string.format("[Day Night Cycle] Time is %d (%s), day %d", math_floor(gGlobalSyncTable.time / SECOND), get_time_string(gGlobalSyncTable.time), get_day_count()))
end end
local function on_24h_command() local function on_24h_command()
@ -326,27 +413,35 @@ local function on_sync_command()
gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * dateTime.hour) + (SECOND * dateTime.minute) gGlobalSyncTable.time = get_day_count() * (MINUTE * 24) + (MINUTE * dateTime.hour) + (SECOND * dateTime.minute)
gGlobalSyncTable.timeScale = REAL_MINUTE gGlobalSyncTable.timeScale = REAL_MINUTE
if network_is_server() then save_time() end save_time()
mod_storage_save_number("scale", REAL_MINUTE)
end end
local function on_music_command()
playNightMusic = not playNightMusic
mod_storage_save_bool("night-music", playNightMusic)
djui_chat_message_create("[Day Night Cycle] Night music status: " .. on_or_off(playNightMusic))
end
--- @param msg string
local function on_time_command(msg) local function on_time_command(msg)
local args = split(msg) local args = split(msg)
local perms = network_is_server() or network_is_moderator()
if args[1] == "set" then if args[1] == "set" then
if not perms then if not network_is_server() then
djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time set") djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time set")
else else
on_set_command(args[2] or "") on_set_command(args[2] or "")
end end
elseif args[1] == "add" then elseif args[1] == "add" then
if not perms then if not network_is_server() then
djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time add") djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time add")
else else
on_add_command(args[2] or "") on_add_command(args[2] or "")
end end
elseif args[1] == "scale" then elseif args[1] == "scale" then
if not perms then if not network_is_server() then
djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time scale") djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time scale")
else else
on_scale_command(args[2] or "") on_scale_command(args[2] or "")
end end
@ -355,14 +450,16 @@ local function on_time_command(msg)
elseif args[1] == "24h" then elseif args[1] == "24h" then
on_24h_command() on_24h_command()
elseif args[1] == "sync" then elseif args[1] == "sync" then
if not perms then if not network_is_server() then
djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to run /time sync") djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to run /time sync")
else else
on_sync_command() on_sync_command()
end end
elseif args[1] == "music" then
on_music_command()
else else
if not perms then if not network_is_server() then
djui_chat_message_create("\\#d86464\\[Day Night Cycle] You do not have permission to enable or disable Day Night Cycle") djui_chat_message_create("\\#ffa0a0\\[Day Night Cycle] You do not have permission to enable or disable Day Night Cycle")
else else
gGlobalSyncTable.dncEnabled = not gGlobalSyncTable.dncEnabled gGlobalSyncTable.dncEnabled = not gGlobalSyncTable.dncEnabled
djui_chat_message_create("[Day Night Cycle] Status: " .. on_or_off(gGlobalSyncTable.dncEnabled)) djui_chat_message_create("[Day Night Cycle] Status: " .. on_or_off(gGlobalSyncTable.dncEnabled))
@ -372,15 +469,153 @@ local function on_time_command(msg)
return true return true
end end
--- @param value boolean
local function on_set_dnc_enabled(_, value)
gGlobalSyncTable.dncEnabled = value
end
--- @param value boolean
local function on_set_24h_time(_, value)
use24h = value
mod_storage_save_bool("24h", value)
end
--- @param value boolean
local function on_set_night_time_music(_, value)
playNightMusic = value
mod_storage_save_bool("night_music", value)
end
--- @param value integer
local function on_set_time_scale(index, value)
gGlobalSyncTable.timeScale = value
mod_storage_save_number("scale", value)
update_mod_menu_element_name(index, "Time Scale: " .. value)
end
local function on_add_hour()
local oldTime = gGlobalSyncTable.time
gGlobalSyncTable.time = gGlobalSyncTable.time + (60 * SECOND)
dnc_call_hook(DNC_HOOK_SET_TIME, oldTime, gGlobalSyncTable.time)
save_time()
end
local function on_subtract_hour()
local oldTime = gGlobalSyncTable.time
gGlobalSyncTable.time = gGlobalSyncTable.time - (60 * SECOND)
dnc_call_hook(DNC_HOOK_SET_TIME, oldTime, gGlobalSyncTable.time)
save_time()
end
local sReadonlyMetatable = {
__index = function(table, key)
return rawget(table, key)
end,
__newindex = function()
error("attempt to update a read-only table", 2)
end
}
_G.dayNightCycleApi = { _G.dayNightCycleApi = {
enable_day_night_cycle = enable_day_night_cycle, version = DNC_VERSION,
get_display_time = get_display_time, enabled = true,
set_display_time = set_display_time, displayTime = true,
playNightMusic = true,
is_dnc_enabled = is_dnc_enabled,
get_day_count = get_day_count, get_day_count = get_day_count,
get_time_string = get_time_string,
get_raw_time = get_raw_time, get_raw_time = get_raw_time,
set_raw_time = set_raw_time, set_raw_time = set_raw_time,
get_time_minutes = get_time_minutes,
get_time_scale = get_time_scale,
set_time_scale = set_time_scale,
get_time_string = get_time_string,
delete_at_dark = delete_at_dark,
show_day_night_cycle = show_day_night_cycle,
dnc_hook_event = dnc_hook_event,
constants = {
SECOND = SECOND,
MINUTE = MINUTE,
HOUR_SUNRISE_START = HOUR_SUNRISE_START,
HOUR_SUNRISE_END = HOUR_SUNRISE_END,
HOUR_SUNRISE_DURATION = HOUR_SUNRISE_DURATION,
HOUR_SUNSET_START = HOUR_SUNSET_START,
HOUR_SUNSET_END = HOUR_SUNSET_END,
HOUR_SUNSET_DURATION = HOUR_SUNSET_DURATION,
HOUR_DAY_START = HOUR_DAY_START,
HOUR_NIGHT_START = HOUR_NIGHT_START,
DIR_DARK = DIR_DARK,
DIR_BRIGHT = DIR_BRIGHT,
FOG_INTENSITY_NORMAL = FOG_INTENSITY_NORMAL,
FOG_INTENSITY_DENSE = FOG_INTENSITY_DENSE,
COLOR_NIGHT = COLOR_NIGHT,
COLOR_AMBIENT_NIGHT = COLOR_AMBIENT_NIGHT,
COLOR_SUNRISE = COLOR_SUNRISE,
COLOR_AMBIENT_SUNRISE = COLOR_AMBIENT_SUNRISE,
COLOR_DAY = COLOR_DAY,
COLOR_AMBIENT_DAY = COLOR_AMBIENT_DAY,
COLOR_SUNSET = COLOR_SUNSET,
COLOR_AMBIENT_SUNSET = COLOR_AMBIENT_SUNSET,
FOG_COLOR_NIGHT = FOG_COLOR_NIGHT,
COLOR_DISPLAY_DARK = COLOR_DISPLAY_DARK,
COLOR_DISPLAY_BRIGHT = COLOR_DISPLAY_BRIGHT,
SKYBOX_SCALE = SKYBOX_SCALE,
SKYBOX_DAY = SKYBOX_DAY,
SKYBOX_SUNSET = SKYBOX_SUNSET,
SKYBOX_NIGHT = SKYBOX_NIGHT,
-- * Called whenever the lighting color is calculated
-- * Parameters: `Color` color
-- * Return: `Color`
DNC_HOOK_SET_LIGHTING_COLOR = DNC_HOOK_SET_LIGHTING_COLOR,
-- * Called whenever the ambient lighting color is calculated
-- * Parameters: `Color` ambientColor
-- * Return: `Color`
DNC_HOOK_SET_AMBIENT_LIGHTING_COLOR = DNC_HOOK_SET_AMBIENT_LIGHTING_COLOR,
-- * Called whenever the lighting direction is calculated
-- * Parameters: `number` dir
-- * Return: `number`
DNC_HOOK_SET_LIGHTING_DIR = DNC_HOOK_SET_LIGHTING_DIR,
-- * Called whenever the fog color is calculated
-- * Parameters: `Color` color
-- * Return: `Color`
DNC_HOOK_SET_FOG_COLOR = DNC_HOOK_SET_FOG_COLOR,
-- * Called whenever the fog intensity is calculated
-- * Parameters: `number` intensity
-- * Return: `number`
DNC_HOOK_SET_FOG_INTENSITY = DNC_HOOK_SET_FOG_INTENSITY,
-- * Called whenever the HUD display time color is calculated
-- * Parameters: `Color` color
-- * Return: `Color`
DNC_HOOK_SET_DISPLAY_TIME_COLOR = DNC_HOOK_SET_DISPLAY_TIME_COLOR,
-- * Called whenever the HUD display time position is calculated
-- * Parameters: `Vec2f` pos
-- * Return: `Vec2f`
DNC_HOOK_SET_DISPLAY_TIME_POS = DNC_HOOK_SET_DISPLAY_TIME_POS,
-- * Called whenever `delete_at_dark` is run
-- * Parameters: `Object` obj, `boolean` shouldDelete
-- * Return: `boolean`
DNC_HOOK_DELETE_AT_DARK = DNC_HOOK_DELETE_AT_DARK,
-- * Called whenever `/time set` or `/time add` is ran
-- * Parameters: `number` oldTime, `number` newTime
-- * Return: nil
DNC_HOOK_SET_TIME = DNC_HOOK_SET_TIME
}
} }
setmetatable(_G.dayNightCycleApi, sReadonlyMetatable)
night_music_register(SEQ_LEVEL_GRASS, "03_level_grass") night_music_register(SEQ_LEVEL_GRASS, "03_level_grass")
night_music_register(SEQ_LEVEL_WATER, "05_level_water") night_music_register(SEQ_LEVEL_WATER, "05_level_water")
@ -393,4 +628,13 @@ hook_event(HOOK_ON_LEVEL_INIT, on_level_init)
hook_event(HOOK_ON_WARP, on_warp) hook_event(HOOK_ON_WARP, on_warp)
hook_event(HOOK_ON_EXIT, on_exit) 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) hook_chat_command("time", "\\#00ffff\\[set|add|scale|query|24h|sync|music]\\#dcdcdc\\ - The command handle for Day Night Cycle DX \\#7f7f7f\\(leave blank to toggle Day Night Cycle on or off)", on_time_command)
if network_is_server() then
hook_mod_menu_checkbox("Enable Day Night Cycle", gGlobalSyncTable.dncEnabled, on_set_dnc_enabled)
hook_mod_menu_checkbox("24 Hour Time", use24h, on_set_24h_time)
hook_mod_menu_checkbox("Night Time Music", playNightMusic, on_set_night_time_music)
hook_mod_menu_slider("Time Scale: " .. math_tointeger(gGlobalSyncTable.timeScale), gGlobalSyncTable.timeScale, 0, 20, on_set_time_scale)
hook_mod_menu_button("Add 1 In-Game Hour", on_add_hour)
hook_mod_menu_button("Subtract 1 In-Game Hour", on_subtract_hour)
end

View file

@ -1,38 +1,15 @@
if SM64COOPDX_VERSION == nil then return end
gVanillaSkyboxModels = {
[BACKGROUND_OCEAN_SKY] = E_MODEL_SKYBOX_OCEAN_SKY,
[BACKGROUND_FLAMING_SKY] = E_MODEL_SKYBOX_FLAMING_SKY,
[BACKGROUND_UNDERWATER_CITY] = E_MODEL_SKYBOX_UNDERWATER_CITY,
[BACKGROUND_BELOW_CLOUDS] = E_MODEL_SKYBOX_BELOW_CLOUDS,
[BACKGROUND_SNOW_MOUNTAINS] = E_MODEL_SKYBOX_SNOW_MOUNTAINS,
[BACKGROUND_DESERT] = E_MODEL_SKYBOX_DESERT,
[BACKGROUND_HAUNTED] = E_MODEL_SKYBOX_HAUNTED,
[BACKGROUND_GREEN_SKY] = E_MODEL_SKYBOX_GREEN_SKY,
[BACKGROUND_ABOVE_CLOUDS] = E_MODEL_SKYBOX_ABOVE_CLOUDS,
[BACKGROUND_PURPLE_SKY] = E_MODEL_SKYBOX_PURPLE_SKY
}
-- localize functions to improve performance -- localize functions to improve performance
local get_skybox,obj_set_model_extended,set_override_far,obj_mark_for_deletion,vec3f_to_object_pos,clampf,cur_obj_hide,cur_obj_unhide = get_skybox,obj_set_model_extended,set_override_far,obj_mark_for_deletion,vec3f_to_object_pos,clampf,cur_obj_hide,cur_obj_unhide local set_override_far,obj_mark_for_deletion,vec3f_to_object_pos,get_skybox,clampf = set_override_far,obj_mark_for_deletion,vec3f_to_object_pos,get_skybox,clampf
--- @param o Object --- @param o Object
function bhv_skybox_init(o) function bhv_skybox_init(o)
o.header.gfx.skipInViewCheck = true o.header.gfx.skipInViewCheck = true
set_override_far(200000)
local skybox = get_skybox()
if o.oBehParams2ndByte == SKYBOX_DAY then
obj_set_model_extended(o, gVanillaSkyboxModels[skybox] or E_MODEL_SKYBOX_OCEAN_SKY)
elseif o.oBehParams2ndByte == SKYBOX_NIGHT then
obj_set_model_extended(o, if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, E_MODEL_SKYBOX_BELOW_CLOUDS_NIGHT, E_MODEL_SKYBOX_NIGHT))
end
set_override_far(100000)
end end
--- @param o Object --- @param o Object
function bhv_skybox_loop(o) function bhv_skybox_loop(o)
if not gGlobalSyncTable.dncEnabled then if not is_dnc_enabled() then
obj_mark_for_deletion(o) obj_mark_for_deletion(o)
return return
end end
@ -44,36 +21,19 @@ function bhv_skybox_loop(o)
-- do not rotate BITDW skybox -- do not rotate BITDW skybox
if skybox == BACKGROUND_GREEN_SKY then return end if skybox == BACKGROUND_GREEN_SKY then return end
local minutes = (gGlobalSyncTable.time / MINUTE) % 24 local minutes = get_time_minutes()
if o.oBehParams2ndByte ~= SKYBOX_SUNSET then
o.oFaceAngleYaw = gGlobalSyncTable.time * 2
else
if minutes < 12 then
o.oFaceAngleYaw = 0
else
o.oFaceAngleYaw = 0x8000
end
end
o.oFaceAngleYaw = (minutes / 24) * 0x10000
if o.oBehParams2ndByte == SKYBOX_DAY then if o.oBehParams2ndByte == SKYBOX_DAY then
if minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then o.oOpacity = 255
o.oOpacity = lerp_round(0, 255, clampf((minutes - HOUR_SUNRISE_END) / HOUR_SUNSET_DURATION, 0, 1))
elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then
o.oOpacity = lerp_round(255, 0, clampf((minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION, 0, 1))
elseif minutes < HOUR_SUNRISE_END or minutes > HOUR_NIGHT_START then
o.oOpacity = 0
elseif minutes > HOUR_DAY_START and minutes < HOUR_SUNSET_START then
o.oOpacity = 255
end
elseif o.oBehParams2ndByte == SKYBOX_SUNSET then elseif o.oBehParams2ndByte == SKYBOX_SUNSET then
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then
o.oOpacity = lerp_round(0, 255, clampf((minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION, 0, 1)) o.oOpacity = lerp_ceil(0, 255, clampf((minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION, 0, 1))
elseif minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then elseif minutes >= HOUR_SUNRISE_END and minutes <= HOUR_DAY_START then
o.oOpacity = lerp_round(255, 0, clampf((minutes - HOUR_SUNRISE_END) / HOUR_SUNRISE_DURATION, 0, 1)) o.oOpacity = lerp_ceil(255, 0, clampf((minutes - HOUR_SUNRISE_END) / HOUR_SUNRISE_DURATION, 0, 1))
elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then elseif minutes >= HOUR_SUNSET_START and minutes <= HOUR_SUNSET_END then
o.oOpacity = lerp_round(0, 255, clampf((minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION, 0, 1)) o.oOpacity = lerp_ceil(0, 255, clampf((minutes - HOUR_SUNSET_START) / HOUR_SUNSET_DURATION, 0, 1))
elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then
o.oOpacity = 255 o.oOpacity = 255
else else
@ -81,25 +41,21 @@ function bhv_skybox_loop(o)
end end
if minutes < 12 then if minutes < 12 then
obj_set_model_extended(o, if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, E_MODEL_SKYBOX_BELOW_CLOUDS_SUNRISE, E_MODEL_SKYBOX_SUNRISE)) o.oAnimState = if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, BACKGROUND_BELOW_CLOUDS_SUNRISE, BACKGROUND_SUNRISE)
else else
obj_set_model_extended(o, if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, E_MODEL_SKYBOX_BELOW_CLOUDS_SUNSET, E_MODEL_SKYBOX_SUNSET)) o.oAnimState = if_then_else(skybox == BACKGROUND_BELOW_CLOUDS, BACKGROUND_BELOW_CLOUDS_SUNSET, BACKGROUND_SUNSET)
end end
o.oFaceAngleYaw = o.oFaceAngleYaw - if_then_else(minutes < 12, 0x3000, 0x6000)
elseif o.oBehParams2ndByte == SKYBOX_NIGHT then elseif o.oBehParams2ndByte == SKYBOX_NIGHT then
if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then if minutes >= HOUR_SUNRISE_START and minutes <= HOUR_SUNRISE_END then
o.oOpacity = lerp_round(255, 0, clampf((minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION, 0, 1)) o.oOpacity = lerp_ceil(255, 0, clampf((minutes - HOUR_SUNRISE_START) / HOUR_SUNRISE_DURATION, 0, 1))
elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then elseif minutes >= HOUR_SUNSET_END and minutes <= HOUR_NIGHT_START then
o.oOpacity = lerp_round(0, 255, clampf((minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION, 0, 1)) o.oOpacity = lerp_ceil(0, 255, clampf((minutes - HOUR_SUNSET_END) / HOUR_SUNSET_DURATION, 0, 1))
elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_END then elseif minutes > HOUR_SUNRISE_END and minutes < HOUR_SUNSET_END then
o.oOpacity = 0 o.oOpacity = 0
elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then elseif minutes > HOUR_NIGHT_START or minutes < HOUR_SUNRISE_START then
o.oOpacity = 255 o.oOpacity = 255
end end
end end
if o.oOpacity == 0 then
cur_obj_hide()
else
cur_obj_unhide()
end
end end