/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Net; using System.Reflection; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Framework; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using OpenSim.Services.Interfaces; using log4net; using Nini.Config; using Mono.Addins; using Caps = OpenSim.Framework.Capabilities.Caps; using OSDArray = OpenMetaverse.StructuredData.OSDArray; using OSDMap = OpenMetaverse.StructuredData.OSDMap; using MethodImplOptions = System.Runtime.CompilerServices.MethodImplOptions; namespace OpenSim.Region.CoreModules.World.LightShare { [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "EnvironmentModule")] public class EnvironmentModule : INonSharedRegionModule, IEnvironmentModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Scene m_scene = null; private UUID regionID = UUID.Zero; private bool Enabled = false; private IEstateModule m_estateModule; private IEventQueue m_eventQueue; private IAssetService m_assetService; private ILandChannel m_landChannel; private static ViewerEnvironment m_DefaultEnv = null; // 1/1 day-to-night ratio //private static readonly string m_defaultDayAssetID = "5646d39e-d3d7-6aff-ed71-30fc87d64a91"; // Default Daycycle // 3/1 day-to-night ratio private static readonly string m_defaultDayAssetID = "5646d39e-d3d7-6aff-ed71-30fc87d64a92"; // Default Daycycle (More Daylight) private static UUID m_defaultDayAssetUUID = new("5646d39e-d3d7-6aff-ed71-30fc87d64a92"); //private static string m_defaultSkyAssetID = "3ae23978-ac82-bcf3-a9cb-ba6e52dcb9ad"; private static UUID m_defaultSkyAssetUUID = new("3ae23978-ac82-bcf3-a9cb-ba6e52dcb9ad"); //private static string m_defaultWaterAssetID = "59d1a851-47e7-0e5f-1ed7-6b715154f41a"; private static UUID m_defaultWaterAssetUUID = new("59d1a851-47e7-0e5f-1ed7-6b715154f41a"); private int m_regionEnvVersion = -1; private double m_framets; #region INonSharedRegionModule public void Initialise(IConfigSource source) { IConfig config = source.Configs["ClientStack.LindenCaps"]; if (config is null) return; if (!config.GetString("Cap_EnvironmentSettings", string.Empty).Equals("localhost")) { m_log.InfoFormat("[{0}]: Module is disabled.", Name); return; } Enabled = true; m_log.InfoFormat("[{0}]: Module is enabled.", Name); } public void Close() { } public string Name { get { return "EnvironmentModule"; } } public Type ReplaceableInterface { get { return null; } } public void AddRegion(Scene scene) { if (!Enabled) return; scene.RegisterModuleInterface(this); m_scene = scene; regionID = scene.RegionInfo.RegionID; } public void RegionLoaded(Scene scene) { if (!Enabled) return; m_estateModule = scene.RequestModuleInterface(); if (m_estateModule is null) { Enabled = false; return; } m_eventQueue = m_scene.RequestModuleInterface(); if (m_eventQueue is null) { Enabled = false; return; } m_assetService = m_scene.AssetService; if (m_assetService is null) { Enabled = false; return; } m_landChannel = m_scene.LandChannel; if (m_landChannel is null) { Enabled = false; return; } if (m_DefaultEnv is null) { AssetBase defEnv = m_assetService.Get(m_defaultDayAssetID); if(defEnv is not null) { byte[] envData = defEnv.Data; try { OSD oenv = OSDParser.Deserialize(envData); m_DefaultEnv = new ViewerEnvironment(); m_DefaultEnv.CycleFromOSD(oenv); } catch (Exception e) { m_DefaultEnv = null; m_log.Warn(string.Format("[Environment {0}] failed to decode default environment asset ", m_scene.Name), e); } } } m_DefaultEnv ??= new ViewerEnvironment(); string senv = scene.SimulationDataService.LoadRegionEnvironmentSettings(scene.RegionInfo.RegionID); if(!string.IsNullOrEmpty(senv)) { try { OSD oenv = OSDParser.Deserialize(senv); ViewerEnvironment VEnv = new(); if (oenv is OSDArray) { VEnv.FromWLOSD(oenv); StoreOnRegion(VEnv); m_log.Info($"[Environment {m_scene.Name}] migrated WindLight environment settings to EEP"); } else { VEnv.FromOSD(oenv); } scene.RegionEnvironment = VEnv; m_regionEnvVersion = VEnv.version; } catch (Exception e) { m_log.Error($"[Environment {m_scene.Name}] failed to load initial Environment {e.Message}"); scene.RegionEnvironment = null; m_regionEnvVersion = -1; } } else { scene.RegionEnvironment = null; m_regionEnvVersion = -1; } m_framets = 0; UpdateEnvTime(); scene.EventManager.OnRegisterCaps += OnRegisterCaps; scene.EventManager.OnFrame += UpdateEnvTime; scene.EventManager.OnAvatarEnteringNewParcel += OnAvatarEnteringNewParcel; } public void RemoveRegion(Scene scene) { if (!Enabled) return; scene.EventManager.OnRegisterCaps -= OnRegisterCaps; } #endregion #region IEnvironmentModule public void StoreOnRegion(ViewerEnvironment VEnv) { try { if (VEnv is null) { m_scene.SimulationDataService.RemoveRegionEnvironmentSettings(regionID); m_scene.RegionEnvironment = null; m_regionEnvVersion = -1; } else { m_regionEnvVersion++; VEnv.version = m_regionEnvVersion; OSD env = VEnv.ToOSD(); //m_scene.SimulationDataService.StoreRegionEnvironmentSettings(regionID, OSDParser.SerializeLLSDXmlString(env)); m_scene.SimulationDataService.StoreRegionEnvironmentSettings(regionID, OSDParser.SerializeLLSDNotationFull(env)); m_scene.RegionEnvironment = VEnv; } m_framets = 0; UpdateEnvTime(); } catch (Exception e) { m_log.Error($"[Environment {m_scene.Name}] failed to store Environment {e.Message}"); } } public void ResetEnvironmentSettings(UUID regionUUID) { if (!Enabled) return; StoreOnRegion(null); WindlightRefresh(0); } public void WindlightRefresh(int interpolate, bool forRegion = true) { List ls = null; m_scene.ForEachRootScenePresence(delegate (ScenePresence sp) { if(sp.IsInTransit || sp.IsNPC) return; IClientAPI client = sp.ControllingClient; if (!client.IsActive) return; uint vflags = client.GetViewerCaps(); if ((vflags & 0x8000) != 0) { if (forRegion) m_estateModule.HandleRegionInfoRequest(client); } else if ((vflags & 0x4000) != 0) m_eventQueue.WindlightRefreshEvent(interpolate, client.AgentId); else { ls ??= MakeLightShareData(); SendLightShare(client, ls); } }); } public void WindlightRefreshForced(IScenePresence isp, int interpolate) { List ls = null; IClientAPI client = isp.ControllingClient; if (!client.IsActive) return; uint vflags = client.GetViewerCaps(); if ((vflags & 0x8000) != 0) { ScenePresence sp = isp as ScenePresence; ILandObject lo = m_scene.LandChannel.GetLandObject(sp.AbsolutePosition.X, sp.AbsolutePosition.Y); if (lo is not null && lo.LandData is not null && lo.LandData.Environment is not null) lo.SendLandUpdateToClient(client); m_estateModule.HandleRegionInfoRequest(client); } else if ((vflags & 0x4000) != 0) m_eventQueue.WindlightRefreshEvent(interpolate, client.AgentId); else { ls ??= MakeLightShareData(); SendLightShare(client, ls); } } public void FromLightShare(RegionLightShareData ls) { if (!Enabled) return; ViewerEnvironment VEnv = new(); VEnv.FromLightShare(ls); StoreOnRegion(VEnv); WindlightRefresh(0); } public RegionLightShareData ToLightShare() { if (!Enabled) return new RegionLightShareData(); RegionLightShareData ls = null; try { ViewerEnvironment VEnv = m_scene.RegionEnvironment; if(VEnv is not null) ls = VEnv.ToLightShare(); } catch (Exception e) { m_log.ErrorFormat("[{0}]: Unable to convert environment to lightShare, Exception: {1} - {2}", Name, e.Message, e.StackTrace); } return ls ?? new RegionLightShareData(); } #endregion #region Events private void OnRegisterCaps(UUID agentID, Caps caps) { // m_log.DebugFormat("[{0}]: Register capability for agentID {1} in region {2}", // Name, agentID, caps.RegionName); caps.RegisterSimpleHandler("EnvironmentSettings", new SimpleStreamHandler("/" + UUID.Random(), delegate (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { processEnv(httpRequest, httpResponse, agentID); })); //Extended caps.RegisterSimpleHandler("ExtEnvironment", new SimpleStreamHandler("/" + UUID.Random(), delegate (IOSHttpRequest httpRequest, IOSHttpResponse httpResponse) { processExtEnv(httpRequest, httpResponse, agentID, caps); })); } #endregion private void processEnv(IOSHttpRequest request, IOSHttpResponse response, UUID agentID) { switch (request.HttpMethod) { case "POST": SetEnvironmentSettings(request, response, agentID); return; case "GET": GetEnvironmentSettings(response, agentID); return; default: { response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; return; } } } private void processExtEnv(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Caps caps) { switch(request.HttpMethod) { case "PUT": case "POST": SetExtEnvironmentSettings(request, response, agentID, caps); return; case "GET": GetExtEnvironmentSettings(request, response, agentID); return; case "DELETE": DeleteExtEnvironmentSettings(request, response, agentID); return; default: { response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; return; } } } private void DeleteExtEnvironmentSettings(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, UUID agentID) { int parcel = -1; if (httpRequest.Query.Count > 0) { if (httpRequest.Query.ContainsKey("parcelid")) { _ = Int32.TryParse((string)httpRequest.Query["parcelid"], out parcel); } } if(parcel == -1) { StoreOnRegion(null); WindlightRefresh(0); } else { ILandObject land = m_scene.LandChannel.GetLandObject(parcel); if (land is not null && land.LandData is not null) { land.StoreEnvironment(null); WindlightRefresh(0, false); } } osUTF8 sb = LLSDxmlEncode2.Start(); LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("messageID", UUID.Zero, sb); LLSDxmlEncode2.AddElem("regionID", regionID, sb); LLSDxmlEncode2.AddElem("success", true, sb); LLSDxmlEncode2.AddEndMap(sb); httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); httpResponse.StatusCode = (int)HttpStatusCode.OK; } private void GetExtEnvironmentSettings(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, UUID agentID) { int parcelid = -1; if (httpRequest.Query.Count > 0) { if (httpRequest.Query.ContainsKey("parcelid")) { _ = Int32.TryParse((string)httpRequest.Query["parcelid"], out parcelid); } } ScenePresence sp = m_scene.GetScenePresence(agentID); if (sp == null) { httpResponse.StatusCode = (int)HttpStatusCode.ServiceUnavailable; httpResponse.AddHeader("Retry-After", "5"); return; } ViewerEnvironment VEnv = null; if (sp.Environment is not null) VEnv = sp.Environment; else if (parcelid == -1) VEnv = GetRegionEnvironment(); else { if (m_scene.RegionInfo.EstateSettings.AllowEnvironmentOverride) { ILandObject land = m_scene.LandChannel.GetLandObject(parcelid); if(land is not null && land.LandData is not null && land.LandData.Environment is not null) VEnv = land.LandData.Environment; } if(VEnv is null) { OSD def = ViewerEnvironment.DefaultToOSD(regionID, parcelid); httpResponse.RawBuffer = OSDParser.SerializeLLSDXmlToBytes(def); httpResponse.StatusCode = (int)HttpStatusCode.OK; return; } } byte[] envBytes = VEnv.ToCapBytes(regionID, parcelid); if(envBytes is null) { osUTF8 sb = LLSDxmlEncode2.Start(); LLSDxmlEncode2.AddArray(sb); LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("messageID", UUID.Zero, sb); LLSDxmlEncode2.AddElem("regionID", regionID, sb); LLSDxmlEncode2.AddEndMap(sb); LLSDxmlEncode2.AddEndArray(sb); httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); } else httpResponse.RawBuffer = envBytes; httpResponse.StatusCode = (int)HttpStatusCode.OK; } private void SetExtEnvironmentSettings(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse, UUID agentID, Caps caps) { bool success = false; string message = "Could not process request"; int parcel = -1; int track = -1; osUTF8 sb = LLSDxmlEncode2.Start(); ScenePresence sp = m_scene.GetScenePresence(agentID); if (sp is null || sp.IsChildAgent || sp.IsNPC) { message = "Could not locate your avatar"; goto Error; } if (httpRequest.Query.Count > 0) { if (httpRequest.Query.ContainsKey("parcelid")) { if (!Int32.TryParse((string)httpRequest.Query["parcelid"], out parcel)) { message = "Failed to decode request"; goto Error; } } if (httpRequest.Query.ContainsKey("trackno")) { if (!Int32.TryParse((string)httpRequest.Query["trackno"], out track)) { message = "Failed to decode request"; goto Error; } } if (track != -1) { message = "Environment Track not supported"; goto Error; } } ViewerEnvironment VEnv; ILandObject lchannel; if (parcel == -1) { if (!m_scene.Permissions.CanIssueEstateCommand(agentID, false)) { message = "Insufficient estate permissions, settings has not been saved."; goto Error; } VEnv = m_scene.RegionEnvironment; lchannel = null; } else { lchannel = m_landChannel.GetLandObject(parcel); if(lchannel is null || lchannel.LandData is null) { message = "Could not locate requested parcel"; goto Error; } if (!m_scene.Permissions.CanEditParcelProperties(agentID, lchannel, GroupPowers.AllowEnvironment, true)) // wrong { message = "No permission to change parcel environment"; goto Error; } VEnv = lchannel.LandData.Environment; } try { OSD req = OSDParser.Deserialize(httpRequest.InputStream); if(req is OSDMap map) { if(map.TryGetValue("environment", out OSD env)) { // need a proper clone VEnv ??= m_DefaultEnv.Clone(); OSDMap evmap = env as OSDMap; if(evmap.TryGetValue("day_asset", out OSD tmp) && !evmap.ContainsKey("day_cycle")) { string id = tmp.AsString(); AssetBase asset = m_assetService.Get(id); if(asset is null || asset.Data is null || asset.Data.Length == 0) { httpResponse.StatusCode = (int)HttpStatusCode.NotFound; return; } try { OSD oenv = OSDParser.Deserialize(asset.Data); evmap.TryGetValue("day_name", out tmp); if(tmp is OSDString) VEnv.FromAssetOSD(tmp.AsString(), oenv); else VEnv.FromAssetOSD(null, oenv); } catch { httpResponse.StatusCode = (int)HttpStatusCode.NotFound; return; } } else VEnv.FromOSD(env); if(lchannel is null) { StoreOnRegion(VEnv); m_log.InfoFormat("[{0}]: ExtEnvironment region {1} settings from agentID {2} saved", Name, caps.RegionName, agentID); } else { lchannel.StoreEnvironment(VEnv); m_log.InfoFormat("[{0}]: ExtEnvironment parcel {1} of region {2} settings from agentID {3} saved", Name, parcel, caps.RegionName, agentID); } WindlightRefresh(0, lchannel is null); success = true; } } else if (req is OSDArray) { VEnv = new ViewerEnvironment(); VEnv.FromWLOSD(req); StoreOnRegion(VEnv); success = true; WindlightRefresh(0); m_log.InfoFormat("[{0}]: ExtEnvironment region {1} settings from agentID {2} saved", Name, caps.RegionName, agentID); LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("messageID", UUID.Zero, sb); LLSDxmlEncode2.AddElem("regionID", regionID, sb); LLSDxmlEncode2.AddElem("success", success, sb); LLSDxmlEncode2.AddEndMap(sb); httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); httpResponse.StatusCode = (int)HttpStatusCode.OK; return; } } catch (Exception e) { m_log.ErrorFormat("[{0}]: ExtEnvironment settings not saved for region {1}, Exception: {2} - {3}", Name, caps.RegionName, e.Message, e.StackTrace); success = false; message = String.Format("ExtEnvironment Set for region {0} has failed, settings not saved.", caps.RegionName); } Error: LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("success", success, sb); if(!success) LLSDxmlEncode2.AddElem("message", message, sb); LLSDxmlEncode2.AddEndMap(sb); httpResponse.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); httpResponse.StatusCode = (int)HttpStatusCode.OK; } private void GetEnvironmentSettings(IOSHttpResponse response, UUID agentID) { // m_log.DebugFormat("[{0}]: Environment GET handle for agentID {1} in region {2}", // Name, agentID, caps.RegionName); ViewerEnvironment VEnv = null; ScenePresence sp = m_scene.GetScenePresence(agentID); if (sp is null) { response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; response.AddHeader("Retry-After", "5"); return; } if (sp.Environment is not null) VEnv = sp.Environment; else { if(m_scene.RegionInfo.EstateSettings.AllowEnvironmentOverride) { ILandObject land = m_scene.LandChannel.GetLandObject(sp.AbsolutePosition.X, sp.AbsolutePosition.Y); if (land is not null && land.LandData is not null && land.LandData.Environment is not null) VEnv = land.LandData.Environment; } } VEnv ??= GetRegionEnvironment(); byte[] envBytes = VEnv.ToCapWLBytes(UUID.Zero, regionID); if(envBytes is null) { osUTF8 sb = LLSDxmlEncode2.Start(); LLSDxmlEncode2.AddArray(sb); LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("messageID", UUID.Zero, sb); LLSDxmlEncode2.AddElem("regionID", regionID, sb); LLSDxmlEncode2.AddEndMap(sb); LLSDxmlEncode2.AddEndArray(sb); response.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); } else response.RawBuffer = envBytes; response.StatusCode = (int)HttpStatusCode.OK; } private void SetEnvironmentSettings(IOSHttpRequest request, IOSHttpResponse response, UUID agentID) { // m_log.DebugFormat("[{0}]: Environment SET handle from agentID {1} in region {2}", // Name, agentID, caps.RegionName); bool success = false; string fail_reason = ""; if (!m_scene.Permissions.CanIssueEstateCommand(agentID, false)) { fail_reason = "Insufficient estate permissions, settings has not been saved."; goto Error; } ScenePresence sp = m_scene.GetScenePresence(agentID); if (sp is null || sp.IsChildAgent || sp.IsNPC) { response.StatusCode = (int)HttpStatusCode.NotFound; return; } if (sp.Environment is not null) { fail_reason = "The environment you see is a forced one. Disable if on control object or tp out and back to region"; goto Error; } ILandObject land = m_scene.LandChannel.GetLandObject(sp.AbsolutePosition.X, sp.AbsolutePosition.Y); if (land is not null && land.LandData is not null && land.LandData.Environment is not null) { fail_reason = "The parcel where you are has own environment set. You need a updated viewer to change environment"; goto Error; } try { ViewerEnvironment VEnv = new(); OSD env = OSDParser.Deserialize(request.InputStream); VEnv.FromWLOSD(env); StoreOnRegion(VEnv); WindlightRefresh(0); m_log.InfoFormat("[{0}]: New Environment settings has been saved from agentID {1} in region {2}", Name, agentID, m_scene.Name); success = true; } catch (Exception e) { m_log.ErrorFormat("[{0}]: Environment settings has not been saved for region {1}, Exception: {2} - {3}", Name, m_scene.Name, e.Message, e.StackTrace); success = false; fail_reason = String.Format("Environment Set for region {0} has failed, settings not saved.", m_scene.Name); } Error: osUTF8 sb = LLSDxmlEncode2.Start(); LLSDxmlEncode2.AddMap(sb); LLSDxmlEncode2.AddElem("messageID", UUID.Zero, sb); LLSDxmlEncode2.AddElem("regionID", regionID, sb); LLSDxmlEncode2.AddElem("success", success, sb); if(!success) LLSDxmlEncode2.AddElem("fail_reason", fail_reason, sb); LLSDxmlEncode2.AddEndMap(sb); response.RawBuffer = LLSDxmlEncode2.EndToBytes(sb); response.StatusCode = (int)HttpStatusCode.OK; } public byte[] GetDefaultAssetData(int type) { OSD osddata; switch(type) { case 0: SkyData sky = new() { Name = "DefaultSky" }; osddata = sky.ToOSD(); break; case 1: WaterData water = new() { Name = "DefaultWater" }; osddata = water.ToOSD(); break; case 2: DayCycle day = new() { Name="New Daycycle" }; DayCycle.TrackEntry te = new(); WaterData dwater = new(){ Name = "DefaultWater" }; day.waterframes["DefaultWater"] = dwater; te.time = 0; te.frameName = "DefaultWater"; day.waterTrack.Add(te); SkyData dsky = new() { Name = "DefaultSky" } ; day.skyframes["DefaultSky"] = dsky; te.time = 0; te.frameName = "DefaultSky"; day.skyTrack0.Add(te); osddata = day.ToOSD(); break; default: return null; } return OSDParser.SerializeLLSDNotationToBytes(osddata,true); } public UUID GetDefaultAsset(int type) { return type switch { 0 => m_defaultSkyAssetUUID, 1 => m_defaultWaterAssetUUID, 2 => m_defaultDayAssetUUID, _ => UUID.Zero, }; } public List MakeLightShareData() { if(m_scene.RegionEnvironment == null) return null; RegionLightShareData wl = ToLightShare(); byte[] mBlock = new Byte[249]; int pos = 0; wl.waterColor.ToBytes(mBlock, 0); pos += 12; Utils.FloatToBytes(wl.waterFogDensityExponent).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.underwaterFogModifier).CopyTo(mBlock, pos); pos += 4; wl.reflectionWaveletScale.ToBytes(mBlock, pos); pos += 12; Utils.FloatToBytes(wl.fresnelScale).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.fresnelOffset).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.refractScaleAbove).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.refractScaleBelow).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.blurMultiplier).CopyTo(mBlock, pos); pos += 4; wl.bigWaveDirection.ToBytes(mBlock, pos); pos += 8; wl.littleWaveDirection.ToBytes(mBlock, pos); pos += 8; wl.normalMapTexture.ToBytes(mBlock, pos); pos += 16; wl.horizon.ToBytes(mBlock, pos); pos += 16; Utils.FloatToBytes(wl.hazeHorizon).CopyTo(mBlock, pos); pos += 4; wl.blueDensity.ToBytes(mBlock, pos); pos += 16; Utils.FloatToBytes(wl.hazeDensity).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.densityMultiplier).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.distanceMultiplier).CopyTo(mBlock, pos); pos += 4; wl.sunMoonColor.ToBytes(mBlock, pos); pos += 16; Utils.FloatToBytes(wl.sunMoonPosition).CopyTo(mBlock, pos); pos += 4; wl.ambient.ToBytes(mBlock, pos); pos += 16; Utils.FloatToBytes(wl.eastAngle).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.sunGlowFocus).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.sunGlowSize).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.sceneGamma).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.starBrightness).CopyTo(mBlock, pos); pos += 4; wl.cloudColor.ToBytes(mBlock, pos); pos += 16; wl.cloudXYDensity.ToBytes(mBlock, pos); pos += 12; Utils.FloatToBytes(wl.cloudCoverage).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.cloudScale).CopyTo(mBlock, pos); pos += 4; wl.cloudDetailXYDensity.ToBytes(mBlock, pos); pos += 12; Utils.FloatToBytes(wl.cloudScrollX).CopyTo(mBlock, pos); pos += 4; Utils.FloatToBytes(wl.cloudScrollY).CopyTo(mBlock, pos); pos += 4; Utils.UInt16ToBytes(wl.maxAltitude).CopyTo(mBlock, pos); pos += 2; mBlock[pos] = Convert.ToByte(wl.cloudScrollXLock); pos++; mBlock[pos] = Convert.ToByte(wl.cloudScrollYLock); pos++; mBlock[pos] = Convert.ToByte(wl.drawClassicClouds); // pos++; List param = new() { mBlock }; return param; } public void SendLightShare(IClientAPI client, List param) { if(param is null || param.Count == 0) client.SendGenericMessage("WindlightReset", UUID.Random(), new List()); else client.SendGenericMessage("Windlight", UUID.Random(), param); } private void OnAvatarEnteringNewParcel(ScenePresence sp, int localLandID, UUID regionID) { if (sp.Environment is not null || sp.IsNPC) return; if (!m_scene.RegionInfo.EstateSettings.AllowEnvironmentOverride) return; IClientAPI client = sp.ControllingClient; uint vflags = client.GetViewerCaps(); if((vflags & 0x8000) != 0) return; m_eventQueue.WindlightRefreshEvent(1, client.AgentId); } private void UpdateEnvTime() { double now = Util.GetTimeStamp(); if (now - m_framets < 2.5) // this will be a conf option return; m_framets = now; UpdateClientsSunTime(); } private void UpdateClientsSunTime() { if(m_scene.GetNumberOfClients() == 0) return; //m_log.DebugFormat("{0} {1} {2} {3}", dayFrac, eepDayFrac, wldayFrac, Util.UnixTimeSinceEpoch_uS()); m_scene.ForEachRootScenePresence(delegate (ScenePresence sp) { if(sp.IsDeleted || sp.IsInTransit || sp.IsNPC) return; ViewerEnvironment VEnv; if(sp.Environment is not null) VEnv = sp.Environment; else VEnv = GetEnvironment(sp.AbsolutePosition.X, sp.AbsolutePosition.Y); float dayFrac = GetDayFractionTime(VEnv); IClientAPI client = sp.ControllingClient; uint vflags = client.GetViewerCaps(); if ((vflags & 0x8000) != 0) { client.SendViewerTime(Vector3.Zero, dayFrac * Utils.TWO_PI); return; } if (dayFrac <= 0.25f) dayFrac += 1.5f; else if (dayFrac > 0.75f) dayFrac += 0.5f; else if (dayFrac >= 0.333333f) dayFrac = 3f * dayFrac - 1f; else dayFrac = 3f * dayFrac + 1f; dayFrac = Utils.Clamp(dayFrac, 0, 2f); dayFrac *= Utils.PI; client.SendViewerTime(Vector3.Zero, dayFrac); }); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public ViewerEnvironment GetEnvironment(Vector3 pos) { ILandObject lo = m_landChannel.GetLandObject(pos.X, pos.Y); if (lo != null && lo.LandData != null && lo.LandData.Environment != null) return lo.LandData.Environment; return m_scene.RegionEnvironment ?? m_DefaultEnv; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public ViewerEnvironment GetEnvironment(float x, float y) { ILandObject lo = m_landChannel.GetLandObject(x, y); if (lo != null && lo.LandData != null && lo.LandData.Environment != null) return lo.LandData.Environment; return m_scene.RegionEnvironment ?? m_DefaultEnv; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public ViewerEnvironment GetRegionEnvironment() { return m_scene.RegionEnvironment ?? m_DefaultEnv; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public float GetDayFractionTime(ViewerEnvironment env) { double dayfrac = env.DayLength; dayfrac = ((Util.UnixTimeSinceEpochSecs() + env.DayOffset) % dayfrac) / dayfrac; return (float)Utils.Clamp(dayfrac, 0, 1); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public float GetRegionDayFractionTime() { return GetDayFractionTime(GetRegionEnvironment()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public int GetDayLength(ViewerEnvironment env) { return env.DayLength; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public int GetDayOffset(ViewerEnvironment env) { return env.DayOffset; } public Vector3 GetSunDir(ViewerEnvironment env, float altitude) { env.getPositions_sundir(altitude, GetDayFractionTime(env), out Vector3 sundir); return sundir; } public Quaternion GetSunRot(ViewerEnvironment env, float altitude) { env.getPositions_sunrot(altitude, GetDayFractionTime(env), out Quaternion sunrot); return sunrot; } public Vector3 GetMoonDir(ViewerEnvironment env, float altitude) { env.getPositions_moondir(altitude, GetDayFractionTime(env), out Vector3 moondir); return moondir; } public Quaternion GetMoonRot(ViewerEnvironment env, float altitude) { env.getPositions_moonrot(altitude, GetDayFractionTime(env), out Quaternion moonrot); return moonrot; } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRegionDayLength() { return GetRegionEnvironment().DayLength; } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetRegionDayOffset() { return GetRegionEnvironment().DayOffset; } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetRegionSunDir(float altitude) { return GetSunDir(GetRegionEnvironment(), altitude); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Quaternion GetRegionSunRot(float altitude) { return GetSunRot(GetRegionEnvironment(), altitude); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetRegionMoonDir(float altitude) { return GetMoonDir(GetRegionEnvironment(), altitude); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Quaternion GetRegionMoonRot(float altitude) { return GetMoonRot(GetRegionEnvironment(), altitude); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetDayLength(Vector3 pos) { return GetEnvironment(pos.X, pos.Y).DayLength; } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetDayOffset(Vector3 pos) { return GetEnvironment(pos.X, pos.Y).DayOffset; } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetSunDir(Vector3 pos) { return GetSunDir(GetEnvironment(pos.X, pos.Y), pos.Z); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Quaternion GetSunRot(Vector3 pos) { return GetSunRot(GetEnvironment(pos.X, pos.Y), pos.Z); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 GetMoonDir(Vector3 pos) { return GetMoonDir(GetEnvironment(pos.X, pos.Y), pos.Z); } [System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.AggressiveInlining)] public Quaternion GetMoonRot(Vector3 pos) { return GetMoonRot(GetEnvironment(pos.X, pos.Y), pos.Z); } } }