/** Time Controller Creates time based on the 24-hour time scheme. Time is computed in minutes, which are by default 1/2 a second in real life (18 frames). This will make each complete day/night cycle last 12 minutes in real life. The time controller has an interface which is accessed by: Time->HasDayNightCycle(): whether time controller is active. Time->SetTime(int to_time): sets the time. Time->GetTime(): returns the time. Time->IsDay(): whether it is day. Time->IsNight(): whether it is night. Time->SetCycleSpeed(int seconds_per_tick): set the speed of the day/night cycle. Time->GetCycleSpeed(): returns the speed of the day/night cycle. @author Ringwall, Maikel */ local time_set; local time; local advance_seconds_per_tick; local daycolour_global; /*-- Interface --*/ // Creates the time controller object if it does not exist otherwise returns the existing controller. public func Init() { // Only a definition call if needed. if (GetType(this) != C4V_Def) return; // Create and return time controller if it does not exist. var time_controller = FindObject(Find_ID(Time)); if (!time_controller) time_controller = CreateObject(Time); return time_controller; } // Returns whether the time controller is active. public func HasDayNightCycle() { // Do definition call if needed. if (GetType(this) == C4V_Def) return FindObject(Find_ID(Time)); return; } // Sets the current time using a 24*60 minute clock scheme. public func SetTime(int to_time) { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) time_controller->SetTime(to_time); return; } // Otherwise normal behavior. // Set time. time = (to_time * 60) % (24 * 60 * 60); // Hide celestials during day. if (Inside(time, time_set.sunrise_end, time_set.sunset_start)) HideCelestials(); else ShowCelestials(); // Adjust to time. AdjustToTime(); return; } // Returns the time in minutes. public func GetTime() { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) return time_controller->GetTime(); return; } // Otherwise normal behavior. return time / 60; } public func IsDay() { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) return time_controller->IsDay(); // If there is no time controller active it is day. return true; } // Otherwise normal behavior. var day_start = (time_set.sunrise_start + time_set.sunrise_end) / 2; var day_end = (time_set.sunset_start + time_set.sunset_end) / 2; if (Inside(time, day_start, day_end)) return true; return false; } public func IsNight() { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) return time_controller->IsNight(); // If there is no time controller active it is not night. return false; } // Otherwise normal behavior. var night_start = (time_set.sunset_start + time_set.sunset_end) / 2; var night_end = (time_set.sunrise_start + time_set.sunrise_end) / 2; if (!Inside(time, night_end, night_start)) return true; return false; } // Sets the number of seconds the day will advance each tick (10 frames). // Setting to 0 will stop day-night cycle. Default is 30 seconds. public func SetCycleSpeed(int seconds_per_tick) { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) time_controller->SetCycleSpeed(seconds_per_tick); return; } // Otherwise normal behavior. advance_seconds_per_tick = seconds_per_tick; } // Returns the number of seconds the day advances each tick (10 frames). public func GetCycleSpeed() { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) return time_controller->GetCycleSpeed(); return; } // Otherwise normal behavior. return advance_seconds_per_tick; } // Places stars in the indicated rectangle from (0, 0) to (lw, lh). public func PlaceStars(int lw, int lh) { // Do definition call if needed. if (GetType(this) == C4V_Def) { var time_controller = FindObject(Find_ID(Time)); if (time_controller) return time_controller->PlaceStars(lw, lh); return; } // First remove possible old star objects, to prevent too many. RemoveAll(Find_ID(Stars)); // Since stars are almost completely parallax (=in screen coordinates), we only need // to place stars for max. a reasonable maximum resolution, let's say 1920x1200. lw = lw ?? 1920; lh = lh ?? 1200; // Star Creation. var maxfailedtries = lw * lh / 40000; var failed = 0; while (failed != maxfailedtries) { var pos = [Random(lw), Random(lh)]; if (!FindObject(Find_ID(Stars), Find_AtPoint(pos[0], pos[1]))) { CreateObjectAbove(Stars, pos[0], pos[1]); continue; } failed++; } return; } /*-- Code -- */ protected func Initialize() { // Only one time control object. if (ObjectCount(Find_ID(Time)) > 1) return RemoveObject(); // Determine the frame times for day and night events. time_set = { sunrise_start = 3 * 60 * 60, // 3:00 sunrise_end = 9 * 60 * 60, // 9:00 sunset_start = 15 * 60 * 60, // 15:00 sunset_end = 21 * 60 * 60, // 21:00 }; // Create moon and stars if celestial objects are not blocked by the scenario. if (!GameCall("HasNoCelestials")) { PlaceStars(); CreateObject(Moon); } // Set standard colour of the day daycolour_global = [255, 255, 255]; // Set the time to midday (12:00). SetTime(12 * 60); // Add effect that controls time cycle. SetCycleSpeed(30); AddEffect("IntTimeCycle", this, 100, 10, this); } public func Destruction() { // Only if last object. if (ObjectCount(Find_ID(Time)) > 1) return; // Remove celestial objects. RemoveAll(Find_Func("IsCelestial")); // Reset sky shading and ambience. SetTime(12 * 60); DoSkyShade(); return; } // Cycles through day and night. protected func FxIntTimeCycleTimer(object target, proplist effect) { // Adjust to time. AdjustToTime(); // Advance time. time += Max(advance_seconds_per_tick * effect.Interval / 10, 1); time %= (24 * 60 * 60); return FX_OK; } private func HideCelestials() { // Hide celestial objects, they will not be drawn during the day. for (var celestial in FindObjects(Find_Func("IsCelestial"))) { celestial.Visibility = VIS_None; celestial->SetObjAlpha(0); } return; } private func ShowCelestials() { // Show celestial objects. for (var celestial in FindObjects(Find_Func("IsCelestial"))) celestial.Visibility = VIS_All; return; } private func OnSunriseEnd() { // Next moon phase. var moon = FindObject(Find_ID(Moon)); if (moon) moon->NextMoonPhase(); HideCelestials(); } private func OnSunsetStart() { ShowCelestials(); } // Adjusts the sky, celestial and others to the current time. Use SetTime() at runtime, not this. private func AdjustToTime() { if (time >= time_set.sunrise_end && time < time_set.sunrise_end + advance_seconds_per_tick) OnSunriseEnd(); else if (time >= time_set.sunset_start && time < time_set.sunset_start + advance_seconds_per_tick) OnSunsetStart(); DoSkyShade(); return; } private func DoSkyShade() { // First determine the time phase we are in. var sunrise, sunset, night, day; sunrise = sunset = night = day = false; if (Inside(time, time_set.sunrise_start, time_set.sunrise_end)) sunrise = true; else if(Inside(time, time_set.sunrise_end, time_set.sunset_start)) day = true; else if(Inside(time, time_set.sunset_start, time_set.sunset_end)) sunset = true; else night = true; // Specify colors in terms of R, G, B, A arrays. var skyshade = [0, 0, 0, 0]; var nightcolour = [10, 25, 40]; var daycolour = daycolour_global; var sunsetcolour = [140, 45, 10]; var sunrisecolour = [140, 100, 70]; var ambient_brightness = 0; // Darkness of night dependent on the moon-phase. if (!day) { var satellite = FindObject(Find_ID(Moon)); if(satellite) { var lightness = satellite->GetMoonLightness(); nightcolour = [ 6 * lightness / 100, 8 + 25 * lightness / 100, 15 + 60 * lightness / 100 ]; } } // Sunrise. if (sunrise) { var time_since_sunrise = time - time_set.sunrise_start; // progress in 0..1800 var progress = time_since_sunrise * 1800 / (time_set.sunrise_end - time_set.sunrise_start); for (var i = 0; i < 3; ++i) { var nightfade = Cos(progress / 2, nightcolour[i], 10); var dayfade = daycolour[i] - Cos(progress / 2, daycolour[i], 10); var sunrisefade = Sin(progress, sunrisecolour[i], 10); skyshade[i] = Min(255, dayfade + nightfade + sunrisefade); } skyshade[3] = Min(255, progress / 2); ambient_brightness = 100 * (40 + 215 * progress / 1800) / 255; } // Day. else if (day) { skyshade = [daycolour[0], daycolour[1], daycolour[2], 255]; //daycolour_global = [GetRGBaValue(GetSkyAdjust(), RGBA_RED), GetRGBaValue(GetSkyAdjust(), RGBA_GREEN), GetRGBaValue(GetSkyAdjust(), RGBA_BLUE)]; ambient_brightness = 100; } // Sunset. else if (sunset) { var time_since_sunset = time - time_set.sunset_start; // progress in 0..1800 var progress = time_since_sunset * 1800 / (time_set.sunset_end - time_set.sunset_start); for (var i = 0; i < 3; ++i) { var dayfade = Cos(progress / 2, daycolour[i], 10); var nightfade = nightcolour[i] - Cos(progress / 2, nightcolour[i], 10); var sunsetfade = Sin(progress, sunsetcolour[i], 10); skyshade[i] = Min(255, dayfade + nightfade + sunsetfade); } skyshade[3] = Min(255, 900 - progress / 2); ambient_brightness = 100 * (255 - 215 * progress / 1800) / 255; } // Night. else if (night) { skyshade = nightcolour; skyshade[3] = 0; ambient_brightness = 15; } // Shade the sky using sky adjust. SetSkyAdjust(RGBa(skyshade[0], skyshade[1], skyshade[2], GetRGBaValue(GetSkyAdjust(), RGBA_ALPHA)), GetSkyAdjust(true)); // Shade the landscape and the general feeling by reducing the ambient light. if (GetAmbientBrightness() != ambient_brightness) SetAmbientBrightness(ambient_brightness); // Adjust celestial objects and clouds. for (var celestial in FindObjects(Find_Func("IsCelestial"))) celestial->SetObjAlpha(255 - skyshade[3]); for (var cloud in FindObjects(Find_ID(Cloud))) cloud->SetLightingShade(255 - skyshade[3]); return; } /*-- Scenario saving --*/ public func SaveScenarioObject(props) { if (!inherited(props, ...)) return false; // Save time props. if (GetTime() != 720) props->AddCall("Time", this, "SetTime", GetTime()); if (GetCycleSpeed() != 30) props->AddCall("CycleSpeed", this, "SetCycleSpeed", GetCycleSpeed()); return true; } /*-- Editor --*/ public func Definition(def) { if (!def.EditorProps) def.EditorProps = {}; def.EditorProps.Time = { Name="$Time$", EditorHelp="$TimeHelp$", Type="int", Min=0, Max=24*60 - 1, AsyncGet="GetTime", Set="SetTime" }; def.EditorProps.CycleSpeed = { Name="$CycleSpeed$", EditorHelp="$CycleSpeedHelp$", Type="int", Min=0, AsyncGet="GetCycleSpeed", Set="SetCycleSpeed" }; } /*-- Properties --*/ local Name = "Time"; local Description = "$Description$"; local Visibility = VIS_Editor; local EditorPlacementLimit = 1;