Files
opensim/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/OSSL_Api.cs

6593 lines
237 KiB
C#

/*
* 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 log4net;
using Nini.Config;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Scenes.Scripting;
using OpenSim.Region.ScriptEngine.Interfaces;
using OpenSim.Region.ScriptEngine.Shared.Api.Interfaces;
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using OpenSim.Services.Connectors.Hypergrid;
using OpenSim.Services.Interfaces;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Security.Cryptography;
using GridRegion = OpenSim.Services.Interfaces.GridRegion;
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
using PermissionMask = OpenSim.Framework.PermissionMask;
using TPFlags = OpenSim.Framework.Constants.TeleportFlags;
#pragma warning disable IDE1006
namespace OpenSim.Region.ScriptEngine.Shared.Api
{
//////////////////////////////////////////////////////////////
//
// Level description
//
// None - Function is no threat at all. It doesn't constitute
// an threat to either users or the system and has no
// known side effects
//
// Nuisance - Abuse of this command can cause a nuisance to the
// region operator, such as log message spew
//
// VeryLow - Extreme levels ob abuse of this function can cause
// impaired functioning of the region, or very gullible
// users can be tricked into experiencing harmless effects
//
// Low - Intentional abuse can cause crashes or malfunction
// under certain circumstances, which can easily be rectified,
// or certain users can be tricked into certain situations
// in an avoidable manner.
//
// Moderate - Intentional abuse can cause denial of service and crashes
// with potential of data or state loss, or trusting users
// can be tricked into embarrassing or uncomfortable
// situationsa.
//
// High - Casual abuse can cause impaired functionality or temporary
// denial of service conditions. Intentional abuse can easily
// cause crashes with potential data loss, or can be used to
// trick experienced and cautious users into unwanted situations,
// or changes global data permanently and without undo ability
// Malicious scripting can allow theft of content
//
// VeryHigh - Even normal use may, depending on the number of instances,
// or frequency of use, result in severe service impairment
// or crash with loss of data, or can be used to cause
// unwanted or harmful effects on users without giving the
// user a means to avoid it.
//
// Severe - Even casual use is a danger to region stability, or function
// allows console or OS command execution, or function allows
// taking money without consent, or allows deletion or
// modification of user data, or allows the compromise of
// sensitive data by design.
// flags functions threat control
public enum AllowedControlFlags : int
{
NONE = 0,
PARCEL_OWNER = 1,
PARCEL_GROUP_MEMBER = 1 << 1,
ESTATE_MANAGER = 1 << 2,
ESTATE_OWNER = 1 << 3,
ACTIVE_GOD = 1 << 4,
GOD = 1 << 5,
GRID_GOD = 1 << 6,
// internal
THREATLEVEL = 1 << 28,
OWNERUUID = 1 << 29,
CREATORUUID = 1 << 30,
//int thingie = 1 << 31,
ALL = 0x0FFFFFFF
}
class FunctionPerms
{
public List<UUID> AllowedCreators;
public List<UUID> AllowedOwners;
public AllowedControlFlags AllowedControl = AllowedControlFlags.NONE;
}
[Serializable]
public class OSSL_Api : IOSSL_Api, IScriptApi
{
public const string GridInfoServiceConfigSectionName = "GridInfoService";
// shared things
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly object m_OSSLLock = new();
private static bool m_doneSharedInit = false;
internal static bool m_OSFunctionsEnabled = false;
internal static TimeZoneInfo PSTTimeZone = null;
internal static bool m_PermissionErrortoOwner = false;
internal static ThreatLevel m_MaxThreatLevel = ThreatLevel.VeryLow;
internal static float m_ScriptDelayFactor = 1.0f;
internal static float m_ScriptDistanceFactor = 1.0f;
internal static IConfig m_osslconfig;
internal static ConcurrentDictionary<string, FunctionPerms> m_FunctionPerms = new();
internal IScriptEngine m_ScriptEngine;
internal LSL_Api m_LSL_Api = null; // get a reference to the LSL API so we can call methods housed there
internal SceneObjectPart m_host;
internal TaskInventoryItem m_item;
protected IUrlModule m_UrlModule = null;
protected ISoundModule m_SoundModule = null;
protected IEnvironmentModule m_envModule = null;
protected IGroupsModule m_groupsModule = null;
protected IMessageTransferModule m_TransferModule = null;
public Scene World;
public void Initialize(IScriptEngine scriptEngine, SceneObjectPart host, TaskInventoryItem item)
{
//private init
m_ScriptEngine = scriptEngine;
World = scriptEngine.World;
m_host = host;
m_item = item;
m_UrlModule = m_ScriptEngine.World.RequestModuleInterface<IUrlModule>();
m_SoundModule = m_ScriptEngine.World.RequestModuleInterface<ISoundModule>();
m_envModule = m_ScriptEngine.World.RequestModuleInterface<IEnvironmentModule>();
m_groupsModule = m_ScriptEngine.World.RequestModuleInterface<IGroupsModule>();
m_TransferModule = m_ScriptEngine.World.RequestModuleInterface<IMessageTransferModule>();
//private init
lock (m_OSSLLock)
{
if(m_doneSharedInit)
return;
m_osslconfig = m_ScriptEngine.ConfigSource.Configs["OSSL"];
m_osslconfig ??= m_ScriptEngine.Config;
if (m_osslconfig.GetBoolean("AllowOSFunctions", true))
{
m_OSFunctionsEnabled = true;
// m_log.Warn("[OSSL] OSSL FUNCTIONS ENABLED");
}
m_PermissionErrortoOwner = m_osslconfig.GetBoolean("PermissionErrorToOwner", m_PermissionErrortoOwner);
m_ScriptDelayFactor = m_ScriptEngine.Config.GetFloat("ScriptDelayFactor", 1.0f);
m_ScriptDistanceFactor = m_ScriptEngine.Config.GetFloat("ScriptDistanceLimitFactor", 1.0f);
string risk = m_osslconfig.GetString("OSFunctionThreatLevel", "VeryLow");
switch (risk)
{
case "NoAccess":
m_MaxThreatLevel = ThreatLevel.NoAccess;
break;
case "None":
m_MaxThreatLevel = ThreatLevel.None;
break;
case "VeryLow":
m_MaxThreatLevel = ThreatLevel.VeryLow;
break;
case "Low":
m_MaxThreatLevel = ThreatLevel.Low;
break;
case "Moderate":
m_MaxThreatLevel = ThreatLevel.Moderate;
break;
case "High":
m_MaxThreatLevel = ThreatLevel.High;
break;
case "VeryHigh":
m_MaxThreatLevel = ThreatLevel.VeryHigh;
break;
case "Severe":
m_MaxThreatLevel = ThreatLevel.Severe;
break;
default:
break;
}
try
{
PSTTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
}
catch
{
PSTTimeZone = null;
}
if(PSTTimeZone == null)
{
try
{
PSTTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
}
catch
{
PSTTimeZone = null;
}
}
m_doneSharedInit = true;
}
}
internal static void OSSLError(string msg)
{
throw new ScriptException("OSSL Runtime Error: " + msg);
}
/// <summary>
/// Initialize the LSL interface.
/// </summary>
/// <remarks>
/// FIXME: This is an abomination. We should be able to set this up earlier but currently we have no
/// guarantee the interface is present on Initialize(). There needs to be another post initialize call from
/// ScriptInstance.
/// </remarks>
private void InitLSL()
{
if (m_LSL_Api == null)
{
m_LSL_Api = (LSL_Api)m_ScriptEngine.GetApi(m_item.ItemID, "LSL");
if (m_LSL_Api == null)
throw new Exception("OSSL failed to load LSL API");
}
}
//
//Dumps an error message on the debug console.
//
internal void OSSLShoutError(string message)
{
if (message.Length > 1023)
message = message[..1023];
World.SimChat(Utils.StringToBytes(message),
ChatTypeEnum.Shout, ScriptBaseClass.DEBUG_CHANNEL, m_host.ParentGroup.RootPart.AbsolutePosition, m_host.Name, m_host.UUID, false);
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
wComm?.DeliverMessage(ChatTypeEnum.Shout, ScriptBaseClass.DEBUG_CHANNEL, m_host.Name, m_host.UUID, message);
if (m_item is not null)
m_ScriptEngine.SleepScript(m_item.ItemID, 1000);
//else
// Thread.Sleep(1000);
}
// Returns if OSSL is enabled. Throws a script exception if OSSL is not allowed..
// for safe funtions always active
public static void CheckThreatLevel()
{
if (!m_OSFunctionsEnabled)
OSSLError("permission denied. All unsafe OSSL funtions disabled"); // throws
}
// Returns if the function is allowed. Throws a script exception if not allowed.
public void CheckThreatLevel(ThreatLevel level, string function)
{
if (!m_OSFunctionsEnabled)
{
if (m_PermissionErrortoOwner)
throw new ScriptException("(OWNER)OSSL Permission Error: All unsafe OSSL funtions disabled");
else
throw new ScriptException("OSSL Permission Error: All unsafe OSSL funtions disabled");
}
string reasonWhyNot = CheckThreatLevelTest(level, function);
if (!string.IsNullOrEmpty(reasonWhyNot))
{
if (m_PermissionErrortoOwner)
throw new ScriptException("(OWNER)OSSL Permission Error: " + reasonWhyNot);
else
throw new ScriptException("OSSL Permission Error: " + reasonWhyNot);
}
}
// Check to see if function is allowed. Returns an empty string if function permitted
// or a string explaining why this function can't be used.
private string CheckThreatLevelTest(ThreatLevel level, string function)
{
if (!m_FunctionPerms.TryGetValue(function, out FunctionPerms perms))
{
perms = new FunctionPerms();
string ownerPerm = m_osslconfig.GetString("Allow_" + function, "");
string creatorPerm = m_osslconfig.GetString("Creators_" + function, "");
if (string.IsNullOrWhiteSpace(ownerPerm) && string.IsNullOrWhiteSpace(creatorPerm))
{
// Default Threat level check
perms.AllowedControl = AllowedControlFlags.THREATLEVEL;
}
else
{
if (bool.TryParse(ownerPerm, out bool allowed))
{
// Boolean given
if (allowed)
{
// Allow globally
perms.AllowedControl = AllowedControlFlags.ALL;
}
// false is fallback
}
else
{
string[] ids;
bool error = false;
if (!string.IsNullOrWhiteSpace(ownerPerm))
{
ids = ownerPerm.Split(Util.SplitCommaArray);
foreach (string id in ids)
{
string current = id.Trim();
current = current.ToUpper();
switch(current)
{
case "":
break;
case "PARCEL_OWNER":
perms.AllowedControl |= AllowedControlFlags.PARCEL_OWNER;
break;
case "PARCEL_GROUP_MEMBER":
perms.AllowedControl |= AllowedControlFlags.PARCEL_GROUP_MEMBER;
break;
case "ESTATE_MANAGER":
perms.AllowedControl |= AllowedControlFlags.ESTATE_MANAGER;
break;
case "ESTATE_OWNER":
perms.AllowedControl |= AllowedControlFlags.ESTATE_OWNER;
break;
case "ACTIVE_GOD":
perms.AllowedControl |= AllowedControlFlags.ACTIVE_GOD;
break;
case "GOD":
perms.AllowedControl |= AllowedControlFlags.GOD;
break;
case "GRID_GOD":
perms.AllowedControl |= AllowedControlFlags.GRID_GOD;
break;
default:
{
if (UUID.TryParse(current, out UUID uuid))
{
if (uuid.IsNotZero())
{
perms.AllowedOwners ??= new List<UUID>();
perms.AllowedControl |= AllowedControlFlags.OWNERUUID;
perms.AllowedOwners.Add(uuid);
}
}
else
{
error = true;
}
break;
}
}
}
if (error)
m_log.WarnFormat("[OSSLENABLE]: error parsing line Allow_{0} = {1}", function, ownerPerm);
}
error = false;
if (!string.IsNullOrWhiteSpace(creatorPerm))
{
ids = creatorPerm.Split(Util.SplitCommaArray);
foreach (string id in ids)
{
string current = id.Trim();
if (UUID.TryParse(current, out UUID uuid))
{
if (!uuid.IsZero())
{
perms.AllowedCreators ??= new List<UUID>();
perms.AllowedControl |= AllowedControlFlags.CREATORUUID;
perms.AllowedCreators.Add(uuid);
}
}
else
{
error = true;
}
}
if (error)
m_log.WarnFormat("[OSSLENABLE]: error parsing line Creators_{0} = {1}", function, creatorPerm);
}
// both empty fallback as disabled
}
}
m_FunctionPerms.TryAdd(function,perms);
}
AllowedControlFlags functionControl = perms.AllowedControl;
if (functionControl == AllowedControlFlags.THREATLEVEL)
{
// Allow / disallow by threat level
if (level <= m_MaxThreatLevel)
return string.Empty;
return $"{function} permission denied. Allowed threat level is {m_MaxThreatLevel} but function threat level is {level}";
}
if (functionControl == 0)
return $"{function} disabled in region configuration";
if (functionControl == AllowedControlFlags.ALL)
return string.Empty;
if (((functionControl & AllowedControlFlags.OWNERUUID) != 0) && perms.AllowedOwners.Contains(m_host.OwnerID))
{
// prim owner is in the list of allowed owners
return string.Empty;
}
if ((functionControl & AllowedControlFlags.PARCEL_OWNER) != 0)
{
ILandObject land = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
if (land.LandData.OwnerID.Equals(m_item.OwnerID))
return string.Empty;
}
//OSSL only may be used if object is in the same group as the parcel
if ((functionControl & AllowedControlFlags.PARCEL_GROUP_MEMBER) != 0)
{
ILandObject land = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
if (land.LandData.GroupID.Equals(m_item.GroupID) && !land.LandData.GroupID.IsZero())
return string.Empty;
}
//Only regionowners may use the function
if ((functionControl & AllowedControlFlags.ESTATE_OWNER) != 0)
{
if (World.RegionInfo.EstateSettings.EstateOwner.Equals(m_item.OwnerID))
return string.Empty;
}
//Only Estate Managers may use the function
if ((functionControl & AllowedControlFlags.ESTATE_MANAGER) != 0)
{
//Only Estate Managers may use the function
if (World.RegionInfo.EstateSettings.IsEstateManagerOrOwner(m_item.OwnerID))
return string.Empty;
}
//Only grid gods may use the function
if ((functionControl & AllowedControlFlags.GRID_GOD) != 0)
{
if (World.Permissions.IsGridGod(m_item.OwnerID))
return string.Empty;
}
//Any god may use the function
if ((functionControl & AllowedControlFlags.GOD) != 0)
{
if (World.Permissions.IsAdministrator(m_item.OwnerID))
return string.Empty;
}
//Only active gods may use the function
if ((functionControl & AllowedControlFlags.ACTIVE_GOD) != 0)
{
ScenePresence sp = World.GetScenePresence(m_item.OwnerID);
if (sp is not null && !sp.IsDeleted && sp.IsGod)
return string.Empty;
}
// else if no creators its denied
if((functionControl & AllowedControlFlags.CREATORUUID) == 0)
return $"{function} permission denied";
if (!perms.AllowedCreators.Contains(m_item.CreatorID))
return($"{function} permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission");
if (m_item.CreatorID.NotEqual(m_item.OwnerID))
{
if ((m_item.CurrentPermissions & (uint)PermissionMask.Modify) != 0)
return $"{function} permission denied. Script creator is not prim owner";
}
return string.Empty;
}
internal void OSSLDeprecated(string function, string replacement)
{
OSSLShoutError($"Use of function {function} is deprecated. Use {replacement} instead.");
}
protected void ScriptSleep(int delay)
{
delay = (int)(delay * m_ScriptDelayFactor);
if (delay < 10)
return;
if(m_item is not null)
m_ScriptEngine.SleepScript(m_item.ItemID, delay);
}
public LSL_Integer osSetTerrainHeight(int x, int y, double val)
{
CheckThreatLevel(ThreatLevel.High, "osSetTerrainHeight");
return SetTerrainHeight(x, y, val);
}
public LSL_Integer osTerrainSetHeight(int x, int y, double val)
{
CheckThreatLevel(ThreatLevel.High, "osTerrainSetHeight");
OSSLDeprecated("osTerrainSetHeight", "osSetTerrainHeight");
return SetTerrainHeight(x, y, val);
}
private LSL_Integer SetTerrainHeight(int x, int y, double val)
{
if (x > (World.RegionInfo.RegionSizeX - 1) || x < 0 || y > (World.RegionInfo.RegionSizeY - 1) || y < 0)
OSSLError("osSetTerrainHeight: Coordinate out of bounds");
if (World.Permissions.CanTerraformLand(m_host.OwnerID, new Vector3(x, y, 0)))
{
World.Heightmap[x, y] = (float)val;
return 1;
}
return 0;
}
public LSL_Float osGetTerrainHeight(int x, int y)
{
CheckThreatLevel();
return GetTerrainHeight(x, y);
}
public LSL_Float osTerrainGetHeight(int x, int y)
{
CheckThreatLevel();
OSSLDeprecated("osTerrainGetHeight", "osGetTerrainHeight");
return GetTerrainHeight(x, y);
}
private LSL_Float GetTerrainHeight(int x, int y)
{
if (x > (World.RegionInfo.RegionSizeX - 1) || x < 0 || y > (World.RegionInfo.RegionSizeY - 1) || y < 0)
OSSLError("osGetTerrainHeight: Coordinate out of bounds");
return World.Heightmap[x, y];
}
double m_lastosTerrainFlush = 0;
public void osTerrainFlush()
{
double now = Util.GetTimeStamp();
if(now - m_lastosTerrainFlush < 60)
return;
m_lastosTerrainFlush = now;
CheckThreatLevel(ThreatLevel.VeryLow, "osTerrainFlush");
ITerrainModule terrainModule = World.RequestModuleInterface<ITerrainModule>();
terrainModule?.TaintTerrain();
}
public int osRegionRestart(double seconds)
{
// This is High here because region restart is not reliable
// it may result in the region staying down or becoming
// unstable. This should be changed to Low or VeryLow once
// The underlying functionality is fixed, since the security
// as such is sound
//
CheckThreatLevel(ThreatLevel.High, "osRegionRestart");
IRestartModule restartModule = World.RequestModuleInterface<IRestartModule>();
if (World.Permissions.CanIssueEstateCommand(m_host.OwnerID, false) && (restartModule != null))
{
if (seconds < 15)
{
restartModule.AbortRestart("Region restart has been aborted\n");
return 1;
}
RegionRestart(seconds, string.Empty);
return 1;
}
return 0;
}
public int osRegionRestart(double seconds, string msg)
{
CheckThreatLevel(ThreatLevel.High, "osRegionRestart");
IRestartModule restartModule = World.RequestModuleInterface<IRestartModule>();
if (World.Permissions.CanIssueEstateCommand(m_host.OwnerID, false) && (restartModule != null))
{
if (seconds < 15)
{
restartModule.AbortRestart("Region restart has been aborted\n");
return 1;
}
RegionRestart(seconds, msg);
return 1;
}
return 0;
}
private void RegionRestart(double seconds, string msg)
{
IRestartModule restartModule = World.RequestModuleInterface<IRestartModule>();
List<int> times = new();
while (seconds > 0)
{
times.Add((int)seconds);
if (seconds > 300)
seconds -= 120;
else if (seconds > 120)
seconds -= 60;
else if (seconds > 60)
seconds -= 30;
else
seconds -= 15;
}
if (msg.Length == 0)
restartModule.ScheduleRestart(UUID.Zero, "Region: " + World.RegionInfo.RegionName + " is about to restart.\n\nIf you stay here you will be logged out.\n\n\nTime remaining: {0}.\n", times.ToArray(), true);
else
restartModule.ScheduleRestart(UUID.Zero, msg + "\n\nTime remaining: {0}.\n", times.ToArray(), true);
}
public void osRegionNotice(string msg)
{
CheckThreatLevel(ThreatLevel.High, "osRegionNotice");
IDialogModule dm = World.RequestModuleInterface<IDialogModule>();
if (dm == null)
return;
if (!World.Permissions.CanIssueEstateCommand(m_host.OwnerID, false))
return;
dm.SendGeneralAlert(msg + "\n");
}
public void osRegionNotice(LSL_Key agentID, string msg)
{
CheckThreatLevel(ThreatLevel.High, "osRegionNotice");
if (!World.Permissions.CanIssueEstateCommand(m_host.OwnerID, false))
return;
IDialogModule dm = World.RequestModuleInterface<IDialogModule>();
if (dm is null)
return;
if (!UUID.TryParse(agentID, out UUID avatarID))
return;
if (!World.TryGetScenePresence(avatarID, out ScenePresence sp))
return;
if (sp is null || sp.IsChildAgent || sp.IsDeleted || sp.IsInTransit || sp.IsNPC)
return;
dm.SendAlertToUser(sp.ControllingClient, msg + "\n", false);
}
public void osSetRot(UUID target, Quaternion rotation)
{
// if enabled It can be used to destroy
// arbitrary builds the user would normally have no rights to
//
CheckThreatLevel(ThreatLevel.VeryHigh, "osSetRot");
if (World.Entities.TryGetValue(target, out EntityBase entity))
{
if (entity is SceneObjectGroup sog)
sog.UpdateGroupRotationR(rotation);
else if (entity is ScenePresence sop)
sop.Rotation = rotation;
}
else
{
OSSLError("osSetRot: Invalid target");
}
}
public string osSetDynamicTextureURL(string dynamicID, string contentType, string url, string extraParams,
int timer)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osSetDynamicTextureURL");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
UUID createdTexture =
textureManager.AddDynamicTextureURL(World.RegionInfo.RegionID, m_host.UUID, contentType, url,
extraParams);
return createdTexture.ToString();
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public string osSetDynamicTextureURLBlend(string dynamicID, string contentType, string url, string extraParams,
int timer, int alpha)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osSetDynamicTextureURLBlend");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
UUID createdTexture =
textureManager.AddDynamicTextureURL(World.RegionInfo.RegionID, m_host.UUID, contentType, url,
extraParams, true, (byte) alpha);
return createdTexture.ToString();
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public string osSetDynamicTextureURLBlendFace(string dynamicID, string contentType, string url, string extraParams,
bool blend, int disp, int timer, int alpha, int face)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osSetDynamicTextureURLBlendFace");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
UUID createdTexture =
textureManager.AddDynamicTextureURL(World.RegionInfo.RegionID, m_host.UUID, contentType, url,
extraParams, blend, disp, (byte) alpha, face);
return createdTexture.ToString();
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public string osSetDynamicTextureData(string dynamicID, string contentType, string data, string extraParams,
int timer)
{
return osSetDynamicTextureDataFace(dynamicID, contentType, data, extraParams, timer, -1);
}
public string osSetDynamicTextureDataFace(string dynamicID, string contentType, string data, string extraParams,
int timer, int face)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetDynamicTextureData");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
if (textureManager is not null)
{
if (extraParams.Length == 0)
{
extraParams = "256";
}
UUID createdTexture =
textureManager.AddDynamicTextureData(World.RegionInfo.RegionID, m_host.UUID, contentType, data,
extraParams, false, 3, 255, face);
return createdTexture.ToString();
}
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public string osSetDynamicTextureDataBlend(string dynamicID, string contentType, string data, string extraParams,
int timer, int alpha)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetDynamicTextureDataBlend");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
if (textureManager is not null)
{
if (extraParams.Length == 0)
{
extraParams = "256";
}
UUID createdTexture =
textureManager.AddDynamicTextureData(World.RegionInfo.RegionID, m_host.UUID, contentType, data,
extraParams, true, (byte) alpha);
return createdTexture.ToString();
}
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public string osSetDynamicTextureDataBlendFace(string dynamicID, string contentType, string data, string extraParams,
bool blend, int disp, int timer, int alpha, int face)
{
CheckThreatLevel(ThreatLevel.VeryLow , "osSetDynamicTextureDataBlendFace");
if (dynamicID.Length == 0)
{
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
if (textureManager is not null)
{
if (extraParams.Length == 0)
{
extraParams = "256";
}
UUID createdTexture =
textureManager.AddDynamicTextureData(World.RegionInfo.RegionID, m_host.UUID, contentType, data,
extraParams, blend, disp, (byte) alpha, face);
return createdTexture.ToString();
}
}
else
{
//TODO update existing dynamic textures
}
return ScriptBaseClass.NULL_KEY;
}
public bool osConsoleCommand(string command)
{
CheckThreatLevel(ThreatLevel.Severe, "osConsoleCommand");
// For safety, we add another permission check here, and don't rely only on the standard OSSL permissions
if (World.Permissions.CanRunConsoleCommand(m_host.OwnerID))
{
MainConsole.Instance.RunCommand(command);
return true;
}
return false;
}
public void osSetPrimFloatOnWater(int floatYN)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetPrimFloatOnWater");
m_host.ParentGroup.RootPart.SetFloatOnWater(floatYN);
}
private bool checkAllowAgentTPbyLandOwner(UUID agentId, Vector3 pos)
{
if(m_host.OwnerID.Equals(agentId))
return true;
if (m_item.PermsGranter.Equals(agentId))
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TELEPORT) != 0)
return true;
}
ILandObject land = World.LandChannel.GetLandObject(pos);
if(land is null)
return true;
LandData landdata = land.LandData;
if(landdata is null)
return true;
if(landdata.OwnerID.Equals(m_host.OwnerID))
return true;
EstateSettings es = World.RegionInfo.EstateSettings;
if(es is not null && es.IsEstateManagerOrOwner(m_host.OwnerID))
return true;
if(!landdata.IsGroupOwned)
return false;
UUID landGroup = landdata.GroupID;
if(landGroup.IsZero())
return false;
if(landGroup.Equals(m_host.GroupID))
return true;
return false;
}
// Teleport functions
public void osLocalTeleportAgent(LSL_Key agent, LSL_Types.Vector3 position, LSL_Types.Vector3 velocity, LSL_Types.Vector3 lookat, LSL_Integer flags)
{
if (!UUID.TryParse(agent, out UUID agentId))
return;
ScenePresence presence = World.GetScenePresence(agentId);
if (presence is null || presence.IsDeleted || presence.IsInTransit)
return;
Vector3 pos = presence.AbsolutePosition;
if (!checkAllowAgentTPbyLandOwner(agentId, pos))
return;
World.RequestLocalTeleport(presence, position, velocity, lookat, flags);
}
public void osTeleportAgent(string agent, string regionName, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
// High because there is no security check. High griefer potential
//
CheckThreatLevel(ThreatLevel.Severe, "osTeleportAgent");
TeleportAgent(agent, regionName, position, lookat);
}
private void TeleportAgent(string agent, string regionName, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
if (UUID.TryParse(agent, out UUID agentId))
{
ScenePresence presence = World.GetScenePresence(agentId);
if (presence is null || presence.IsDeleted || presence.IsChildAgent || presence.IsInTransit)
return;
Vector3 pos = presence.AbsolutePosition;
if (!checkAllowAgentTPbyLandOwner(agentId, pos))
{
ScriptSleep(500);
return;
}
if (string.IsNullOrEmpty(regionName) || regionName.Equals(World.RegionInfo.RegionName, StringComparison.InvariantCultureIgnoreCase))
{
World.RequestTeleportLocation(presence.ControllingClient, World.RegionInfo.RegionName, position,
lookat, (uint)TPFlags.ViaLocation);
ScriptSleep(500);
}
else
{
// We will launch the teleport on a new thread so that when the script threads are terminated
// before teleport in ScriptInstance.GetXMLState(), we don't end up aborting the one doing the teleporting.
Util.FireAndForget(
o => World.RequestTeleportLocation(
presence.ControllingClient, regionName, position,
lookat, (uint)TPFlags.ViaLocation),
null, "OSSL_Api.TeleportAgentByRegionCoords");
ScriptSleep(5000);
}
}
}
public void osTeleportAgent(string agent, int regionGridX, int regionGridY, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
// High because there is no security check. High griefer potential
//
CheckThreatLevel(ThreatLevel.Severe, "osTeleportAgent");
TeleportAgent(agent, regionGridX, regionGridY, position, lookat);
}
private void TeleportAgent(string agent, int regionGridX, int regionGridY,
LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
if (UUID.TryParse(agent, out UUID agentId))
{
ScenePresence presence = World.GetScenePresence(agentId);
if (presence is null || presence.IsDeleted || presence.IsChildAgent || presence.IsInTransit)
return;
Vector3 pos = presence.AbsolutePosition;
if (!checkAllowAgentTPbyLandOwner(agentId, pos))
{
ScriptSleep(500);
return;
}
ulong regionHandle = Util.RegionGridLocToHandle((uint)regionGridX, (uint)regionGridY);
Util.FireAndForget(
o => World.RequestTeleportLocation(
presence.ControllingClient, regionHandle,
position, lookat, (uint)TPFlags.ViaLocation),
null, "OSSL_Api.TeleportAgentByRegionName");
ScriptSleep(5000);
}
}
public void osTeleportAgent(string agent, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
if (UUID.TryParse(agent, out UUID agentId))
{
ScenePresence presence = World.GetScenePresence(agentId);
if (presence == null || presence.IsDeleted || presence.IsChildAgent || presence.IsInTransit)
return;
Vector3 pos = presence.AbsolutePosition;
if (!checkAllowAgentTPbyLandOwner(agentId, pos))
{
ScriptSleep(500);
return;
}
RegionInfo ri = World.RegionInfo;
double px = position.x;
double py = position.y;
if (px >= 0 && px < ri.RegionSizeX && py >= 0 && py < ri.RegionSizeY)
{
World.RequestTeleportLocation(presence.ControllingClient, ri.RegionName, position,
lookat, (uint)TPFlags.ViaLocation);
ScriptSleep(500);
return;
}
// not in region. lets use global position then.
px += ri.WorldLocX;
py += ri.WorldLocY;
int gx = (int)px / 256;
int gy = (int)py / 256;
px -= 256 * gx;
py -= 256 * gy;
ulong regionHandle = Util.RegionGridLocToHandle((uint)gx, (uint)gy);
Util.FireAndForget(
o => World.RequestTeleportLocation(
presence.ControllingClient, regionHandle,
new Vector3((float)px, (float)py, (float)position.z), lookat, (uint)TPFlags.ViaLocation),
null, "OSSL_Api.TeleportAgentByFarPos");
ScriptSleep(5000);
}
}
public void osTeleportOwner(string regionName, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
// Threat level None because this is what can already be done with the World Map in the viewer
CheckThreatLevel(ThreatLevel.None, "osTeleportOwner");
TeleportAgent(m_host.OwnerID.ToString(), regionName, position, lookat);
}
public void osTeleportOwner(int regionGridX, int regionGridY, LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
CheckThreatLevel(ThreatLevel.None, "osTeleportOwner");
TeleportAgent(m_host.OwnerID.ToString(), regionGridX, regionGridY, position, lookat);
}
public void osTeleportOwner(LSL_Types.Vector3 position, LSL_Types.Vector3 lookat)
{
CheckThreatLevel(ThreatLevel.None, "osTeleportOwner");
osTeleportAgent(m_host.OwnerID.ToString(), position, lookat);
}
///<summary>
/// Allows a script IN the target prim to force an avatar to sit on it using normal methods
/// as if called by the client.
/// Silent fail if agent (or target if overloaded) not found.
/// Does work if passed key (or keys if overloaded).
/// </summary>
/// <param name="avatar"></param>
public void osForceOtherSit(string avatar)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osForceOtherSit");
ForceSit(avatar, m_host.UUID);
}
/// <summary>
/// Overload method of osForceOtherSit(string avatar) to allow a script NOT in the target prim to force
/// an avatar to sit on the target prim using normal methods as if called by the client.
/// </summary>
/// <param name="avatar"></param>
/// <param name="target"></param>
public void osForceOtherSit(string avatar, string target)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osForceOtherSit");
if(UUID.TryParse( target, out UUID targetID))
ForceSit(avatar, targetID);
}
public void ForceSit(string avatar, UUID targetID)
{
if (!UUID.TryParse(avatar, out UUID agentID))
return;
ScenePresence presence = World.GetScenePresence(agentID);
if(presence is null)
return;
SceneObjectPart part = World.GetSceneObjectPart(targetID);
if (part is not null && part.SitTargetAvatar.IsZero())
presence.HandleAgentRequestSit(presence.ControllingClient,
agentID, targetID,
part.SitTargetPosition);
}
// Get a list of all the avatars/agents in the region
public LSL_List osGetAgents()
{
// threat level is None as we could get this information with an
// in-world script as well, just not as efficient
CheckThreatLevel(ThreatLevel.None, "osGetAgents");
LSL_List result = new();
World.ForEachRootScenePresence(delegate(ScenePresence sp)
{
result.Add(new LSL_String(sp.Name));
});
return result;
}
public string osGetAgentIP(string agent)
{
CheckThreatLevel(ThreatLevel.Severe, "osGetAgentIP");
if(!(World.Permissions.IsGod(m_host.OwnerID))) // user god always needed
return string.Empty;
if(!UUID.TryParse(agent, out UUID avatarID))
return string.Empty;
ScenePresence target = World.GetScenePresence(avatarID);
if (target is not null)
{
return target.ControllingClient.RemoteEndPoint.Address.ToString();
}
return string.Empty;
}
// Adam's super super custom animation functions
public void osAvatarPlayAnimation(LSL_Key avatar, string animation)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osAvatarPlayAnimation");
if (!UUID.TryParse(avatar, out UUID avatarID))
return;
ScenePresence target = World.GetScenePresence(avatarID);
if (target is null)
return;
UUID animID = UUID.Zero;
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in m_host.TaskInventory)
{
if (inv.Value.Type == (int)AssetType.Animation)
{
if (inv.Value.Name == animation)
{
animID = inv.Value.AssetID;
break;
}
}
}
m_host.TaskInventory.LockItemsForRead(false);
if (animID.IsZero())
target.Animator.AddAnimation(animation, m_host.UUID);
else
target.Animator.AddAnimation(animID, m_host.UUID);
}
public void osAvatarStopAnimation(LSL_Key avatar, string animation)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osAvatarStopAnimation");
if (!UUID.TryParse(avatar, out UUID avatarID))
return;
ScenePresence target = World.GetScenePresence(avatarID);
if (target is null)
return;
if (!UUID.TryParse(animation, out UUID animID))
{
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(animation);
if (item != null && item.Type == (int)AssetType.Animation)
animID = item.AssetID;
else
animID = UUID.Zero;
}
if (animID.IsZero())
target.Animator.RemoveAnimation(animation);
else
target.Animator.RemoveAnimation(animID, true);
}
//Texture draw functions
public string osDrawResetTransform(string drawList)
{
CheckThreatLevel();
drawList += "ResetTransf;";
return drawList;
}
public string osDrawRotationTransform(string drawList, LSL_Float x)
{
CheckThreatLevel();
drawList += "RotTransf " + x + ";";
return drawList;
}
public string osDrawScaleTransform(string drawList, LSL_Float x, LSL_Float y)
{
CheckThreatLevel();
drawList += "ScaleTransf " + x + "," + y + ";";
return drawList;
}
public string osDrawTranslationTransform(string drawList, LSL_Float x, LSL_Float y)
{
CheckThreatLevel();
drawList += "TransTransf " + x + "," + y + ";";
return drawList;
}
public string osMovePen(string drawList, int x, int y)
{
CheckThreatLevel();
drawList += "MoveTo " + x + "," + y + ";";
return drawList;
}
public string osDrawLine(string drawList, int startX, int startY, int endX, int endY)
{
CheckThreatLevel();
drawList += "MoveTo "+ startX+","+ startY +"; LineTo "+endX +","+endY +"; ";
return drawList;
}
public string osDrawLine(string drawList, int endX, int endY)
{
CheckThreatLevel();
drawList += "LineTo " + endX + "," + endY + "; ";
return drawList;
}
public string osDrawText(string drawList, string text)
{
CheckThreatLevel();
drawList += "Text " + text + "; ";
return drawList;
}
public string osDrawEllipse(string drawList, int width, int height)
{
CheckThreatLevel();
drawList += "Ellipse " + width + "," + height + "; ";
return drawList;
}
public string osDrawFilledEllipse(string drawList, int width, int height)
{
CheckThreatLevel();
drawList += "FillEllipse " + width + "," + height + "; ";
return drawList;
}
public string osDrawRectangle(string drawList, int width, int height)
{
CheckThreatLevel();
drawList += "Rectangle " + width + "," + height + "; ";
return drawList;
}
public string osDrawFilledRectangle(string drawList, int width, int height)
{
CheckThreatLevel();
drawList += "FillRectangle " + width + "," + height + "; ";
return drawList;
}
public string osDrawFilledPolygon(string drawList, LSL_List x, LSL_List y)
{
CheckThreatLevel();
if (x.Length != y.Length || x.Length < 3)
{
return "";
}
drawList += "FillPolygon " + x.GetStringItem(0) + "," + y.GetStringItem(0);
for (int i = 1; i < x.Length; i++)
{
drawList += "," + x.GetStringItem(i) + "," + y.GetStringItem(i);
}
drawList += "; ";
return drawList;
}
public string osDrawPolygon(string drawList, LSL_List x, LSL_List y)
{
CheckThreatLevel();
if (x.Length != y.Length || x.Length < 3)
{
return "";
}
drawList += "Polygon " + x.GetStringItem(0) + "," + y.GetStringItem(0);
for (int i = 1; i < x.Length; i++)
{
drawList += "," + x.GetStringItem(i) + "," + y.GetStringItem(i);
}
drawList += "; ";
return drawList;
}
public string osSetFontSize(string drawList, int fontSize)
{
CheckThreatLevel();
drawList += "FontSize "+ fontSize +"; ";
return drawList;
}
public string osSetFontName(string drawList, string fontName)
{
CheckThreatLevel();
drawList += "FontName "+ fontName +"; ";
return drawList;
}
public string osSetPenSize(string drawList, int penSize)
{
CheckThreatLevel();
drawList += "PenSize " + penSize + "; ";
return drawList;
}
public string osSetPenColor(string drawList, string color)
{
CheckThreatLevel();
drawList += "PenColor " + color + "; ";
return drawList;
}
public string osSetPenColor(string drawList, LSL_Types.Vector3 color)
{
CheckThreatLevel();
byte argbR = Utils.FloatZeroOneToByte((float)color.x);
byte argbG = Utils.FloatZeroOneToByte((float)color.y);
byte argbB = Utils.FloatZeroOneToByte((float)color.z);
StringBuilder sb = new(19);
sb.Append("PenColor FF");
sb.Append(Utils.charHighNibbleToHexChar(argbR));
sb.Append(Utils.charLowNibbleToHexChar(argbR));
sb.Append(Utils.charHighNibbleToHexChar(argbG));
sb.Append(Utils.charLowNibbleToHexChar(argbG));
sb.Append(Utils.charHighNibbleToHexChar(argbB));
sb.Append(Utils.charLowNibbleToHexChar(argbB));
sb.Append("; ");
drawList += sb.ToString();
return drawList;
}
public string osSetPenColor(string drawList, LSL_Types.Vector3 color, LSL_Float alpha)
{
CheckThreatLevel();
byte argbA = Utils.FloatZeroOneToByte((float)alpha);
byte argbR = Utils.FloatZeroOneToByte((float)color.x);
byte argbG = Utils.FloatZeroOneToByte((float)color.y);
byte argbB = Utils.FloatZeroOneToByte((float)color.z);
StringBuilder sb = new(19);
sb.Append("PenColor ");
sb.Append(Utils.charHighNibbleToHexChar(argbA));
sb.Append(Utils.charLowNibbleToHexChar(argbA));
sb.Append(Utils.charHighNibbleToHexChar(argbR));
sb.Append(Utils.charLowNibbleToHexChar(argbR));
sb.Append(Utils.charHighNibbleToHexChar(argbG));
sb.Append(Utils.charLowNibbleToHexChar(argbG));
sb.Append(Utils.charHighNibbleToHexChar(argbB));
sb.Append(Utils.charLowNibbleToHexChar(argbB));
sb.Append(';');
sb.Append(' ');
drawList += sb.ToString();
return drawList;
}
// Deprecated
public string osSetPenColour(string drawList, string colour)
{
CheckThreatLevel();
OSSLDeprecated("osSetPenColour", "osSetPenColor");
drawList += "PenColour " + colour + "; ";
return drawList;
}
public string osSetPenCap(string drawList, string direction, string type)
{
CheckThreatLevel();
drawList += "PenCap " + direction + "," + type + "; ";
return drawList;
}
public string osDrawImage(string drawList, int width, int height, string imageUrl)
{
CheckThreatLevel();
drawList +="Image " +width + "," + height+ ","+ imageUrl +"; " ;
return drawList;
}
public LSL_Vector osGetDrawStringSize(string contentType, string text, string fontName, int fontSize)
{
CheckThreatLevel();
IDynamicTextureManager textureManager = World.RequestModuleInterface<IDynamicTextureManager>();
if (textureManager is not null)
{
textureManager.GetDrawStringSize(contentType, text, fontName, fontSize,
out double xSize, out double ySize);
return new LSL_Vector(xSize, ySize, 0);
}
return LSL_Vector.Zero;
}
public void osSetRegionWaterHeight(double height)
{
CheckThreatLevel(ThreatLevel.High, "osSetRegionWaterHeight");
World.EventManager.TriggerRequestChangeWaterHeight((float)height);
}
/// <summary>
/// Changes the Region Sun Settings, then Triggers a Sun Update
/// </summary>
/// <param name="useEstateSun">True to use Estate Sun instead of Region Sun</param>
/// <param name="sunFixed">True to keep the sun stationary</param>
/// <param name="sunHour">The "Sun Hour" that is desired, 0...24, with 0 just after SunRise</param>
public void osSetRegionSunSettings(bool useEstateSun, bool sunFixed, double sunHour)
{
CheckThreatLevel(ThreatLevel.High, "osSetRegionSunSettings");
while (sunHour > 24.0)
sunHour -= 24.0;
while (sunHour < 0)
sunHour += 24.0;
World.RegionInfo.RegionSettings.UseEstateSun = useEstateSun;
World.RegionInfo.RegionSettings.SunPosition = sunHour + 6; // LL Region Sun Hour is 6 to 30
World.RegionInfo.RegionSettings.FixedSun = sunFixed;
World.RegionInfo.RegionSettings.Save();
World.EventManager.TriggerEstateToolsSunUpdate(World.RegionInfo.RegionHandle);
}
/// <summary>
/// Changes the Estate Sun Settings, then Triggers a Sun Update
/// </summary>
/// <param name="sunFixed">True to keep the sun stationary, false to use global time</param>
/// <param name="sunHour">The "Sun Hour" that is desired, 0...24, with 0 just after SunRise</param>
public void osSetEstateSunSettings(bool sunFixed, double sunHour)
{
/*
CheckThreatLevel(ThreatLevel.High, "osSetEstateSunSettings");
while (sunHour > 24.0)
sunHour -= 24.0;
while (sunHour < 0)
sunHour += 24.0;
World.RegionInfo.EstateSettings.UseGlobalTime = !sunFixed;
World.RegionInfo.EstateSettings.SunPosition = sunHour;
World.RegionInfo.EstateSettings.FixedSun = sunFixed;
World.EstateDataService.StoreEstateSettings(World.RegionInfo.EstateSettings);
World.EventManager.TriggerEstateToolsSunUpdate(World.RegionInfo.RegionHandle);
*/
}
/// <summary>
/// Return the current Sun Hour 0...24, with 0 being roughly sun-rise
/// </summary>
/// <returns></returns>
public LSL_Float osGetCurrentSunHour()
{
CheckThreatLevel();
if (m_envModule is null)
return 0;
float frac = m_envModule.GetRegionDayFractionTime();
return 24 * frac;
}
public LSL_Float osGetApparentTime()
{
CheckThreatLevel();
if (m_envModule is null)
return 0;
float frac = m_envModule.GetRegionDayFractionTime();
return 86400 * frac;
}
private static string timeToString(float frac, bool format24)
{
int h = (int)frac;
frac -= h;
frac *= 60;
int m = (int)frac;
frac -= m;
frac *= 60;
int s = (int)frac;
if (format24)
{
return string.Format("{0:00}:{1:00}:{2:00}", h, m, s);
}
if (h > 12)
return string.Format("{0}:{1:00}:{2:00} PM", h - 12, m, s);
if (h == 12)
return string.Format("{0}:{1:00}:{2:00} PM", h, m, s);
return string.Format("{0}:{1:00}:{2:00} AM", h, m, s);
}
public LSL_String osGetApparentTimeString(LSL_Integer format24)
{
CheckThreatLevel();
if (m_envModule is null)
return format24 != 0 ? "00:00:00" : "0:00:00 AM";
float frac = 24 * m_envModule.GetRegionDayFractionTime();
return timeToString(frac, format24 != 0);
}
public LSL_Float osGetApparentRegionTime()
{
CheckThreatLevel();
if (m_envModule is null)
return 0;
float frac = m_envModule.GetRegionDayFractionTime();
return 86400 * frac;
}
public LSL_String osGetApparentRegionTimeString(LSL_Integer format24)
{
CheckThreatLevel();
if (m_envModule is null)
return format24 != 0 ? "00:00:00" : "0:00:00 AM";
float frac = 24 * m_envModule.GetRegionDayFractionTime();
return timeToString(frac, format24 != 0);
}
public double osSunGetParam(string param)
{
CheckThreatLevel(ThreatLevel.None, "osSunGetParam");
OSSLDeprecated("osSunGetParam", "osGetSunParam");
return GetSunParam(param);
}
public LSL_Float osGetSunParam(LSL_String param)
{
CheckThreatLevel();
return GetSunParam(param);
}
private double GetSunParam(string param)
{
param = param.ToLower();
switch(param)
{
case "day_length":
return m_envModule is null ? 14400 : m_envModule.GetDayLength(m_host.AbsolutePosition);
case "year_length":
return 365;
case "day_night_offset":
return 0;
case "update_interval":
return 0.1;
case "day_time_sun_hour_scale":
return 1;
default:
break;
}
return 0;
}
public void osSunSetParam(string param, double value)
{
CheckThreatLevel(ThreatLevel.None, "osSunSetParam");
OSSLDeprecated("osSunSetParam", "osSetSunParam");
SetSunParam(param, value);
}
public void osSetSunParam(string param, double value)
{
CheckThreatLevel(ThreatLevel.None, "osSetSunParam");
SetSunParam(param, value);
}
private void SetSunParam(string param, double value)
{
ISunModule module = World.RequestModuleInterface<ISunModule>();
module?.SetSunParameter(param, value);
}
public string osWindActiveModelPluginName()
{
CheckThreatLevel(ThreatLevel.None, "osWindActiveModelPluginName");
IWindModule module = World.RequestModuleInterface<IWindModule>();
if (module is not null)
{
return module.WindActiveModelPluginName;
}
return string.Empty;
}
public void osSetWindParam(string plugin, string param, LSL_Float value)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetWindParam");
IWindModule module = World.RequestModuleInterface<IWindModule>();
if (module is not null)
{
try
{
module.WindParamSet(plugin, param, (float)value);
}
catch (Exception) { }
}
}
public LSL_Float osGetWindParam(string plugin, string param)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osGetWindParam");
IWindModule module = World.RequestModuleInterface<IWindModule>();
if (module is not null)
{
return module.WindParamGet(plugin, param);
}
return 0.0f;
}
public LSL_Integer osGetParcelDwell(LSL_Vector pos)
{
LandData land = World.GetLandData(pos);
if (land is not null)
{
return (int)land.Dwell;
}
return 0;
}
// Routines for creating and managing parcels programmatically
public void osParcelJoin(LSL_Vector pos1, LSL_Vector pos2)
{
CheckThreatLevel(ThreatLevel.High, "osParcelJoin");
int startx = (int)(pos1.x < pos2.x ? pos1.x : pos2.x);
int starty = (int)(pos1.y < pos2.y ? pos1.y : pos2.y);
int endx = (int)(pos1.x > pos2.x ? pos1.x : pos2.x);
int endy = (int)(pos1.y > pos2.y ? pos1.y : pos2.y);
World.LandChannel.Join(startx,starty,endx,endy,m_host.OwnerID);
}
public void osParcelSubdivide(LSL_Vector pos1, LSL_Vector pos2)
{
CheckThreatLevel(ThreatLevel.High, "osParcelSubdivide");
int startx = (int)(pos1.x < pos2.x ? pos1.x : pos2.x);
int starty = (int)(pos1.y < pos2.y ? pos1.y : pos2.y);
int endx = (int)(pos1.x > pos2.x ? pos1.x : pos2.x);
int endy = (int)(pos1.y > pos2.y ? pos1.y : pos2.y);
World.LandChannel.Subdivide(startx,starty,endx,endy,m_host.OwnerID);
}
public void osParcelSetDetails(LSL_Vector pos, LSL_List rules)
{
const string functionName = "osParcelSetDetails";
CheckThreatLevel(ThreatLevel.High, functionName);
OSSLDeprecated(functionName, "osSetParcelDetails");
SetParcelDetails(pos, rules, functionName);
}
public void osSetParcelDetails(LSL_Vector pos, LSL_List rules)
{
const string functionName = "osSetParcelDetails";
CheckThreatLevel(ThreatLevel.High, functionName);
SetParcelDetails(pos, rules, functionName);
}
private void SetParcelDetails(LSL_Vector pos, LSL_List rules, string functionName)
{
// Get a reference to the land data and make sure the owner of the script
// can modify it
ILandObject startLandObject = World.LandChannel.GetLandObject((int)pos.x, (int)pos.y);
if (startLandObject is null)
{
OSSLShoutError("There is no land at that location");
return;
}
if (!World.Permissions.CanEditParcelProperties(m_host.OwnerID, startLandObject, GroupPowers.LandOptions, false))
{
OSSLShoutError("script owner does not have permission to modify the parcel");
return;
}
// Create a new land data object we can modify
LandData newLand = startLandObject.LandData.Copy();
UUID uuid;
EstateSettings es = World.RegionInfo.EstateSettings;
bool changed = false;
bool changedSeeAvs = false;
bool changedoverlay = false;
bool changedneedupdate = false;
string arg;
int iarg;
// Process the rules, not sure what the impact would be of changing owner or group
for (int idx = 0; idx < rules.Length;)
{
int code = rules.GetIntegerItem(idx++);
switch (code)
{
case ScriptBaseClass.PARCEL_DETAILS_NAME:
arg = rules.GetStrictStringItem(idx++);
if (newLand.Name != arg)
{
newLand.Name = arg;
changed = true;
}
break;
case ScriptBaseClass.PARCEL_DETAILS_DESC:
arg = rules.GetStrictStringItem(idx++);
if (newLand.Description != arg)
{
newLand.Description = arg;
changed = true;
}
break;
case ScriptBaseClass.PARCEL_DETAILS_OWNER:
arg = rules.GetStrictStringItem(idx++);
if (es is not null && !es.IsEstateManagerOrOwner(m_host.OwnerID))
{
OSSLShoutError("script owner does not have permission to modify the parcel owner");
}
else
{
if (UUID.TryParse(arg, out uuid))
{
if(newLand.OwnerID.NotEqual(uuid))
{
changed = true;
newLand.OwnerID = uuid;
newLand.GroupID = UUID.Zero;
}
}
}
break;
case ScriptBaseClass.PARCEL_DETAILS_GROUP:
arg = rules.GetStrictStringItem(idx++);
if (m_host.OwnerID.Equals(newLand.OwnerID) || es == null || es.IsEstateManagerOrOwner(m_host.OwnerID))
{
if (UUID.TryParse(arg, out uuid))
{
if(newLand.GroupID.NotEqual(uuid))
{
if(uuid.IsZero())
{
changed = true;
newLand.GroupID = uuid;
}
else
{
GroupMembershipData member = null;
if (m_groupsModule is not null)
member = m_groupsModule.GetMembershipData(uuid, newLand.OwnerID);
if (member is null)
OSSLShoutError(string.Format("land owner is not member of the new group for parcel"));
else
{
changed = true;
newLand.GroupID = uuid;
}
}
}
}
}
else
{
OSSLShoutError("script owner does not have permission to modify the parcel group");
}
break;
case ScriptBaseClass.PARCEL_DETAILS_CLAIMDATE:
if(es is not null && !es.IsEstateManagerOrOwner(m_host.OwnerID))
{
OSSLShoutError("script owner does not have permission to modify the parcel CLAIM DATE");
}
else
{
int date = rules.GetIntegerItem(idx++);
if (date == 0)
date = Util.UnixTimeSinceEpoch();
if(newLand.ClaimDate != date)
{
changed = true;
newLand.ClaimDate = date;
}
}
break;
case ScriptBaseClass.PARCEL_DETAILS_SEE_AVATARS:
iarg = rules.GetIntegerItem(idx++);
bool newavs = iarg != 0;
if(newLand.SeeAVs != newavs)
{
changed = true;
changedSeeAvs = true;
changedoverlay = true;
changedneedupdate = true;
newLand.SeeAVs = newavs;
}
break;
case ScriptBaseClass.PARCEL_DETAILS_ANY_AVATAR_SOUNDS:
iarg = rules.GetIntegerItem(idx++);
bool newavsounds = iarg != 0;
if(newLand.AnyAVSounds != newavsounds)
{
changed = true;
newLand.AnyAVSounds = newavsounds;
}
break;
case ScriptBaseClass.PARCEL_DETAILS_GROUP_SOUNDS:
iarg = rules.GetIntegerItem(idx++);
bool newgrpsounds = iarg != 0;
if(newLand.GroupAVSounds != newgrpsounds)
{
changed = true;
newLand.GroupAVSounds = newgrpsounds;
}
break;
}
}
if(changed)
{
World.LandChannel.UpdateLandObject(newLand.LocalID, newLand);
if(changedneedupdate)
{
UUID parcelID= newLand.GlobalID;
World.ForEachRootScenePresence(delegate (ScenePresence avatar)
{
if (avatar is null || avatar.IsDeleted || avatar.IsInTransit)
return;
if(changedSeeAvs && avatar.currentParcelUUID.Equals(parcelID))
avatar.currentParcelUUID = parcelID; // force parcel flags review
if(avatar.ControllingClient is null)
return;
// this will be needed for some things like damage etc
// if(avatar.currentParcelUUID == parcelID)
// startLandObject.SendLandUpdateToClient(avatar.ControllingClient);
if(changedoverlay && !avatar.IsNPC)
World.LandChannel.SendParcelsOverlay(avatar.ControllingClient);
});
}
}
}
public void osSetParcelMusicURL(LSL_String url)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetParcelMusicURL");
ILandObject land = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
land?.SetMusicUrl(url);
}
public void osSetParcelMediaURL(LSL_String url)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osSetParcelMediaURL");
ILandObject land = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
land?.SetMediaUrl(url);
}
public void osSetParcelSIPAddress(string SIPAddress)
{
// What actually is the difference to the LL function?
//
CheckThreatLevel(ThreatLevel.VeryLow, "osSetParcelSIPAddress");
ILandObject land = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
if (land.LandData.OwnerID.NotEqual(m_host.OwnerID))
{
OSSLError("osSetParcelSIPAddress: Sorry, you need to own the land to use this function");
return;
}
// get the voice module
IVoiceModule voiceModule = World.RequestModuleInterface<IVoiceModule>();
if (voiceModule is not null)
voiceModule.setLandSIPAddress(SIPAddress,land.LandData.GlobalID);
else
OSSLError("osSetParcelSIPAddress: No voice module enabled for this land");
}
public string osGetScriptEngineName()
{
// This gets a "high" because knowing the engine may be used
// to exploit engine-specific bugs or induce usage patterns
// that trigger engine-specific failures.
// Besides, public grid users aren't supposed to know.
//
CheckThreatLevel(ThreatLevel.High, "osGetScriptEngineName");
int scriptEngineNameIndex = 0;
if (!string.IsNullOrEmpty(m_ScriptEngine.ScriptEngineName))
{
// parse off the "ScriptEngine."
scriptEngineNameIndex = m_ScriptEngine.ScriptEngineName.IndexOf('.', scriptEngineNameIndex);
scriptEngineNameIndex++; // get past delimiter
int scriptEngineNameLength = m_ScriptEngine.ScriptEngineName.Length - scriptEngineNameIndex;
// create char array then a string that is only the script engine name
Char[] scriptEngineNameCharArray = m_ScriptEngine.ScriptEngineName.ToCharArray(scriptEngineNameIndex, scriptEngineNameLength);
string scriptEngineName = new(scriptEngineNameCharArray);
return scriptEngineName;
}
return string.Empty;
}
public LSL_Integer osCheckODE()
{
CheckThreatLevel();
if (m_ScriptEngine.World.PhysicsScene is not null)
{
if (m_ScriptEngine.World.PhysicsScene.EngineType == "OpenDynamicsEngine")
{
return new LSL_Integer(1); // true
}
}
return new LSL_Integer(0);
}
public string osGetPhysicsEngineType()
{
// High because it can be used to target attacks to known weaknesses
// This would allow a new class of griefer scripts that don't even
// require their user to know what they are doing (see script
// kiddie)
// Because it would be nice if scripts didn't blow up if the information
// about the physics engine, this function returns an empty string if
// the user does not have permission to see it. This as opposed to
// throwing an exception.
string ret = string.Empty;
if (string.IsNullOrEmpty(CheckThreatLevelTest(ThreatLevel.High, "osGetPhysicsEngineType")))
{
if (m_ScriptEngine.World.PhysicsScene != null)
{
ret = m_ScriptEngine.World.PhysicsScene.EngineType;
// An old physics engine might have an uninitialized engine type
ret ??= "unknown";
}
}
return ret;
}
public string osGetPhysicsEngineName()
{
CheckThreatLevel();
string ret = "NoEngine";
if (m_ScriptEngine.World.PhysicsScene != null)
{
ret = m_ScriptEngine.World.PhysicsScene.EngineName;
// An old physics engine might have an uninitialized engine type
ret ??= "UnknownEngine";
}
return ret;
}
public string osGetSimulatorVersion()
{
// High because it can be used to target attacks to known weaknesses
// This would allow a new class of griefer scripts that don't even
// require their user to know what they are doing (see script
// kiddie)
//
CheckThreatLevel(ThreatLevel.High,"osGetSimulatorVersion");
return m_ScriptEngine.World.GetSimulatorVersion();
}
/// <summary>
/// Send a message to to object identified by the given UUID
/// </summary>
/// <remarks>
/// A script in the object must implement the dataserver function
/// the dataserver function is passed the ID of the calling function and a string message
/// </remarks>
/// <param name="objectUUID"></param>
/// <param name="message"></param>
public void osMessageObject(LSL_Key objectUUID, string message)
{
CheckThreatLevel(ThreatLevel.Low, "osMessageObject");
if (!UUID.TryParse(objectUUID, out UUID objUUID)) // prior to patching, a thrown exception regarding invalid GUID format would be shouted instead.
{
OSSLShoutError("osMessageObject() cannot send messages to objects with invalid UUIDs");
return;
}
MessageObject(objUUID, message);
//ScriptSleep(25); // mostly a thread yield
}
private void MessageObject(UUID objUUID, string message)
{
object[] resobj = new object[] { new LSL_String(m_host.UUID.ToString()), new LSL_String(message) };
SceneObjectPart sceneOP = World.GetSceneObjectPart(objUUID);
if (sceneOP == null) // prior to patching, PostObjectEvent() would cause a throw exception to be shouted instead.
{
OSSLShoutError("osMessageObject() cannot send message to " + objUUID.ToString() + ", object was not found in scene.");
return;
}
m_ScriptEngine.PostObjectEvent(
sceneOP.LocalId, new EventParams(
"dataserver", resobj, Array.Empty<DetectParams>()));
}
/// <summary>
/// Similar to llDie but given an object UUID
/// </summary>
/// <param name="objectUUID"></param>
public void osDie(LSL_Key objectUUID)
{
CheckThreatLevel(ThreatLevel.Low, "osDie");
if (!UUID.TryParse(objectUUID, out UUID objUUID))
{
OSSLShoutError("osDie() cannot delete objects with invalid UUIDs");
return;
}
if(objUUID.IsZero())
return;
SceneObjectGroup sceneOG = World.GetSceneObjectGroup(objUUID);
if (sceneOG is null || sceneOG.IsDeleted || sceneOG.IsAttachment)
return;
if (sceneOG.OwnerID.NotEqual(m_host.OwnerID))
return;
// restrict to objects rezzed by host
if(sceneOG.RezzerID.Equals(m_host.ParentGroup.UUID))
{
// harakiri check should be true alwaya
if (sceneOG.UUID.NotEqual(m_host.ParentGroup.UUID))
World.DeleteSceneObject(sceneOG, false);
}
}
/// <summary>
/// Write a notecard directly to the prim's inventory.
/// </summary>
/// <remarks>
/// This needs ThreatLevel high. It is an excellent griefer tool,
/// In a loop, it can cause asset bloat and DOS levels of asset
/// writes.
/// </remarks>
/// <param name="notecardName">The name of the notecard to write.</param>
/// <param name="contents">The contents of the notecard.</param>
public void osMakeNotecard(string notecardName, LSL_String contents)
{
CheckThreatLevel(ThreatLevel.High, "osMakeNotecard");
SaveNotecard(notecardName, "Script generated notecard", contents + "\n", false);
}
public void osMakeNotecard(string notecardName, LSL_List contents)
{
CheckThreatLevel(ThreatLevel.High, "osMakeNotecard");
StringBuilder notecardData = new();
for (int i = 0; i < contents.Length; i++)
notecardData.Append(contents.GetStringItem(i) + "\n");
SaveNotecard(notecardName, "Script generated notecard", notecardData.ToString(), false);
}
/// <summary>
/// Save a notecard to prim inventory.
/// </summary>
/// <param name="name"></param>
/// <param name="description">Description of notecard</param>
/// <param name="notecardData"></param>
/// <param name="forceSameName">
/// If true, then if an item exists with the same name, it is replaced.
/// If false, then a new item is created witha slightly different name (e.g. name 1)
/// </param>
/// <returns>Prim inventory item created.</returns>
protected TaskInventoryItem SaveNotecard(string name, string description, string data, bool forceSameName)
{
// Create new asset
AssetBase asset = new(UUID.Random(), name, (sbyte)AssetType.Notecard, m_host.OwnerID.ToString())
{
Description = description
};
osUTF8 contents = new(data, 65536);
int len = contents.Length;
osUTF8Slice utf = new(len + 128);
utf.AppendASCII("Linden text version 2\n{\nLLEmbeddedItems version 1\n{\ncount 0\n}\nText length ");
utf.AppendASCII(len.ToString());
utf.AppendASCII("\n");
utf.Append(contents);
utf.AppendASCII("}");
asset.Data = utf.ToArray();
World.AssetService.Store(asset);
// Create Task Entry
TaskInventoryItem taskItem = new()
{
ParentID = m_host.UUID,
CreationDate = (uint)Util.UnixTimeSinceEpoch(),
Name = name,
Description = description,
Type = (int)AssetType.Notecard,
InvType = (int)InventoryType.Notecard,
OwnerID = m_host.OwnerID,
CreatorID = m_host.OwnerID,
BasePermissions = (uint)PermissionMask.All | (uint)PermissionMask.Export,
CurrentPermissions = (uint)PermissionMask.All | (uint)PermissionMask.Export,
EveryonePermissions = 0,
NextPermissions = (uint)PermissionMask.All,
GroupID = m_host.GroupID,
GroupPermissions = 0,
Flags = 0,
PermsGranter = UUID.Zero,
PermsMask = 0,
AssetID = asset.FullID
};
taskItem.ResetIDs(m_host.UUID);
if (forceSameName)
m_host.Inventory.AddInventoryItemExclusive(taskItem, false);
else
m_host.Inventory.AddInventoryItem(taskItem, false);
m_host.ParentGroup.InvalidateDeepEffectivePerms();
return taskItem;
}
/// <summary>
/// Load the notecard data found at the given prim inventory item name or asset uuid.
/// </summary>
/// <param name="notecardNameOrUuid"></param>
/// <returns>The text loaded. Null if no notecard was found.</returns>
protected string LoadNotecard(string notecardNameOrUuid)
{
UUID assetID = CacheNotecard(notecardNameOrUuid);
if (!assetID.IsZero())
{
StringBuilder notecardData = new();
for (int count = 0; count < NotecardCache.GetLines(assetID); count++)
{
string line = NotecardCache.GetLine(assetID, count) + "\n";
// m_log.DebugFormat("[OSSL]: From notecard {0} loading line {1}", notecardNameOrUuid, line);
notecardData.Append(line);
}
return notecardData.ToString();
}
return null;
}
/// <summary>
/// Cache a notecard's contents.
/// </summary>
/// <param name="notecardNameOrUuid"></param>
/// <returns>
/// The asset id of the notecard, which is used for retrieving the cached data.
/// UUID.Zero if no asset could be found.
/// </returns>
protected UUID CacheNotecard(string notecardNameOrUuid)
{
if (!UUID.TryParse(notecardNameOrUuid, out UUID assetID))
{
assetID = SearchTaskInventoryForAssetId(notecardNameOrUuid);
}
if (assetID.IsZero())
return UUID.Zero;
if (!NotecardCache.IsCached(assetID))
{
AssetBase a = World.AssetService.Get(assetID.ToString());
if (a == null)
{
// Whoops, it's still possible here that the notecard name was properly
// formatted like a UUID but isn't an asset UUID so lets look it up by name after all
assetID = SearchTaskInventoryForAssetId(notecardNameOrUuid);
if (assetID.IsZero())
return UUID.Zero;
if (!NotecardCache.IsCached(assetID))
{
a = World.AssetService.Get(assetID.ToString());
if (a == null)
{
return UUID.Zero;
}
}
}
NotecardCache.Cache(assetID, a.Data);
}
return assetID;
}
protected UUID SearchTaskInventoryForAssetId(string name)
{
UUID assetId = UUID.Zero;
m_host.TaskInventory.LockItemsForRead(true);
foreach (TaskInventoryItem item in m_host.TaskInventory.Values)
{
if (item.Type == 7 && item.Name == name)
{
assetId = item.AssetID;
}
}
m_host.TaskInventory.LockItemsForRead(false);
return assetId;
}
/// <summary>
/// Directly get an entire notecard at once.
/// </summary>
/// <remarks>
/// Instead of using the LSL Dataserver event to pull notecard data
/// this will simply read the entire notecard and return its data as a string.
///
/// Warning - due to the synchronous method this function uses to fetch assets, its use
/// may be dangerous and unreliable while running in grid mode.
/// </remarks>
/// <param name="name">Name of the notecard or its asset id</param>
/// <param name="line">The line number to read. The first line is line 0</param>
/// <returns>Notecard line</returns>
public string osGetNotecardLine(string name, int line)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osGetNotecardLine");
UUID assetID = CacheNotecard(name);
if (assetID.IsZero())
{
OSSLShoutError("Notecard '" + name + "' could not be found.");
return "ERROR!";
}
return NotecardCache.GetLine(assetID, line);
}
/// <summary>
/// Get an entire notecard at once.
/// </summary>
/// <remarks>
/// Instead of using the LSL Dataserver event to pull notecard data line by line,
/// this will simply read the entire notecard and return its data as a string.
///
/// Warning - due to the synchronous method this function uses to fetch assets, its use
/// may be dangerous and unreliable while running in grid mode.
/// </remarks>
/// <param name="name">Name of the notecard or its asset id</param>
/// <returns>Notecard text</returns>
public string osGetNotecard(string name)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osGetNotecard");
string text = LoadNotecard(name);
if (text == null)
{
OSSLShoutError("Notecard '" + name + "' could not be found.");
return "ERROR!";
}
else
{
return text;
}
}
/// <summary>
/// Get the number of lines in the given notecard.
/// </summary>
/// <remarks>
/// Instead of using the LSL Dataserver event to pull notecard data,
/// this will simply read the number of note card lines and return this data as an integer.
///
/// Warning - due to the synchronous method this function uses to fetch assets, its use
/// may be dangerous and unreliable while running in grid mode.
/// </remarks>
/// <param name="name">Name of the notecard or its asset id</param>
/// <returns></returns>
public int osGetNumberOfNotecardLines(string name)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osGetNumberOfNotecardLines");
UUID assetID = CacheNotecard(name);
if (assetID.IsZero())
{
OSSLShoutError("Notecard '" + name + "' could not be found.");
return -1;
}
return NotecardCache.GetLines(assetID);
}
public string osAvatarName2Key(string firstname, string lastname)
{
CheckThreatLevel(ThreatLevel.Low, "osAvatarName2Key");
ScenePresence sp = World.GetScenePresence(firstname, lastname);
if(sp is not null)
return sp.UUID.ToString();
IUserManagement userManager = World.RequestModuleInterface<IUserManagement>();
if (userManager is null)
{
OSSLShoutError("osAvatarName2Key: UserManagement module not available");
return string.Empty;
}
// Check local grid
UUID userID = userManager.GetUserIdByName(firstname, lastname);
if (!userID.IsZero())
return userID.ToString();
// HG ?
if (Util.ParseForeignAvatarName(firstname, lastname, out string realFirstName, out string realLastName, out string serverURI))
{
try
{
UserAgentServiceConnector userConnection = new(serverURI);
if (userConnection is not null)
{
userID = userConnection.GetUUID(realFirstName, realLastName);
if (!userID.IsZero())
{
userManager.AddUser(userID, realFirstName, realLastName, serverURI);
return userID.ToString();
}
}
}
catch (Exception /*e*/)
{
// m_log.Warn("[osAvatarName2Key] UserAgentServiceConnector - Unable to connect to destination grid ", e);
}
}
return ScriptBaseClass.NULL_KEY;
}
public string osKey2Name(string id)
{
CheckThreatLevel(ThreatLevel.Low, "osKey2Name");
if (UUID.TryParse(id, out UUID key))
{
ScenePresence sp = World.GetScenePresence(key);
if(sp is not null)
return sp.Name;
UserAccount account = World.UserAccountService.GetUserAccount(World.RegionInfo.ScopeID, key);
if (account is not null)
return account.Name;
if (m_ScriptEngine.World.GridUserService is not null)
{
GridUserInfo uInfo = m_ScriptEngine.World.GridUserService.GetGridUserInfo(key.ToString());
if (uInfo is not null)
{
if (Util.ParseFullUniversalUserIdentifier(uInfo.UserID, out _,
out string gridURL, out string firstName, out string lastName))
{
string grid = new Uri(gridURL).Authority;
return firstName + "." + lastName + " @" + grid;
}
}
}
}
return "";
}
private enum InfoType
{
Nick,
Name,
Login,
Home,
Custom
};
private string GridUserInfo(InfoType type)
{
return GridUserInfo(type, "");
}
private string GridUserInfo(InfoType type, string key)
{
IConfigSource config = m_ScriptEngine.ConfigSource;
string url = null;
IConfig gridInfoConfig = config.Configs["GridInfo"];
if (gridInfoConfig is not null)
url = gridInfoConfig.GetString("GridInfoURI", String.Empty);
if (string.IsNullOrEmpty(url))
return "Configuration Error!";
string verb ="/json_grid_info";
OSDMap info = WebUtil.GetFromService($"{url}{verb}", 3000);
if (info["Success"].AsBoolean() != true)
return "Get GridInfo Failed!";
OSDMap json = (OSDMap)OSDParser.DeserializeJson(info["_RawResult"].AsString());
return type switch
{
InfoType.Nick => (string)json["gridnick"],
InfoType.Name => (string)json["gridname"],
InfoType.Login => (string)json["login"],
InfoType.Home => (string)json["home"],
InfoType.Custom => (string)json[key],
_ => "error",
};
}
public string osSHA256(string input)
{
byte[] bytes;
// Create a SHA256
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
}
return Util.bytesToLowcaseHexString(bytes);
}
/// <summary>
/// Get the nickname of this grid, as set in the [GridInfo] config section.
/// </summary>
/// <remarks>
/// </remarks>
/// <returns></returns>
public string osGetGridNick()
{
return World.SceneGridInfo == null ? string.Empty : World.SceneGridInfo.GridNick;
}
public string osGetGridName()
{
return World.SceneGridInfo == null ? string.Empty : World.SceneGridInfo.GridName;
}
public string osGetGridLoginURI()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetGridLoginURI");
string loginURI = string.Empty;
IConfigSource config = m_ScriptEngine.ConfigSource;
if (config.Configs[GridInfoServiceConfigSectionName] != null)
loginURI = config.Configs[GridInfoServiceConfigSectionName].GetString("login", loginURI);
if (string.IsNullOrEmpty(loginURI))
loginURI = GridUserInfo(InfoType.Login);
return loginURI;
}
public string osGetGridHomeURI()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetGridHomeURI");
return World.SceneGridInfo == null ? string.Empty : World.SceneGridInfo.HomeURLNoEndSlash;
}
public string osGetGridGatekeeperURI()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetGridGatekeeperURI");
return World.SceneGridInfo == null ? string.Empty : World.SceneGridInfo.GateKeeperURLNoEndSlash;
}
public string osGetGridCustom(string key)
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetGridCustom");
string retval = string.Empty;
IConfigSource config = m_ScriptEngine.ConfigSource;
if (config.Configs[GridInfoServiceConfigSectionName] != null)
retval = config.Configs[GridInfoServiceConfigSectionName].GetString(key, retval);
if (string.IsNullOrEmpty(retval))
retval = GridUserInfo(InfoType.Custom, key);
return retval;
}
public string osGetAvatarHomeURI(string uuid)
{
CheckThreatLevel(ThreatLevel.Low, "osGetAvatarHomeURI");
IUserManagement userManager = m_ScriptEngine.World.RequestModuleInterface<IUserManagement>();
string returnValue;
if (userManager is null)
returnValue = string.Empty;
else
returnValue = userManager.GetUserServerURL(new UUID(uuid), "HomeURI");
if (returnValue.Length == 0)
return World.SceneGridInfo.HomeURLNoEndSlash;
return returnValue;
}
public LSL_String osFormatString(string str, LSL_List strings)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osFormatString");
return string.Format(str, strings.Data);
}
public LSL_List osMatchString(string src, string pattern, int start)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osMatchString");
LSL_List result = new();
// Normalize indices (if negative).
// After normlaization they may still be
// negative, but that is now relative to
// the start, rather than the end, of the
// sequence.
if (start < 0)
{
start = src.Length + start;
}
if (start < 0 || start >= src.Length)
{
return result; // empty list
}
// Find matches beginning at start position
Regex matcher = new(pattern);
Match match = matcher.Match(src, start);
while (match.Success)
{
foreach (System.Text.RegularExpressions.Group g in match.Groups)
{
if (g.Success)
{
result.Add(new LSL_String(g.Value));
result.Add(new LSL_Integer(g.Index));
}
}
match = match.NextMatch();
}
return result;
}
public LSL_String osReplaceString(string src, string pattern, string replace, int count, int start)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osReplaceString");
// Normalize indices (if negative).
// After normalization they may still be
// negative, but that is now relative to
// the start, rather than the end, of the
// sequence.
if (start < 0)
{
start = src.Length + start;
}
if (start < 0 || start >= src.Length)
{
return src;
}
// Find matches beginning at start position
Regex matcher = new(pattern);
return matcher.Replace(src,replace,count,start);
}
public string osLoadedCreationDate()
{
CheckThreatLevel(ThreatLevel.Low, "osLoadedCreationDate");
return World.RegionInfo.RegionSettings.LoadedCreationDate;
}
public string osLoadedCreationTime()
{
CheckThreatLevel(ThreatLevel.Low, "osLoadedCreationTime");
return World.RegionInfo.RegionSettings.LoadedCreationTime;
}
public string osLoadedCreationID()
{
CheckThreatLevel(ThreatLevel.Low, "osLoadedCreationID");
return World.RegionInfo.RegionSettings.LoadedCreationID;
}
/// <summary>
/// Get the primitive parameters of a linked prim.
/// </summary>
/// <remarks>
/// Threat level is 'Low' because certain users could possibly be tricked into
/// dropping an unverified script into one of their own objects, which could
/// then gather the physical construction details of the object and transmit it
/// to an unscrupulous third party, thus permitting unauthorized duplication of
/// the object's form.
/// </remarks>
/// <param name="linknumber"></param>
/// <param name="rules"></param>
/// <returns></returns>
public LSL_List osGetLinkPrimitiveParams(int linknumber, LSL_List rules)
{
CheckThreatLevel(ThreatLevel.High, "osGetLinkPrimitiveParams");
InitLSL();
// One needs to cast m_LSL_Api because we're using functions not
// on the ILSL_Api interface.
LSL_List retVal = new();
LSL_List remaining = new();
List<SceneObjectPart> parts = m_LSL_Api.GetLinkParts(linknumber);
foreach (SceneObjectPart part in parts)
{
remaining = m_LSL_Api.GetPrimParams(part, rules, ref retVal);
}
while (remaining.Length > 1)
{
linknumber = remaining.GetIntegerItem(0);
parts = m_LSL_Api.GetLinkParts(linknumber);
if(parts.Count == 0)
break;
rules = remaining.GetSublist(1, -1);
foreach (SceneObjectPart part in parts)
remaining = m_LSL_Api.GetPrimParams(part, rules, ref retVal);
}
return retVal;
}
public void osForceCreateLink(string target, int parent)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osForceCreateLink");
InitLSL();
m_LSL_Api.CreateLink(target, parent);
}
public void osForceBreakLink(int linknum)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osForceBreakLink");
InitLSL();
m_LSL_Api.BreakLink(linknum);
}
public void osForceBreakAllLinks()
{
CheckThreatLevel(ThreatLevel.VeryLow, "osForceBreakAllLinks");
InitLSL();
m_LSL_Api.BreakAllLinks();
}
public LSL_Integer osIsNpc(LSL_Key npc)
{
CheckThreatLevel();
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
if (UUID.TryParse(npc.m_string, out UUID npcId))
if (module.IsNPC(npcId, World))
return ScriptBaseClass.TRUE;
}
return ScriptBaseClass.FALSE;
}
public LSL_Key osNpcCreate(string firstname, string lastname, LSL_Vector position, string notecard)
{
CheckThreatLevel(ThreatLevel.High, "osNpcCreate");
// have to get the npc module also here to set the default Not Owned
INPCModule module = World.RequestModuleInterface<INPCModule>();
if(module is null)
return LSL_Key.NullKey;
bool owned = (module.NPCOptionFlags & NPCOptionsFlags.AllowNotOwned) == 0;
return NpcCreate(firstname, lastname, position, notecard, owned, false, false);
}
public LSL_Key osNpcCreate(string firstname, string lastname, LSL_Vector position, string notecard, int options)
{
CheckThreatLevel(ThreatLevel.High, "osNpcCreate");
return NpcCreate(
firstname, lastname, position, notecard,
(options & ScriptBaseClass.OS_NPC_NOT_OWNED) == 0,
(options & ScriptBaseClass.OS_NPC_SENSE_AS_AGENT) != 0,
(options & ScriptBaseClass.OS_NPC_OBJECT_GROUP) != 0);
}
private LSL_Key NpcCreate(
string firstname, string lastname, LSL_Vector position, string notecard, bool owned, bool senseAsAgent, bool hostGroupID)
{
if (!World.Permissions.CanRezObject(1, m_host.OwnerID, new Vector3((float)position.x, (float)position.y, (float)position.z)))
{
OSSLError("no permission to rez NPC at requested location");
return LSL_Key.NullKey;
}
INPCModule module = World.RequestModuleInterface<INPCModule>();
if(module is null)
{
OSSLError("NPC module not enabled");
return LSL_Key.NullKey;
}
string groupTitle = String.Empty;
UUID groupID = UUID.Zero;
AvatarAppearance appearance = null;
// check creation options
NPCOptionsFlags createFlags = module.NPCOptionFlags;
if((createFlags & NPCOptionsFlags.AllowNotOwned) == 0 && !owned)
{
OSSLError("Not owned NPCs disabled");
owned = true; // we should get here...
}
if((createFlags & NPCOptionsFlags.AllowSenseAsAvatar) == 0 && senseAsAgent)
{
OSSLError("NPC allow sense as Avatar disabled");
senseAsAgent = false;
}
if(hostGroupID && !m_host.GroupID.IsZero())
{
if (m_groupsModule != null)
{
GroupMembershipData member = m_groupsModule.GetMembershipData(m_host.GroupID, m_host.OwnerID);
if (member == null)
{
OSSLError(string.Format("osNpcCreate: the object owner is not member of the object group"));
return LSL_Key.NullKey;
}
groupID = m_host.GroupID;
if((createFlags & NPCOptionsFlags.NoNPCGroup) != 0)
{
GroupRecord grprec = m_groupsModule.GetGroupRecord(m_host.GroupID);
if(grprec != null && grprec.GroupName != "")
groupTitle = grprec.GroupName;
}
}
}
if((createFlags & NPCOptionsFlags.NoNPCGroup) == 0)
{
if (!string.IsNullOrEmpty(firstname) || !string.IsNullOrEmpty(lastname))
{
if (firstname != "Shown outfit:")
groupTitle = "- NPC -";
}
}
if((createFlags & NPCOptionsFlags.AllowCloneOtherAvatars) != 0)
{
if (UUID.TryParse(notecard, out UUID id))
{
ScenePresence clonePresence = World.GetScenePresence(id);
if (clonePresence != null)
appearance = clonePresence.Appearance;
}
}
if (appearance is null)
{
if(!string.IsNullOrWhiteSpace(notecard))
{
string appearanceSerialized = LoadNotecard(notecard);
if (appearanceSerialized is null)
{
OSSLError(string.Format("osNpcCreate: Notecard '{0}' not found.", notecard));
return LSL_Key.NullKey;
}
try
{
OSDMap appearanceOsd = (OSDMap)OSDParser.DeserializeLLSDXml(appearanceSerialized);
appearance = new AvatarAppearance();
appearance.Unpack(appearanceOsd);
}
catch
{
OSSLError(string.Format("osNpcCreate: Error processing notecard '{0}'", notecard));
return LSL_Key.NullKey;
}
}
}
UUID x = module.CreateNPC(firstname,
lastname,
position,
UUID.Random(),
owned ? m_host.OwnerID : UUID.Zero,
groupTitle,
groupID,
senseAsAgent,
World,
appearance);
if (World.TryGetScenePresence(x, out ScenePresence sp))
{
sp.SendAvatarDataToAllAgents();
}
return new LSL_Key(x.ToString());
}
/// <summary>
/// Save the current appearance of the NPC permanently to the named notecard.
/// </summary>
/// <param name="avatar"></param>
/// <param name="notecard">The name of the notecard to which to save the appearance.</param>
/// <returns>The asset ID of the notecard saved.</returns>
public LSL_Key osNpcSaveAppearance(LSL_Key npc, LSL_String notecard)
{
return NpcSaveAppearance(npc, notecard, false);
}
public LSL_Key osNpcSaveAppearance(LSL_Key npc, LSL_String notecard, LSL_Integer includeHuds)
{
return NpcSaveAppearance(npc, notecard, includeHuds == 0);
}
protected LSL_Key NpcSaveAppearance(LSL_Key npc, string notecard, bool NoHUds)
{
CheckThreatLevel(ThreatLevel.High, "osNpcSaveAppearance");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return LSL_Key.NullKey;
if (!npcModule.CheckPermissions(npcId, m_host.OwnerID))
return LSL_Key.NullKey;
return SaveAppearanceToNotecard(npcId, notecard, NoHUds);
}
return LSL_Key.NullKey;
}
public void osNpcLoadAppearance(LSL_Key npc, string notecard)
{
CheckThreatLevel(ThreatLevel.High, "osNpcLoadAppearance");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return;
if (!npcModule.CheckPermissions(npcId, m_host.OwnerID))
return;
string appearanceSerialized = LoadNotecard(notecard);
if (appearanceSerialized is null)
OSSLError(string.Format("osNpcCreate: Notecard reference '{0}' not found.", notecard));
OSDMap appearanceOsd = (OSDMap)OSDParser.DeserializeLLSDXml(appearanceSerialized);
// OSD a = OSDParser.DeserializeLLSDXml(appearanceSerialized);
// Console.WriteLine("appearanceSerialized {0}", appearanceSerialized);
// Console.WriteLine("a.Type {0}, a.ToString() {1}", a.Type, a);
AvatarAppearance appearance = new();
appearance.Unpack(appearanceOsd);
npcModule.SetNPCAppearance(npcId, appearance, m_host.ParentGroup.Scene);
}
}
public LSL_Key osNpcGetOwner(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.None, "osNpcGetOwner");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (UUID.TryParse(npc.m_string, out UUID npcId))
{
UUID owner = npcModule.GetOwner(npcId);
if (!owner.IsZero())
return new LSL_Key(owner.ToString());
else
return npc;
}
}
return LSL_Key.NullKey;
}
public LSL_Vector osNpcGetPos(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.High, "osNpcGetPos");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return LSL_Vector.Zero;
if (!npcModule.CheckPermissions(npcId, m_host.OwnerID))
return LSL_Vector.Zero;
ScenePresence sp = World.GetScenePresence(npcId);
if (sp is not null)
return new LSL_Vector(sp.AbsolutePosition);
}
return LSL_Vector.Zero;
}
public void osNpcMoveTo(LSL_Key npc, LSL_Vector pos)
{
CheckThreatLevel(ThreatLevel.High, "osNpcMoveTo");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return;
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.MoveToTarget(npcId, World, pos, false, true, false);
}
}
public void osNpcMoveToTarget(LSL_Key npc, LSL_Vector target, int options)
{
CheckThreatLevel(ThreatLevel.High, "osNpcMoveToTarget");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return;
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.MoveToTarget(
new UUID(npc.m_string),
World,
target,
(options & ScriptBaseClass.OS_NPC_NO_FLY) != 0,
(options & ScriptBaseClass.OS_NPC_LAND_AT_TARGET) != 0,
(options & ScriptBaseClass.OS_NPC_RUNNING) != 0);
}
}
public LSL_Rotation osNpcGetRot(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.High, "osNpcGetRot");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return LSL_Rotation.Identity;
if (!npcModule.CheckPermissions(npcId, m_host.OwnerID))
return LSL_Rotation.Identity;
ScenePresence sp = World.GetScenePresence(npcId);
if (sp is not null)
return new LSL_Rotation(sp.GetWorldRotation());
}
return LSL_Rotation.Identity;
}
public void osNpcSetRot(LSL_Key npc, LSL_Rotation rotation)
{
CheckThreatLevel(ThreatLevel.High, "osNpcSetRot");
INPCModule npcModule = World.RequestModuleInterface<INPCModule>();
if (npcModule is not null)
{
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return;
if (!npcModule.CheckPermissions(npcId, m_host.OwnerID))
return;
ScenePresence sp = World.GetScenePresence(npcId);
if (sp is not null)
sp.Rotation = rotation;
}
}
public void osNpcStopMoveToTarget(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.High, "osNpcStopMoveToTarget");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.StopMoveToTarget(npcId, World);
}
}
public void osNpcSetProfileAbout(LSL_Key npc, string about)
{
CheckThreatLevel(ThreatLevel.Low, "osNpcSetProfileAbout");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
ScenePresence sp = World.GetScenePresence(npcId);
if (sp is not null)
((INPC)(sp.ControllingClient)).profileAbout = about;
}
}
public void osNpcSetProfileImage(LSL_Key npc, string image)
{
CheckThreatLevel(ThreatLevel.Low, "osNpcSetProfileImage");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
UUID ImageID = ScriptUtils.GetAssetIdFromItemName(m_host, image, (int)AssetType.Texture);
if (ImageID.IsZero())
{
if (!UUID.TryParse(image, out ImageID))
return;
}
ScenePresence sp = World.GetScenePresence(npcId);
if (sp is not null)
((INPC)(sp.ControllingClient)).profileImage = ImageID;
}
}
public void osNpcSay(LSL_Key npc, string message)
{
osNpcSay(npc, 0, message);
}
public void osNpcSay(LSL_Key npc, int channel, string message)
{
CheckThreatLevel(ThreatLevel.High, "osNpcSay");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
InitLSL();
m_LSL_Api.ThrottleSay(channel, 2000);
module.Say(npcId, World, message, channel);
}
}
public void osNpcSayTo(LSL_Key npc, LSL_Key target, int channel, string msg)
{
CheckThreatLevel(ThreatLevel.High, "osNpcSayTo");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is null)
return;
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
if (wComm is null)
return;
if (!UUID.TryParse(npc.m_string, out UUID npcId))
return;
if (!UUID.TryParse(target.m_string, out UUID TargetID))
return;
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
ScenePresence NPCpresence = World.GetScenePresence(npcId);
if (NPCpresence is null || NPCpresence.IsDeleted || !NPCpresence.IsNPC)
return;
Vector3 npcPOS = NPCpresence.AbsolutePosition;
string npcNAME = NPCpresence.Name;
InitLSL();
m_LSL_Api.ThrottleSay(channel, 2000);
wComm.DeliverMessageTo(TargetID, channel, npcPOS, npcNAME, npcId, msg);
}
public void osNpcShout(LSL_Key npc, int channel, string message)
{
CheckThreatLevel(ThreatLevel.High, "osNpcShout");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
InitLSL();
m_LSL_Api.ThrottleSay(channel, 2000);
module.Shout(npcId, World, message, channel);
}
}
public void osNpcSit(LSL_Key npc, LSL_Key target, int options)
{
CheckThreatLevel(ThreatLevel.High, "osNpcSit");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.Sit(npcId, new UUID(target.m_string), World);
}
}
public void osNpcStand(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.High, "osNpcStand");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.Stand(npcId, World);
}
}
public void osNpcRemove(LSL_Key npc)
{
CheckThreatLevel(ThreatLevel.High, "osNpcRemove");
try
{
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.DeleteNPC(npcId, World);
}
}
catch { }
}
public void osNpcPlayAnimation(LSL_Key npc, string animation)
{
CheckThreatLevel(ThreatLevel.High, "osNpcPlayAnimation");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is null)
return;
if (!UUID.TryParse(npc.m_string, out UUID npcID))
return;
ScenePresence target = World.GetScenePresence(npcID);
if (target is null || !target.IsNPC)
return;
if (!module.CheckPermissions(npcID, m_host.OwnerID))
return;
UUID animID = UUID.Zero;
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in m_host.TaskInventory)
{
if (inv.Value.Type == (int)AssetType.Animation)
{
if (inv.Value.Name == animation)
{
animID = inv.Value.AssetID;
break;
}
}
}
m_host.TaskInventory.LockItemsForRead(false);
if (animID.IsZero())
target.Animator.AddAnimation(animation, m_host.UUID);
else
target.Animator.AddAnimation(animID, m_host.UUID);
}
public void osNpcStopAnimation(LSL_Key npc, string animation)
{
CheckThreatLevel(ThreatLevel.High, "osNpcStopAnimation");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is null)
return;
if (!UUID.TryParse(npc.m_string, out UUID npcID))
return;
ScenePresence target = World.GetScenePresence(npcID);
if (target is null || !target.IsNPC)
return;
if (!module.CheckPermissions(npcID, m_host.OwnerID))
return;
if (!UUID.TryParse(animation, out UUID animID))
{
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(animation);
if (item is not null && item.Type == (int)AssetType.Animation)
animID = item.AssetID;
else
animID = UUID.Zero;
}
if (animID.IsZero())
target.Animator.RemoveAnimation(animation);
else
target.Animator.RemoveAnimation(animID, true);
}
public void osNpcWhisper(LSL_Key npc, int channel, string message)
{
CheckThreatLevel(ThreatLevel.High, "osNpcWhisper");
INPCModule module = World.RequestModuleInterface<INPCModule>();
if (module is not null)
{
UUID npcId = new(npc.m_string);
if (!module.CheckPermissions(npcId, m_host.OwnerID))
return;
module.Whisper(npcId, World, message, channel);
}
}
public void osNpcTouch(LSL_Key npcLSL_Key, LSL_Key object_key, LSL_Integer link_num)
{
CheckThreatLevel(ThreatLevel.High, "osNpcTouch");
INPCModule module = World.RequestModuleInterface<INPCModule>();
int linkNum = link_num.value;
if (module is not null || (linkNum < 0 && linkNum != ScriptBaseClass.LINK_THIS))
{
if (!UUID.TryParse(npcLSL_Key, out UUID npcId) || !module.CheckPermissions(npcId, m_host.OwnerID))
return;
SceneObjectPart part = null;
if (UUID.TryParse(object_key.m_string, out UUID objectId))
part = World.GetSceneObjectPart(objectId);
if (part is null)
return;
if (linkNum != ScriptBaseClass.LINK_THIS)
{
if (linkNum == 0 || linkNum == ScriptBaseClass.LINK_ROOT)
{ // 0 and 1 are treated as root, find the root if the current part isnt it
part = part.ParentGroup.RootPart;
}
else
{ // Find the prim with the given link number if not found then fail silently
part = part.ParentGroup.GetLinkNumPart(linkNum);
}
}
if (part is null)
return;
module.Touch(npcId, part.UUID);
}
}
/// <summary>
/// Save the current appearance of the script owner permanently to the named notecard.
/// </summary>
/// <param name="notecard">The name of the notecard to which to save the appearance.</param>
/// <returns>The asset ID of the notecard saved.</returns>
public LSL_Key osOwnerSaveAppearance(LSL_String notecard)
{
CheckThreatLevel(ThreatLevel.High, "osOwnerSaveAppearance");
return SaveAppearanceToNotecard(m_host.OwnerID, notecard, false);
}
public LSL_Key osOwnerSaveAppearance(LSL_String notecard, LSL_Integer includeHuds)
{
CheckThreatLevel(ThreatLevel.High, "osOwnerSaveAppearance");
return SaveAppearanceToNotecard(m_host.OwnerID, notecard, includeHuds == 0);
}
public LSL_Key osAgentSaveAppearance(LSL_Key avatarKey, LSL_String notecard)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osAgentSaveAppearance");
if (!UUID.TryParse(avatarKey, out UUID avatarId))
return new LSL_Key(ScriptBaseClass.NULL_KEY);
return SaveAppearanceToNotecard(avatarId, notecard, false);
}
public LSL_Key osAgentSaveAppearance(LSL_Key avatarKey, LSL_String notecard, LSL_Integer includeHuds)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osAgentSaveAppearance");
if (!UUID.TryParse(avatarKey, out UUID avatarId))
return LSL_Key.NullKey;
return SaveAppearanceToNotecard(avatarId, notecard, includeHuds == 0);
}
protected LSL_Key SaveAppearanceToNotecard(UUID avatarId, string notecard, bool NoHuds)
{
ScenePresence sp = World.GetScenePresence(avatarId);
if (sp is null || sp.IsChildAgent)
return LSL_Key.NullKey;
return SaveAppearanceToNotecard(sp, notecard, NoHuds);
}
protected LSL_Key SaveAppearanceToNotecard(ScenePresence sp, string notecard, bool NoHuds)
{
IAvatarFactoryModule appearanceModule = World.RequestModuleInterface<IAvatarFactoryModule>();
if (appearanceModule is not null)
{
appearanceModule.SaveBakedTextures(sp.UUID);
OSDMap appearancePacked = sp.Appearance.PackForNotecard(NoHuds);
TaskInventoryItem item
= SaveNotecard(notecard, "Avatar Appearance", Util.GetFormattedXml(appearancePacked as OSD), true);
return new LSL_Key(item.AssetID.ToString());
}
return LSL_Key.NullKey;
}
/// <summary>
/// Get the gender as specified in avatar appearance for a given avatar key
/// </summary>
/// <param name="rawAvatarId"></param>
/// <returns>"male" or "female" or "unknown"</returns>
public LSL_String osGetGender(LSL_Key rawAvatarId)
{
CheckThreatLevel(ThreatLevel.None, "osGetGender");
if (!UUID.TryParse(rawAvatarId, out UUID avatarId))
return new LSL_String("unknown");
ScenePresence sp = World.GetScenePresence(avatarId);
if (sp is null || sp.IsChildAgent || sp.Appearance is null || sp.Appearance.VisualParams is null)
return new LSL_String("unknown");
// find the index of "shape" parameter "male"
int vpShapeMaleIndex = 0;
bool indexFound = false;
VisualParam param = new();
foreach(var vpEntry in VisualParams.Params)
{
param = vpEntry.Value;
if (param.Name == "male" && param.Wearable == "shape")
{
indexFound = true;
break;
}
if (param.Group == 0)
vpShapeMaleIndex++;
}
if (!indexFound)
return new LSL_String("unknown");
float vpShapeMale = Utils.ByteToFloat(sp.Appearance.VisualParams[vpShapeMaleIndex], param.MinValue, param.MaxValue);
bool isMale = vpShapeMale > 0.5f;
return new LSL_String(isMale ? "male" : "female");
}
/// <summary>
/// Get current region's map texture UUID
/// </summary>
/// <returns></returns>
public LSL_Key osGetMapTexture()
{
CheckThreatLevel();
return m_ScriptEngine.World.RegionInfo.RegionSettings.TerrainImageID.ToString();
}
/// <summary>
/// Get a region's map texture UUID by region UUID or name.
/// </summary>
/// <param name="regionName"></param>
/// <returns></returns>
public LSL_Key osGetRegionMapTexture(string regionNameOrID)
{
CheckThreatLevel(ThreatLevel.High, "osGetRegionMapTexture");
Scene scene = m_ScriptEngine.World;
GridRegion region;
if(string.IsNullOrWhiteSpace(regionNameOrID))
return scene.RegionInfo.RegionSettings.TerrainImageID.ToString();
//If string is a key, use it. Otherwise, try to locate region by name.
if (UUID.TryParse(regionNameOrID, out UUID key))
region = scene.GridService.GetRegionByUUID(UUID.Zero, key);
else
region = scene.GridService.GetRegionByName(UUID.Zero, regionNameOrID);
// If region was found, return the regions map texture key.
if (region is not null)
key = region.TerrainImage;
ScriptSleep(1000);
return new LSL_Key(key.ToString());
}
/// <summary>
/// Return information regarding various simulator statistics (sim fps, physics fps, time
/// dilation, total number of prims, total number of active scripts, script lps, various
/// timing data, packets in/out, etc. Basically much the information that's shown in the
/// client's Statistics Bar (Ctrl-Shift-1)
/// </summary>
/// <returns>List of floats</returns>
public LSL_List osGetRegionStats()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetRegionStats");
LSL_List ret = new();
float[] stats = World.StatsReporter.LastReportedSimStats;
for (int i = 0; i < stats.Length; i++)
{
ret.Add(new LSL_Float(stats[i]));
}
return ret;
}
public LSL_Vector osGetRegionSize()
{
CheckThreatLevel();
//Scene scene = m_ScriptEngine.World;
RegionInfo reg = World.RegionInfo;
//GridRegion region = scene.GridService.GetRegionByUUID(UUID.Zero, World.RegionInfo.RegionID);
//return new LSL_Vector((float)region.RegionSizeX, (float)region.RegionSizeY, (float)Constants.RegionHeight);
return new LSL_Vector((float)reg.RegionSizeX, (float)reg.RegionSizeY, 0.0f);
}
public int osGetSimulatorMemory()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetSimulatorMemory");
long pws = Util.GetPhysicalMemUse();
if (pws > Int32.MaxValue)
return Int32.MaxValue;
if (pws < 0)
return 0;
return (int)pws;
}
public int osGetSimulatorMemoryKB()
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetSimulatorMemoryKB");
long pws = Util.GetPhysicalMemUse();
if ((pws & 0x3FFL) != 0)
pws += 0x400L;
pws >>= 10;
if (pws > Int32.MaxValue)
return Int32.MaxValue;
return (int)pws;
}
public void osSetSpeed(string ID, LSL_Float SpeedModifier)
{
CheckThreatLevel(ThreatLevel.Moderate, "osSetSpeed");
if (!UUID.TryParse(ID, out UUID avid))
return;
ScenePresence avatar = World.GetScenePresence(avid);
if (avatar is not null)
avatar.SpeedModifier = (float)SpeedModifier;
}
public void osSetOwnerSpeed(LSL_Float SpeedModifier)
{
CheckThreatLevel(ThreatLevel.Moderate, "osSetOwnerSpeed");
if(SpeedModifier > 4)
SpeedModifier = 4;
ScenePresence avatar = World.GetScenePresence(m_host.OwnerID);
if (avatar is not null)
avatar.SpeedModifier = (float)SpeedModifier;
}
public void osKickAvatar(string FirstName, string SurName, string alert)
{
CheckThreatLevel(ThreatLevel.Severe, "osKickAvatar");
if(string.IsNullOrEmpty(alert))
{
World.ForEachRootScenePresence(delegate (ScenePresence sp)
{
if (sp.Firstname == FirstName && sp.Lastname == SurName)
sp.Scene.CloseAgent(sp.UUID, false);
});
}
else
{
World.ForEachRootScenePresence(delegate (ScenePresence sp)
{
if (sp.Firstname == FirstName && sp.Lastname == SurName)
{
sp.ControllingClient.Kick(alert);
sp.Scene.CloseAgent(sp.UUID, false);
}
});
}
}
public void osKickAvatar(LSL_Key agentKey, string alert)
{
CheckThreatLevel(ThreatLevel.Severe, "osKickAvatar");
if (!UUID.TryParse(agentKey, out UUID id) || id.IsZero())
return;
ScenePresence sp = World.GetScenePresence(id);
if(sp is null)
return;
// kick client...
if (!string.IsNullOrEmpty(alert))
sp.ControllingClient.Kick(alert);
// ...and close on our side
sp.Scene.CloseAgent(id, false);
}
public LSL_Float osGetHealth(LSL_Key agentKey)
{
CheckThreatLevel(ThreatLevel.None, "osGetHealth");
LSL_Float health = new(-1);
if (!UUID.TryParse(agentKey, out UUID id) || id.IsZero())
return health;
ScenePresence presence = World.GetScenePresence(id);
if (presence is not null)
health = presence.Health;
return health;
}
public void osCauseDamage(LSL_Key avatar, LSL_Float damage)
{
CheckThreatLevel(ThreatLevel.High, "osCauseDamage");
if (!UUID.TryParse(avatar, out UUID avatarId))
return;
ScenePresence presence = World.GetScenePresence(avatarId);
if (presence is null)
return;
Vector3 pos = m_host.GetWorldPosition();
LandData land = World.GetLandData(pos);
if ((land.Flags & (uint)ParcelFlags.AllowDamage) == (uint)ParcelFlags.AllowDamage)
{
float health = presence.Health;
health -= (float)damage;
presence.setHealthWithUpdate(health);
if (health <= 0)
{
// check to see if it is an NPC and just remove it
if (presence.IsNPC)
{
INPCModule NPCmodule = World.RequestModuleInterface<INPCModule>();
NPCmodule?.DeleteNPC(presence.UUID, World);
return;
}
float healthliveagain = 100;
presence.ControllingClient.SendAgentAlertMessage("You died!", true);
presence.setHealthWithUpdate(healthliveagain);
presence.Scene.TeleportClientHome(presence.UUID, presence.ControllingClient);
}
}
}
public void osCauseHealing(LSL_Key avatar, LSL_Float healing)
{
CheckThreatLevel(ThreatLevel.High, "osCauseHealing");
if (!UUID.TryParse(avatar, out UUID avatarId))
return;
ScenePresence presence = World.GetScenePresence(avatarId);
if (presence is null)
return;
float health = presence.Health;
health += (float)healing;
if (health >= 100)
health = 100;
presence.setHealthWithUpdate(health);
}
public void osSetHealth(LSL_Key avatar, LSL_Float health)
{
CheckThreatLevel(ThreatLevel.High, "osSetHealth");
if (!UUID.TryParse(avatar, out UUID avatarId))
return;
ScenePresence presence = World.GetScenePresence(avatarId);
if (presence is not null)
{
if (health > 100.0)
health = 100.0;
else if (health < 1.0)
health = 1.0;
presence.setHealthWithUpdate((float)health);
}
}
public void osSetHealRate(LSL_Key avatar, LSL_Float healrate)
{
CheckThreatLevel(ThreatLevel.High, "osSetHealRate");
if (!UUID.TryParse(avatar, out UUID avatarId))
return;
ScenePresence presence = World.GetScenePresence(avatarId);
if (presence is null)
return;
presence.HealRate = (float)healrate;
}
public LSL_Float osGetHealRate(LSL_Key avatar)
{
CheckThreatLevel(ThreatLevel.None, "osGetHealRate");
LSL_Float rate = new(0);
if (!UUID.TryParse(avatar, out UUID avatarId))
return rate;
ScenePresence presence = World.GetScenePresence(avatarId);
if (presence is not null)
rate = presence.HealRate;
return rate;
}
public LSL_List osGetPrimitiveParams(LSL_Key prim, LSL_List rules)
{
CheckThreatLevel();
InitLSL();
return m_LSL_Api.GetPrimitiveParamsEx(prim, rules);
}
public void osSetPrimitiveParams(LSL_Key prim, LSL_List rules)
{
CheckThreatLevel();
InitLSL();
m_LSL_Api.SetPrimitiveParamsEx(prim, rules, "osSetPrimitiveParams");
}
/// <summary>
/// Set parameters for light projection in host prim
/// </summary>
public void osSetProjectionParams(LSL_Integer projection, LSL_Key texture, LSL_Float fov, LSL_Float focus, LSL_Float amb)
{
SetProjectionParams(m_host, projection, texture, fov, focus, amb);
}
/// <summary>
/// Set parameters for light projection of a linkset prim
/// </summary>
public void osSetProjectionParams(LSL_Integer linknum, LSL_Integer projection, LSL_Key texture, LSL_Float fov, LSL_Float focus, LSL_Float amb)
{
if (linknum == ScriptBaseClass.LINK_THIS || linknum == m_host.LinkNum)
{
SetProjectionParams(m_host, projection, texture, fov, focus, amb);
return;
}
if (linknum < 0 || linknum > m_host.ParentGroup.PrimCount)
return;
if(linknum < 2 && m_host.LinkNum < 2)
{
SetProjectionParams(m_host, projection, texture, fov, focus, amb);
return;
}
SceneObjectPart obj = m_host.ParentGroup.GetLinkNumPart(linknum);
if(obj is not null)
SetProjectionParams(obj, projection, texture, fov, focus, amb);
}
/// <summary>
/// Set parameters for light projection with uuid of target prim
/// </summary>
public void osSetProjectionParams(LSL_Key prim, LSL_Integer llprojection, LSL_Key texture, LSL_Float fov, LSL_Float focus, LSL_Float amb)
{
if(UUID.TryParse(prim, out UUID pID) && pID.IsNotZero())
{
SceneObjectPart obj = World.GetSceneObjectPart(pID);
if(obj is not null)
{
if(obj.OwnerID.Equals(m_host.OwnerID))
SetProjectionParams(obj, llprojection, texture, fov, focus, amb);
}
}
else
SetProjectionParams(m_host, llprojection, texture, fov, focus, amb);
}
private static void SetProjectionParams(SceneObjectPart obj, LSL_Integer llprojection, LSL_Key texture, LSL_Float fov, LSL_Float focus, LSL_Float amb)
{
if(obj is null || obj.IsDeleted || obj.Shape == null)
return;
if(llprojection != 0)
{
if (!UUID.TryParse(texture, out UUID texID))
return;
obj.Shape.ProjectionEntry = true;
obj.Shape.ProjectionTextureUUID = texID;
obj.Shape.ProjectionFOV = Util.Clamp((float)fov, 0, 3.0f);
obj.Shape.ProjectionFocus = Util.Clamp((float)focus, -20.0f, 20.0f);
obj.Shape.ProjectionAmbiance = Util.Clamp((float)amb, 0, 1.0f);
obj.ParentGroup.HasGroupChanged = true;
obj.ScheduleFullUpdate();
return;
}
if(obj.Shape.ProjectionEntry)
{
obj.Shape.ProjectionEntry = false;
obj.ParentGroup.HasGroupChanged = true;
obj.ScheduleFullUpdate();
}
}
/// <summary>
/// Like osGetAgents but returns enough info for a radar
/// </summary>
/// <returns>Strided list of the UUID, position and name of each avatar in the region</returns>
public LSL_List osGetAvatarList()
{
CheckThreatLevel(ThreatLevel.None, "osGetAvatarList");
LSL_List result = new();
World.ForEachRootScenePresence(delegate (ScenePresence avatar)
{
if (avatar is not null && !avatar.IsDeleted && avatar.UUID.NotEqual(m_host.OwnerID))
{
result.Add(new LSL_String(avatar.UUID.ToString()));
result.Add(new LSL_Vector(avatar.AbsolutePosition));
result.Add(new LSL_String(avatar.Name));
}
});
return result;
}
public LSL_List osGetNPCList()
{
CheckThreatLevel(ThreatLevel.None, "osGetNPCList");
LSL_List result = new();
World.ForEachRootScenePresence(delegate (ScenePresence avatar)
{
// npcs are not childagents but that is now.
if (avatar is not null && avatar.IsNPC && !avatar.IsDeleted && !avatar.IsChildAgent && !avatar.IsInTransit)
{
result.Add(new LSL_String(avatar.UUID.ToString()));
result.Add(new LSL_Vector(avatar.AbsolutePosition));
result.Add(new LSL_String(avatar.Name));
}
});
return result;
}
/// <summary>
/// Convert a unix time to a llGetTimestamp() like string
/// </summary>
/// <param name="unixTime"></param>
/// <returns></returns>
public LSL_String osUnixTimeToTimestamp(LSL_Integer time)
{
return new LSL_String(Util.ToDateTime(time.value).ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ"));
}
/// <summary>
/// Invite user to the group this object is set to
/// </summary>
/// <param name="agentId"></param>
/// <returns></returns>
public LSL_Integer osInviteToGroup(LSL_Key agentId)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osInviteToGroup");
// groups module is required
if (m_groupsModule is null)
return ScriptBaseClass.FALSE;
if (!UUID.TryParse(agentId, out UUID agent))
return ScriptBaseClass.FALSE;
// object has to be set to a group, but not group owned
if (m_host.GroupID.IsZero() || m_host.GroupID.Equals(m_host.OwnerID))
return ScriptBaseClass.FALSE;
// invited agent has to be present in this scene
ScenePresence sp = World.GetScenePresence(agent);
if (sp is null || sp.IsNPC || sp.IsChildAgent || !sp.ControllingClient.IsActive)
return ScriptBaseClass.FALSE;
if (sp.ControllingClient.IsGroupMember(m_host.GroupID))
return 2;
// object owner needs invite power
ulong ownerPowers = m_groupsModule.GetFullGroupPowers(m_host.OwnerID, m_host.GroupID);
if ((ownerPowers & (ulong)GroupPowers.Invite) == 0)
return ScriptBaseClass.FALSE;
m_groupsModule.InviteGroup(null, m_host.OwnerID, m_host.GroupID, agent, UUID.Zero);
return ScriptBaseClass.TRUE;
}
/// <summary>
/// Eject user from the group this object is set to
/// </summary>
/// <param name="agentId"></param>
/// <returns></returns>
public LSL_Integer osEjectFromGroup(LSL_Key agentId)
{
CheckThreatLevel(ThreatLevel.VeryLow, "osEjectFromGroup");
// groups module is required
if (m_groupsModule is null)
return ScriptBaseClass.FALSE;
if (!UUID.TryParse(agentId, out UUID agent))
return ScriptBaseClass.FALSE;
// object has to be set to a group, but not group owned
if (m_host.GroupID.IsZero() || m_host.GroupID.Equals(m_host.OwnerID))
return ScriptBaseClass.FALSE;
// object owner needs eject power
ulong ownerPowers = m_groupsModule.GetFullGroupPowers(m_host.OwnerID, m_host.GroupID);
if ((ownerPowers & (ulong)GroupPowers.Eject) == 0)
return ScriptBaseClass.FALSE;
m_groupsModule.EjectGroupMember(null, m_host.OwnerID, m_host.GroupID, agent);
return ScriptBaseClass.TRUE;
}
/// <summary>
/// Sets terrain estate texture
/// </summary>
/// <param name="level"></param>
/// <param name="texture"></param>
/// <returns></returns>
public void osSetTerrainTexture(int level, LSL_Key texture)
{
CheckThreatLevel(ThreatLevel.High, "osSetTerrainTexture");
//Check to make sure that the script's owner is the estate manager/master
//World.Permissions.GenericEstatePermission(
if (World.Permissions.IsGod(m_host.OwnerID))
{
if (level < 0 || level > 3)
return;
if (!UUID.TryParse(texture, out UUID textureID))
return;
// estate module is required
IEstateModule estate = World.RequestModuleInterface<IEstateModule>();
estate?.setEstateTerrainBaseTexture(level, textureID);
}
}
/// <summary>
/// Sets terrain heights of estate
/// </summary>
/// <param name="corner"></param>
/// <param name="low"></param>
/// <param name="high"></param>
/// <returns></returns>
public void osSetTerrainTextureHeight(int corner, double low, double high)
{
CheckThreatLevel(ThreatLevel.High, "osSetTerrainTextureHeight");
//Check to make sure that the script's owner is the estate manager/master
//World.Permissions.GenericEstatePermission(
if (World.Permissions.IsGod(m_host.OwnerID))
{
if (corner < 0 || corner > 3)
return;
// estate module is required
IEstateModule estate = World.RequestModuleInterface<IEstateModule>();
estate?.setEstateTerrainTextureHeights(corner, (float)low, (float)high);
}
}
#region Attachment commands
public void osForceAttachToAvatar(int attachmentPoint)
{
CheckThreatLevel(ThreatLevel.High, "osForceAttachToAvatar");
InitLSL();
m_LSL_Api.AttachToAvatar(attachmentPoint);
}
public void osForceAttachToAvatarFromInventory(string itemName, int attachmentPoint)
{
CheckThreatLevel(ThreatLevel.High, "osForceAttachToAvatarFromInventory");
ForceAttachToAvatarFromInventory(m_host.OwnerID, itemName, attachmentPoint);
}
public void osForceAttachToOtherAvatarFromInventory(string rawAvatarId, string itemName, int attachmentPoint)
{
CheckThreatLevel(ThreatLevel.VeryHigh, "osForceAttachToOtherAvatarFromInventory");
if (!UUID.TryParse(rawAvatarId, out UUID avatarId))
return;
ForceAttachToAvatarFromInventory(avatarId, itemName, attachmentPoint);
}
public void ForceAttachToAvatarFromInventory(UUID avatarId, string itemName, int attachmentPoint)
{
IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule;
if (attachmentsModule is null)
return;
InitLSL();
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(itemName);
if (item is null)
{
m_LSL_Api.llSay(0, $"Could not find object '{itemName}'");
throw new Exception($"The inventory item '{itemName}' could not be found");
}
if (item.InvType != (int)InventoryType.Object)
{
m_LSL_Api.llSay(0, $"Unable to attach, item '{itemName}' is not an object.");
throw new Exception($"The inventory item '{itemName}' is not an object");
}
if ((item.Flags & (uint)InventoryItemFlags.ObjectHasMultipleItems) != 0)
{
m_LSL_Api.llSay(0, $"Unable to attach coalesced object, item '{itemName}'");
throw new Exception($"The inventory item '{itemName}' is a coalesced object");
}
ScenePresence sp = World.GetScenePresence(avatarId);
if (sp is null)
return;
InventoryItemBase newItem = World.MoveTaskInventoryItem(sp.UUID, UUID.Zero, m_host, item.ItemID, out string message);
if (newItem is null)
{
m_log.ErrorFormat(
"[OSSL API]: Could not create user inventory item {0} for {1}, attach point {2} in {3}: {4}",
itemName, m_host.Name, attachmentPoint, World.Name, message);
m_LSL_Api.llSay(0, message);
return;
}
attachmentsModule.RezSingleAttachmentFromInventory(sp, newItem.ID, (uint)attachmentPoint);
}
public void osForceDetachFromAvatar()
{
CheckThreatLevel(ThreatLevel.High, "osForceDetachFromAvatar");
InitLSL();
m_LSL_Api.DetachFromAvatar();
}
private static bool listObjToInt(object p, out int i)
{
try
{
if (p is LSL_Integer lpi)
i = lpi.value;
else if (p is int pi)
i = pi;
else if (p is uint upi)
i = (int)upi;
else if (p is string ps)
return int.TryParse(ps, out i);
else if (p is LSL_String ps1)
return int.TryParse(ps1.m_string, out i);
else
{
i = 0;
return false;
}
return true;
}
catch
{
i = 0;
return false;
}
}
public LSL_List osGetNumberOfAttachments(LSL_Key avatar, LSL_List attachmentPoints)
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetNumberOfAttachments");
LSL_List resp = new();
if (attachmentPoints.Length >= 1 && UUID.TryParse(avatar.ToString(), out UUID targetUUID) && World.TryGetScenePresence(targetUUID, out ScenePresence target))
{
foreach (object point in attachmentPoints.Data)
{
if(listObjToInt(point, out int ipoint))
{
resp.Add(new LSL_Integer(ipoint));
if (ipoint <= 0)
{
// indicates zero attachments
resp.Add(new LSL_Integer(0));
}
else
{
// gets the number of attachments on the attachment point
resp.Add(new LSL_Integer(target.GetAttachments((uint)ipoint).Count));
}
}
}
}
return resp;
}
public void osMessageAttachments(LSL_Key avatar, string message, LSL_List attachmentPoints, int options)
{
CheckThreatLevel(ThreatLevel.Moderate, "osMessageAttachments");
if (!UUID.TryParse(avatar.ToString(), out UUID targetUUID) || targetUUID.IsZero())
return;
if (!World.TryGetScenePresence(targetUUID, out ScenePresence target))
return;
if (target.IsDeleted || target.IsInTransit)
return;
if(target.GetAttachmentsCount() == 0)
return;
bool invertPoints = (options & ScriptBaseClass.OS_ATTACH_MSG_INVERT_POINTS) != 0;
bool msgAll = false;
List<int> aps;
if(attachmentPoints.Length > 0)
{
aps = new List<int>(attachmentPoints.Length);
foreach (object point in attachmentPoints.Data)
{
if (!listObjToInt(point, out int ipoint))
return;
if(ipoint == ScriptBaseClass.OS_ATTACH_MSG_ALL)
{
if(invertPoints)
return;
msgAll = true;
break;
}
else if(ipoint > 0)
aps.Add(ipoint);
}
}
else
aps = new List<int>();
if(aps.Count == 0)
{
if (!invertPoints && !msgAll)
return;
msgAll = true;
invertPoints = false;
}
List<SceneObjectGroup> attachments;
if (msgAll || invertPoints)
{
attachments = target.GetAttachments();
}
else
{
attachments = new List<SceneObjectGroup>();
foreach (int point in aps)
attachments.AddRange(target.GetAttachments((uint)point));
}
// if we have no attachments at this point, exit now
if (attachments.Count == 0)
return;
bool optionObjCreator = (options & ScriptBaseClass.OS_ATTACH_MSG_OBJECT_CREATOR) != 0;
bool optionScriptCreator = (options & ScriptBaseClass.OS_ATTACH_MSG_SCRIPT_CREATOR) != 0;
UUID hostCreatorID = m_host.CreatorID;
UUID itemCreatorID = m_item.CreatorID;
//int penalty = 100;
foreach (SceneObjectGroup sog in attachments)
{
if(sog.IsDeleted || sog.inTransit || ((sog.ScriptEvents & scriptEvents.dataserver) == 0))
continue;
if (invertPoints && aps.Contains((int)sog.AttachmentPoint))
continue;
UUID CreatorID = sog.RootPart.CreatorID;
if (optionObjCreator && CreatorID.NotEqual(hostCreatorID))
continue;
if (optionScriptCreator && CreatorID.NotEqual(itemCreatorID))
continue;
SceneObjectPart[] parts = sog.Parts;
foreach(SceneObjectPart p in parts)
{
if((p.ScriptEvents & scriptEvents.dataserver) != 0)
{
MessageObject(p.UUID, message);
//penalty += 10;
}
}
}
//ScriptSleep(penalty);
}
#endregion
/// <summary>
/// Checks if thing is a UUID.
/// </summary>
/// <param name="thing"></param>
/// <returns>1 if thing is a valid UUID, 0 otherwise</returns>
public LSL_Integer osIsUUID(string thing)
{
return UUID.TryParse(thing, out _) ? 1 : 0;
}
/// <summary>
/// Wraps to Math.Min()
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public LSL_Float osMin(double a, double b)
{
return Math.Min(a, b);
}
/// <summary>
/// Wraps to Math.max()
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public LSL_Float osMax(double a, double b)
{
return Math.Max(a, b);
}
public LSL_Key osGetRezzingObject()
{
CheckThreatLevel(ThreatLevel.None, "osGetRezzingObject");
UUID rezID = m_host.ParentGroup.RezzerID;
if(rezID.IsZero() || m_host.ParentGroup.Scene.GetScenePresence(rezID) is not null)
return LSL_Key.NullKey;
return new LSL_Key(rezID.ToString());
}
/// <summary>
/// Sets the response type for an HTTP request/response
/// </summary>
/// <returns></returns>
public void osSetContentType(LSL_Key id, string type)
{
CheckThreatLevel(ThreatLevel.Severe, "osSetContentType");
m_UrlModule?.HttpContentType(new UUID(id),type);
}
/// Shout an error if the object owner did not grant the script the specified permissions.
/// </summary>
/// <param name="perms"></param>
/// <returns>boolean indicating whether an error was shouted.</returns>
protected bool ShoutErrorOnLackingOwnerPerms(int perms, string errorPrefix)
{
if ((m_item.PermsMask & perms) == 0)
{
OSSLShoutError($"{errorPrefix}. Permissions not granted.");
return true;
}
if (m_item.PermsGranter.NotEqual(m_host.OwnerID))
{
OSSLShoutError($"{errorPrefix}. Permissions not granted to owner.");
return true;
}
return false;
}
protected void DropAttachment(bool checkPerms)
{
if (checkPerms && ShoutErrorOnLackingOwnerPerms(ScriptBaseClass.PERMISSION_ATTACH, "Cannot drop attachment"))
return;
IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule;
if (attachmentsModule is null)
return;
ScenePresence sp = m_host.ParentGroup.Scene.GetScenePresence(m_host.ParentGroup.OwnerID);
if (sp is not null)
attachmentsModule.DetachSingleAttachmentToGround(sp, m_host.ParentGroup.LocalId);
}
protected void DropAttachmentAt(bool checkPerms, LSL_Vector pos, LSL_Rotation rot)
{
if (checkPerms && ShoutErrorOnLackingOwnerPerms(ScriptBaseClass.PERMISSION_ATTACH, "Cannot drop attachment"))
return;
IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule;
if (attachmentsModule is null)
return;
ScenePresence sp = m_host.ParentGroup.Scene.GetScenePresence(m_host.ParentGroup.OwnerID);
if (sp is not null)
attachmentsModule.DetachSingleAttachmentToGround(sp, m_host.ParentGroup.LocalId, pos, rot);
}
public void osDropAttachment()
{
CheckThreatLevel(ThreatLevel.Moderate, "osDropAttachment");
DropAttachment(true);
}
public void osForceDropAttachment()
{
CheckThreatLevel(ThreatLevel.High, "osForceDropAttachment");
DropAttachment(false);
}
public void osDropAttachmentAt(LSL_Vector pos, LSL_Rotation rot)
{
CheckThreatLevel(ThreatLevel.Moderate, "osDropAttachmentAt");
DropAttachmentAt(true, pos, rot);
}
public void osForceDropAttachmentAt(LSL_Vector pos, LSL_Rotation rot)
{
CheckThreatLevel(ThreatLevel.High, "osForceDropAttachmentAt");
DropAttachmentAt(false, pos, rot);
}
public LSL_Integer osListenRegex(int channelID, string name, string ID, string msg, int regexBitfield)
{
CheckThreatLevel(ThreatLevel.Low, "osListenRegex");
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
if(wComm is null)
return -1;
if(!UUID.TryParse(ID, out UUID keyID))
return -1;
// if we want the name to be used as a regular expression, ensure it is valid first.
if ((regexBitfield & ScriptBaseClass.OS_LISTEN_REGEX_NAME) == ScriptBaseClass.OS_LISTEN_REGEX_NAME)
{
try
{
Regex.IsMatch("", name);
}
catch
{
OSSLShoutError("Name regex is invalid.");
return -1;
}
}
// if we want the msg to be used as a regular expression, ensure it is valid first.
if ((regexBitfield & ScriptBaseClass.OS_LISTEN_REGEX_MESSAGE) == ScriptBaseClass.OS_LISTEN_REGEX_MESSAGE)
{
try
{
Regex.IsMatch("", msg);
}
catch
{
OSSLShoutError("Message regex is invalid.");
return -1;
}
}
return wComm.Listen(m_item.ItemID, m_host.UUID,
channelID, name, keyID, msg, regexBitfield);
}
public LSL_Integer osRegexIsMatch(string input, string pattern)
{
CheckThreatLevel(ThreatLevel.Low, "osRegexIsMatch");
try
{
return Regex.IsMatch(input, pattern) ? 1 : 0;
}
catch (Exception)
{
OSSLShoutError("Possible invalid regular expression detected.");
return 0;
}
}
public LSL_Key osRequestURL(LSL_List options)
{
CheckThreatLevel(ThreatLevel.Moderate, "osRequestURL");
Hashtable opts = new();
for (int i = 0 ; i < options.Length ; i++)
{
object opt = options.Data[i];
if (opt.ToString() == "allowXss")
opts["allowXss"] = true;
}
if (m_UrlModule is not null)
return m_UrlModule.RequestURL(m_ScriptEngine.ScriptModule, m_host, m_item.ItemID, opts).ToString();
return LSL_Key.NullKey;
}
public LSL_Key osRequestSecureURL(LSL_List options)
{
CheckThreatLevel(ThreatLevel.Moderate, "osRequestSecureURL");
Hashtable opts = new();
for (int i = 0 ; i < options.Length ; i++)
{
object opt = options.Data[i];
if (opt.ToString() == "allowXss")
opts["allowXss"] = true;
}
if (m_UrlModule != null)
return m_UrlModule.RequestSecureURL(m_ScriptEngine.ScriptModule, m_host, m_item.ItemID, opts).ToString();
return LSL_Key.NullKey;
}
public void osCollisionSound(string impact_sound, double impact_volume)
{
CheckThreatLevel();
if(impact_sound.Length == 0)
{
m_host.CollisionSoundVolume = (float)impact_volume;
m_host.CollisionSound = m_host.invalidCollisionSoundUUID;
if(impact_volume == 0.0)
m_host.CollisionSoundType = -1; // disable all sounds
else if(impact_volume == 1.0f)
m_host.CollisionSoundType = 0; // full return to default sounds
else
m_host.CollisionSoundType = 2; // default sounds with volume
m_host.aggregateScriptEvents();
return;
}
// TODO: Parameter check logic required.
UUID soundId = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, impact_sound, AssetType.Sound);
if(soundId.IsZero())
m_host.CollisionSoundType = -1;
else
{
m_host.CollisionSound = soundId;
m_host.CollisionSoundVolume = (float)impact_volume;
m_host.CollisionSoundType = 1;
}
m_host.aggregateScriptEvents();
}
// still not very usefull, detector is lost on rez, restarts, etc
public void osVolumeDetect(int detect)
{
CheckThreatLevel();
if (m_host.ParentGroup is null || m_host.ParentGroup.IsDeleted || m_host.ParentGroup.IsAttachment)
return;
m_host.ScriptSetVolumeDetect(detect != 0);
}
/// <summary>
/// Get inertial data
/// </summary>
/// <remarks>
/// </remarks>
/// <returns>
/// a LSL list with contents:
/// LSL_Float mass, the total mass of a linkset
/// LSL_Vector CenterOfMass, center mass relative to root prim
/// LSL_Vector Inertia, elements of diagonal of inertia Ixx,Iyy,Izz divided by total mass
/// LSL_Vector aux, elements of upper triagle of inertia Ixy (= Iyx), Ixz (= Izx), Iyz(= Izy) divided by total mass
/// </returns>
public LSL_List osGetInertiaData()
{
CheckThreatLevel();
LSL_List result = new();
SceneObjectGroup sog = m_host.ParentGroup;
if (sog is null || sog.IsDeleted)
return result;
sog.GetInertiaData(out float TotalMass, out Vector3 CenterOfMass, out Vector3 Inertia, out Vector4 aux );
if(TotalMass > 0)
{
float t = 1.0f/TotalMass;
Inertia.X *= t;
Inertia.Y *= t;
Inertia.Z *= t;
aux.X *= t;
aux.Y *= t;
aux.Z *= t;
}
result.Add(new LSL_Float(TotalMass));
result.Add(new LSL_Vector(CenterOfMass.X, CenterOfMass.Y, CenterOfMass.Z));
result.Add(new LSL_Vector(Inertia.X, Inertia.Y, Inertia.Z));
result.Add(new LSL_Vector(aux.X, aux.Y, aux.Z));
return result;
}
/// <summary>
/// set inertial data
/// replaces the automatic calculation of mass, center of mass and inertia
///
/// </summary>
/// <param name="Mass">total mass of linkset</param>
/// <param name="centerOfMass">location of center of mass relative to root prim in local coords</param>
/// <param name="principalInertiaScaled">moment of inertia relative to principal axis and center of mass,Ixx, Iyy, Izz divided by mass</param>
/// <param name="lslrot">rotation of the inertia, relative to local axis</param>
/// <remarks>
/// the inertia argument is is inertia divided by mass, so corresponds only to the geometric distribution of mass and both can be changed independently.
/// </remarks>
public void osSetInertia(LSL_Float mass, LSL_Vector centerOfMass, LSL_Vector principalInertiaScaled, LSL_Rotation lslrot)
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return;
if(mass < 0 || principalInertiaScaled.x < 0 || principalInertiaScaled.y < 0 || principalInertiaScaled.z < 0)
return;
// need more checks
Vector3 CenterOfMass = new((float)centerOfMass.x, (float)centerOfMass.y, (float)centerOfMass.z);
float m = (float)mass;
Vector3 Inertia = new(
m * (float)principalInertiaScaled.x,
m * (float)principalInertiaScaled.y,
m * (float)principalInertiaScaled.z);
Vector4 rot = new((float)lslrot.x, (float)lslrot.y, (float)lslrot.y, (float)lslrot.s);
rot.Normalize();
sog.SetInertiaData(m, CenterOfMass, Inertia, rot );
}
/// <summary>
/// set inertial data as a sphere
/// replaces the automatic calculation of mass, center of mass and inertia
///
/// </summary>
/// <param name="Mass">total mass of linkset</param>
/// <param name="boxsize">size of the Box</param>
/// <param name="centerOfMass">location of center of mass relative to root prim in local coords</param>
/// <param name="lslrot">rotation of the box, and so inertia, relative to local axis</param>
/// <remarks>
/// </remarks>
public void osSetInertiaAsBox(LSL_Float mass, LSL_Vector boxSize, LSL_Vector centerOfMass, LSL_Rotation lslrot)
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return;
if(mass < 0)
return;
// need more checks
Vector3 CenterOfMass = new((float)centerOfMass.x, (float)centerOfMass.y, (float)centerOfMass.z);
float lx = (float)boxSize.x;
float ly = (float)boxSize.y;
float lz = (float)boxSize.z;
float m = (float)mass;
float t = m / 12.0f;
Vector3 Inertia = new( t * (ly*ly + lz*lz),
t * (lx*lx + lz*lz),
t * (lx*lx + ly*ly));
Vector4 rot = new((float)lslrot.x, (float)lslrot.y, (float)lslrot.z, (float)lslrot.s);
rot.Normalize();
sog.SetInertiaData(m, CenterOfMass, Inertia, rot );
}
/// <summary>
/// set inertial data as a sphere
/// replaces the automatic calculation of mass, center of mass and inertia
///
/// </summary>
/// <param name="Mass">total mass of linkset</param>
/// <param name="radius">radius of the sphere</param>
/// <param name="centerOfMass">location of center of mass relative to root prim in local coords</param>
/// <remarks>
/// </remarks>
public void osSetInertiaAsSphere(LSL_Float mass, LSL_Float radius, LSL_Vector centerOfMass)
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return;
if(mass < 0)
return;
// need more checks
Vector3 CenterOfMass = new((float)centerOfMass.x, (float)centerOfMass.y, (float)centerOfMass.z);
float r = (float)radius;
float m = (float)mass;
float t = 0.4f * m * r * r;
Vector3 Inertia = new(t, t, t);
sog.SetInertiaData(m, CenterOfMass, Inertia, new Vector4(0f, 0f, 0f,1.0f));
}
/// <summary>
/// set inertial data as a cylinder
/// replaces the automatic calculation of mass, center of mass and inertia
///
/// </summary>
/// <param name="Mass">total mass of linkset</param>
/// <param name="radius">radius of the cylinder</param>
/// <param name="length">length of the cylinder</param>
/// <param name="centerOfMass">location of center of mass relative to root prim in local coords</param>
/// <param name="lslrot">rotation of the cylinder, and so inertia, relative to local axis</param>
/// <remarks>
/// cylinder axis aligned with Z axis. For other orientations provide the rotation.
/// </remarks>
public void osSetInertiaAsCylinder(LSL_Float mass, LSL_Float radius, LSL_Float length, LSL_Vector centerOfMass, LSL_Rotation lslrot)
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return;
if(mass < 0)
return;
// need more checks
Vector3 CenterOfMass = new((float)centerOfMass.x, (float)centerOfMass.y, (float)centerOfMass.z);
float m = (float)mass;
float r = (float)radius;
r *= r;
float t = (float)length;
t *= t;
t += 3.0f * r;
t *= 8.333333e-2f * m;
Vector3 Inertia = new(t, t, 0.5f * m * r);
Vector4 rot = new((float)lslrot.x, (float)lslrot.y, (float)lslrot.z, (float)lslrot.s);
rot.Normalize();
sog.SetInertiaData(m, CenterOfMass, Inertia, rot);
}
/// <summary>
/// removes inertial data manual override
/// default automatic calculation is used again
///
/// </summary>
public void osClearInertia()
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return;
sog.SetInertiaData(-1, Vector3.Zero, Vector3.Zero, Vector4.Zero );
}
private bool checkAllowObjectTPbyLandOwner(Vector3 pos)
{
ILandObject land = World.LandChannel.GetLandObject(pos);
if(land is null)
return true;
LandData landdata = land.LandData;
if(landdata is null)
return true;
UUID hostOwner = m_host.OwnerID;
if(landdata.OwnerID.Equals(hostOwner))
return true;
EstateSettings es = World.RegionInfo.EstateSettings;
if(es is not null && es.IsEstateManagerOrOwner(hostOwner))
return true;
if(!landdata.IsGroupOwned)
return false;
UUID landGroup = landdata.GroupID;
if(landGroup.IsZero())
return false;
if(landGroup.Equals(m_host.GroupID))
return true;
return false;
}
/// <summary>
/// teleports a object (full linkset)
/// </summary>
/// <param name="objectUUID">the id of the linkset to teleport</param>
/// <param name="targetPos">target position</param>
/// <param name="rotation"> a rotation to apply</param>
/// <param name="flags">several flags/param>
/// <remarks>
/// only does teleport local to region
/// if object has scripts, owner must have rights to run scripts on target location
/// object owner must have rights to enter ojects on target location
/// target location parcel must have enought free prims capacity for the linkset prims
/// all avatars siting on the object must have access to target location
/// has a cool down time. retries before expire reset it
/// fail conditions are silent ignored
/// </remarks>
public LSL_Integer osTeleportObject(LSL_Key objectUUID, LSL_Vector targetPos, LSL_Rotation rotation, LSL_Integer flags)
{
CheckThreatLevel(ThreatLevel.Severe, "osTeleportObject");
if (!UUID.TryParse(objectUUID, out UUID objUUID))
{
OSSLShoutError("osTeleportObject() invalid object Key");
return -1;
}
SceneObjectGroup sog = World.GetSceneObjectGroup(objUUID);
if(sog is null || sog.IsDeleted || sog.inTransit)
return -1;
if(sog.OwnerID.NotEqual(m_host.OwnerID))
{
Vector3 pos = sog.AbsolutePosition;
if(!checkAllowObjectTPbyLandOwner(pos))
return -1;
}
return sog.TeleportObject(m_host.ParentGroup.UUID, targetPos, rotation, flags);
// a delay here may break vehicles
}
public LSL_Integer osGetLinkNumber(LSL_String name)
{
CheckThreatLevel();
SceneObjectGroup sog = m_host.ParentGroup;
if(sog is null || sog.IsDeleted)
return -1;
return sog.GetLinkNumber(name);
}
// rounds to the nearest number with provided number of decimal places
public LSL_Float osRound(LSL_Float value, LSL_Integer ndigits)
{
if(ndigits <= 0)
return Math.Round(value, MidpointRounding.AwayFromZero);
if(ndigits > 15)
ndigits = 15;
return Math.Round(value, ndigits, MidpointRounding.AwayFromZero);
}
public LSL_Float osVecMagSquare(LSL_Vector a)
{
return LSL_Vector.MagSquare(a);
}
public LSL_Float osVecDistSquare(LSL_Vector a, LSL_Vector b)
{
return LSL_Vector.MagSquare(a - b);
}
// returns the angle between 2 vectors 0 to pi
public LSL_Float osAngleBetween(LSL_Vector a, LSL_Vector b)
{
double dot = LSL_Vector.Dot(a,b);
double mcross = LSL_Vector.Mag(LSL_Vector.Cross(a,b));
return Math.Atan2(mcross, dot);
}
public void osAdjustSoundVolume(LSL_Integer linknum, LSL_Float volume)
{
SceneObjectPart sop = GetSingleLinkPart(linknum);
sop?.AdjustSoundGain(volume);
}
public void osSetSoundRadius(LSL_Integer linknum, LSL_Float radius)
{
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is not null)
sop.SoundRadius = radius;
}
public void osPlaySound(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsZero())
return;
// send the sound, once, to all clients in range
m_SoundModule.SendSound(sop, soundID, volume, false, 0, false, false);
}
public void osLoopSound(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsZero())
return;
m_SoundModule.LoopSound(sop, soundID, volume, false, false);
}
public void osLoopSoundMaster(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsZero())
return;
m_SoundModule.LoopSound(sop, soundID, volume, true, false);
}
public void osLoopSoundSlave(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsNotZero())
m_SoundModule.LoopSound(sop, soundID, volume, false, true);
}
public void osPlaySoundSlave(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsNotZero())
m_SoundModule.SendSound(sop, soundID, volume, false, 0, true, false);
}
public void osTriggerSound(LSL_Integer linknum, LSL_String sound, LSL_Float volume)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsNotZero())
m_SoundModule.SendSound(sop, soundID, volume, true, 0, false, false);
}
public void osTriggerSoundLimited(LSL_Integer linknum, LSL_String sound, LSL_Float volume,
LSL_Vector top_north_east, LSL_Vector bottom_south_west)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsNotZero())
m_SoundModule.TriggerSoundLimited(sop.UUID, soundID, volume,
bottom_south_west, top_north_east);
}
public void osStopSound(LSL_Integer linknum)
{
if (m_SoundModule is null)
return;
InitLSL();
List<SceneObjectPart> sops = m_LSL_Api.GetLinkParts(linknum);
if(sops is null || sops.Count == 0)
return;
for(int i = 0; i < sops.Count; ++i)
m_SoundModule.StopSound(sops[i]);
}
public void osPreloadSound(LSL_Integer linknum, LSL_String sound)
{
if (m_SoundModule is null)
return;
SceneObjectPart sop = GetSingleLinkPart(linknum);
if(sop is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(sop, m_host, sound, AssetType.Sound);
if (soundID.IsZero())
return;
m_SoundModule.PreloadSound(sop, soundID);
ScriptSleep(1000);
}
// get only one part
private SceneObjectPart GetSingleLinkPart(int linkType)
{
if (m_host.ParentGroup is null || m_host.ParentGroup.IsDeleted)
return null;
switch (linkType)
{
case ScriptBaseClass.LINK_SET:
case ScriptBaseClass.LINK_ALL_OTHERS:
case ScriptBaseClass.LINK_ALL_CHILDREN:
return null;
case 0:
case ScriptBaseClass.LINK_ROOT:
return m_host.ParentGroup.RootPart;
case ScriptBaseClass.LINK_THIS:
return m_host;
default:
if(linkType < 0)
return null;
return m_host.ParentGroup.GetLinkNumPart(linkType);
}
}
// funtions to retrieve user country
// adaptation cm* counter parts from Avination Careminster extensions API
// included in Avination code contribution
// for now users country can only be set directly on DB
public LSL_String osDetectedCountry(LSL_Integer number)
{
CheckThreatLevel(ThreatLevel.Moderate, "osDetectedCountry");
if (World.UserAccountService is null)
return LSL_String.Empty;
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
if (detectedParams is null)
return LSL_String.Empty;
UUID key = detectedParams.Key;
if (key.IsZero())
return LSL_String.Empty;
UserAccount account = World.UserAccountService.GetUserAccount(World.RegionInfo.ScopeID, key);
return account.UserCountry;
}
public LSL_String osGetAgentCountry(LSL_Key id)
{
CheckThreatLevel(ThreatLevel.Moderate, "osGetAgentCountry");
if (World.UserAccountService == null)
return LSL_String.Empty;
if (!UUID.TryParse(id, out UUID key))
return LSL_String.Empty;
if (key.IsZero())
return LSL_String.Empty;
//if owner is not god, target must be in region, or nearby regions
if (!World.Permissions.IsGod(m_host.OwnerID))
{
ScenePresence sp = World.GetScenePresence(key);
if(sp is null)
return LSL_String.Empty;
}
UserAccount account = World.UserAccountService.GetUserAccount(World.RegionInfo.ScopeID, key);
return account.UserCountry;
}
public LSL_String osStringSubString(LSL_String src, LSL_Integer offset)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return LSL_String.Empty;
if (offset >= src.Length)
return LSL_String.Empty;
if (offset <= 0)
return src;
return src.m_string[(offset.value)..];
}
public LSL_String osStringSubString(LSL_String src, LSL_Integer offset, LSL_Integer length)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return LSL_String.Empty;
if (length <= 0 || offset >= src.Length)
return LSL_String.Empty;
if (offset <= 0)
{
if(length == src.Length)
return src;
offset = 0;
}
if (length > src.Length - offset)
length = src.Length - offset;
return src.m_string.Substring(offset, length);
}
public LSL_Integer osStringStartsWith(LSL_String src, LSL_String value, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return 0;
if (string.IsNullOrEmpty(value))
return 0;
return src.m_string.StartsWith(value, ignorecase != 0, Culture.GetDefaultCurrentCulture()) ? 1 : 0;
}
public LSL_Integer osStringEndsWith(LSL_String src, LSL_String value, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return 0;
if (string.IsNullOrEmpty(value))
return 0;
return src.m_string.EndsWith(value, ignorecase != 0, Culture.GetDefaultCurrentCulture()) ? 1 : 0;
}
public LSL_Integer osStringIndexOf(LSL_String src, LSL_String value, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return -1;
if (string.IsNullOrEmpty(value))
return -1;
if (ignorecase == 0)
return src.m_string.IndexOf(value, StringComparison.Ordinal);
return src.m_string.IndexOf(value, StringComparison.OrdinalIgnoreCase);
}
public LSL_Integer osStringIndexOf(LSL_String src, LSL_String value,
LSL_Integer offset, LSL_Integer count, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return -1;
if (string.IsNullOrEmpty(value))
return -1;
if (offset >= src.Length)
return -1;
else if (offset < 0)
offset = 0;
if (count <= 0)
count = src.Length - offset;
else if (count > src.Length - offset)
count = src.Length - offset;
if (ignorecase == 0)
return src.m_string.IndexOf(value, offset, count, StringComparison.Ordinal);
return src.m_string.IndexOf(value, offset, count, StringComparison.OrdinalIgnoreCase);
}
public LSL_Integer osStringLastIndexOf(LSL_String src, LSL_String value, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return -1;
if (string.IsNullOrEmpty(value))
return -1;
if (ignorecase == 0)
return src.m_string.LastIndexOf(value, StringComparison.Ordinal);
return src.m_string.LastIndexOf(value, StringComparison.OrdinalIgnoreCase);
}
public LSL_Integer osStringLastIndexOf(LSL_String src, LSL_String value,
LSL_Integer offset, LSL_Integer count, LSL_Integer ignorecase)
{
CheckThreatLevel();
if (string.IsNullOrEmpty(src))
return -1;
if (string.IsNullOrEmpty(value))
return -1;
if (offset >= src.Length)
return -1;
if (offset < 0)
offset = 0;
if (count <= 0)
count = src.Length - offset;
else if (count > src.Length - offset)
count = src.Length - offset;
if (ignorecase == 0)
return src.m_string.LastIndexOf(value, offset, count, StringComparison.Ordinal);
return src.m_string.LastIndexOf(value, offset, count, StringComparison.OrdinalIgnoreCase);
}
public LSL_String osStringRemove(LSL_String src, LSL_Integer offset, LSL_Integer count)
{
if (string.IsNullOrEmpty(src))
return LSL_String.Empty;
if (offset >= src.Length)
return LSL_String.Empty;
if (offset < 0)
offset = 0;
if (count <= 0)
count = src.Length - offset;
else if (count > src.Length - offset)
count = src.Length - offset;
if (count >= src.Length)
return LSL_String.Empty;
return src.m_string.Remove(offset, count);
}
public LSL_String osStringReplace(LSL_String src, LSL_String oldvalue, LSL_String newvalue)
{
if (string.IsNullOrEmpty(src))
return LSL_String.Empty;
if (string.IsNullOrEmpty(oldvalue))
return LSL_String.Empty;
if (string.IsNullOrEmpty(newvalue))
newvalue = null;
return src.m_string.Replace(oldvalue, newvalue);
}
public LSL_Integer osApproxEquals(LSL_Float a, LSL_Float b)
{
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
return 1;
}
public LSL_Integer osApproxEquals(LSL_Float a, LSL_Float b, LSL_Float margin)
{
double e = Math.Abs(margin);
if (a > b + e || a < b - e)
return 0;
return 1;
}
public LSL_Integer osApproxEquals(LSL_Vector va, LSL_Vector vb)
{
double a = va.x;
double b = vb.x;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
a = va.y;
b = vb.y;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
a = va.z;
b = vb.z;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
return 1;
}
public LSL_Integer osApproxEquals(LSL_Vector va, LSL_Vector vb, LSL_Float margin)
{
double e = Math.Abs(margin);
double a = va.x;
double b = vb.x;
if (a > b + e || a < b - e)
return 0;
a = va.y;
b = vb.y;
if (a > b + e || a < b - e)
return 0;
a = va.z;
b = vb.z;
if (a > b + e || a < b - e)
return 0;
return 1;
}
public LSL_Integer osApproxEquals(LSL_Rotation ra, LSL_Rotation rb)
{
double a = ra.x;
double b = rb.x;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
a = ra.y;
b = rb.y;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
a = ra.z;
b = rb.z;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
a = ra.s;
b = rb.s;
if (a > b + 1.0e-6 || a < b - 1.0e-6)
return 0;
return 1;
}
public LSL_Integer osApproxEquals(LSL_Rotation ra, LSL_Rotation rb, LSL_Float margin)
{
double e = Math.Abs(margin);
double a = ra.x;
double b = rb.x;
if (a > b + e || a < b - e)
return 0;
a = ra.y;
b = rb.y;
if (a > b + e || a < b - e)
return 0;
a = ra.z;
b = rb.z;
if (a > b + e || a < b - e)
return 0;
a = ra.s;
b = rb.s;
if (a > b + e || a < b - e)
return 0;
return 1;
}
public LSL_Key osGetInventoryLastOwner(LSL_String itemNameorid)
{
TaskInventoryItem item = UUID.TryParse(itemNameorid, out UUID itemID) ?
m_host.Inventory.GetInventoryItem(itemID) :
m_host.Inventory.GetInventoryItem(itemNameorid);
if (item is null)
return LSL_String.NullKey;
UUID id = item.LastOwnerID.IsNotZero() ? item.LastOwnerID : item.OwnerID;
return new LSL_Key(id.ToString());
}
public LSL_Key osGetInventoryItemKey(LSL_String name)
{
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(name);
if (item is null)
return LSL_String.NullKey;
if ((item.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
{
return new LSL_Key(item.ItemID.ToString());
}
return LSL_String.NullKey;
}
public LSL_String osGetInventoryName(LSL_Key itemId)
{
TaskInventoryItem item = null;
if (UUID.TryParse(itemId, out UUID itemID))
item = m_host.Inventory.GetInventoryItem(itemID);
return (item is null) ? LSL_String.Empty : item.Name;
}
public LSL_String osGetLinkInventoryName(LSL_Integer linkNumber, LSL_Key itemId)
{
TaskInventoryItem item = null;
if (UUID.TryParse(itemId, out UUID itemID))
{
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return LSL_String.Empty;
item = part.Inventory.GetInventoryItem(itemID);
}
return (item is null) ? LSL_String.Empty : item.Name;
}
public LSL_String osGetInventoryDesc(LSL_String itemNameorid)
{
TaskInventoryItem item = UUID.TryParse(itemNameorid, out UUID itemID) ?
m_host.Inventory.GetInventoryItem(itemID) :
m_host.Inventory.GetInventoryItem(itemNameorid);
return (item == null) ? LSL_String.Empty : item.Description;
}
public LSL_String osGetLinkInventoryDesc(LSL_Integer linkNumber, LSL_String itemNameorid)
{
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return LSL_String.Empty;
TaskInventoryItem item = UUID.TryParse(itemNameorid, out UUID itemID) ?
part.Inventory.GetInventoryItem(itemID) :
part.Inventory.GetInventoryItem(itemNameorid);
return (item == null) ? LSL_String.Empty : item.Description;
}
public LSL_Key osGetLinkInventoryKey(LSL_Integer linkNumber, LSL_String name, LSL_Integer type)
{
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return LSL_String.NullKey;
TaskInventoryItem item = part.Inventory.GetInventoryItem(name, type);
if (item is null)
return LSL_String.NullKey;
if ((item.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
{
return new LSL_Key(item.AssetID.ToString());
}
return LSL_String.NullKey;
}
public LSL_Key osGetLinkInventoryItemKey(LSL_Integer linkNumber, LSL_String name)
{
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return LSL_String.NullKey;
TaskInventoryItem item = part.Inventory.GetInventoryItem(name);
if (item is null)
return LSL_String.NullKey;
if ((item.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
{
return new LSL_Key(item.ItemID.ToString());
}
return LSL_String.NullKey;
}
public LSL_List osGetInventoryItemKeys(LSL_Integer type)
{
LSL_List ret = new();
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in m_host.TaskInventory)
{
if ((inv.Value.Type == type || type == -1) &&
(inv.Value.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
ret.Add(inv.Value.ItemID.ToString());
}
m_host.TaskInventory.LockItemsForRead(false);
return ret;
}
public LSL_List osGetLinkInventoryItemKeys(LSL_Integer linkNumber, LSL_Integer type)
{
LSL_List ret = new();
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return ret;
part.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in part.TaskInventory)
{
if (inv.Value.Type == type || type == -1 &&
(inv.Value.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
ret.Add(inv.Value.ItemID.ToString());
}
part.TaskInventory.LockItemsForRead(false);
return ret;
}
public LSL_List osGetInventoryNames(LSL_Integer type)
{
LSL_List ret = new();
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in m_host.TaskInventory)
{
if (inv.Value.Type == type || type == -1)
ret.Add(inv.Value.Name);
}
m_host.TaskInventory.LockItemsForRead(false);
return ret;
}
public LSL_List osGetLinkInventoryNames(LSL_Integer linkNumber, LSL_Integer type)
{
LSL_List ret = new();
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return ret;
part.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair<UUID, TaskInventoryItem> inv in part.TaskInventory)
{
if (inv.Value.Type == type || type == -1)
ret.Add(inv.Value.Name);
}
part.TaskInventory.LockItemsForRead(false);
return ret;
}
public void osRemoveLinkInventory(LSL_Integer linkNumber, LSL_String name)
{
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return;
TaskInventoryItem item = part.Inventory.GetInventoryItem(name);
if (item == null)
return;
if (item.ItemID == m_item.ItemID)
throw new ScriptDeleteException();
else
part.Inventory.RemoveInventoryItem(item.ItemID);
}
///<summary>
/// Give a specified item from a child prim inventory
/// to a destination (object or avatar).
///</summary>
///<param name="linkNumber">The link number of the child prim.</param>
///<param name="destination">The UUID of the destination avatar or object.</param>
///<param name="inventory">The name of the item to give.</param>
public void osGiveLinkInventory(LSL_Integer linkNumber, LSL_Key destination, LSL_String inventory)
{
if (!UUID.TryParse(destination, out UUID destId) || destId.IsZero())
return;
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return;
TaskInventoryItem item = part.Inventory.GetInventoryItem(inventory);
if (item is null)
return;
if (World.TryGetSceneObjectPart(destId, out _))
{
// destination is an object
World.MoveTaskInventoryItem(destId, part, item.ItemID);
}
else
{
if(!World.TryGetScenePresence(destId, out ScenePresence _))
{
UserAccount account = World.UserAccountService.GetUserAccount(World.RegionInfo.ScopeID, destId);
if (account == null)
{
GridUserInfo info = World.GridUserService.GetGridUserInfo(destId.ToString());
if(info == null || info.Online == false)
return;
}
}
// destination is an avatar
InventoryItemBase agentItem = World.MoveTaskInventoryItem(destId, UUID.Zero, part, item.ItemID, out string message);
if (agentItem is null)
{
m_LSL_Api.llSay(0, message);
return;
}
byte[] bucket = new byte[1];
bucket[0] = (byte)item.Type;
GridInstantMessage msg = new GridInstantMessage(World,
m_host.OwnerID, m_host.Name, destId,
(byte)InstantMessageDialog.TaskInventoryOffered,
m_host.OwnerID.Equals(m_host.GroupID), "'"+item.Name+"'. ("+m_host.Name+" is located at "+
World.RegionInfo.RegionName + " "+ m_host.AbsolutePosition.ToString() + ")",
agentItem.ID, true, m_host.AbsolutePosition,
bucket, true);
if (World.TryGetScenePresence(destId, out ScenePresence sp))
sp.ControllingClient.SendInstantMessage(msg);
else
m_TransferModule?.SendInstantMessage(msg, delegate(bool success) {});
//This delay should only occur when giving inventory to avatars.
ScriptSleep(3000);
}
}
public void osGiveLinkInventoryList(LSL_Integer linkNumber, LSL_Key destination, LSL_String category, LSL_List inventory)
{
if (inventory.Length == 0)
return;
SceneObjectPart part = GetSingleLinkPart(linkNumber);
if(part == null)
return;
if (!UUID.TryParse(destination, out UUID destID) || destID.IsZero())
return;
bool isNotOwner = true;
if (!World.TryGetSceneObjectPart(destID, out SceneObjectPart destSop))
{
if (!World.TryGetScenePresence(destID, out ScenePresence sp))
{
// we could check if it is a grid user and allow the transfer as in older code
// but that increases security risk
OSSLShoutError("osGiveLinkInventoryList: Unable to give list, destination not found");
ScriptSleep(100);
return;
}
isNotOwner = sp.UUID.NotEqual(m_host.OwnerID);
}
List<UUID> itemList = new(inventory.Length);
foreach (object item in inventory.Data)
{
string rawItemString = item.ToString();
TaskInventoryItem taskItem = (UUID.TryParse(rawItemString, out UUID itemID)) ?
part.Inventory.GetInventoryItem(itemID) : part.Inventory.GetInventoryItem(rawItemString);
if(taskItem is null)
continue;
if ((taskItem.CurrentPermissions & (uint)PermissionMask.Copy) == 0)
continue;
if (destSop is not null)
{
if(!World.Permissions.CanDoObjectInvToObjectInv(taskItem, m_host, destSop))
continue;
}
else
{
if(isNotOwner)
{
if ((taskItem.CurrentPermissions & (uint)PermissionMask.Transfer) == 0)
continue;
}
}
itemList.Add(taskItem.ItemID);
}
if (itemList.Count == 0)
{
OSSLShoutError("osGiveLinkInventoryList: Unable to give list, no items found");
ScriptSleep(100);
return;
}
UUID folderID = m_ScriptEngine.World.MoveTaskInventoryItems(destID, category, part, itemList, false);
if (folderID.IsZero())
{
OSSLShoutError("osGiveLinkInventoryList: Unable to give list");
ScriptSleep(100);
return;
}
if (destSop is not null)
{
ScriptSleep(100);
return;
}
if (m_TransferModule != null)
{
byte[] bucket = new byte[] { (byte)AssetType.Folder };
Vector3 pos = m_host.AbsolutePosition;
GridInstantMessage msg = new(World, m_host.OwnerID, m_host.Name, destID,
(byte)InstantMessageDialog.TaskInventoryOffered,
m_host.OwnerID.Equals(m_host.GroupID), string.Format("'{0}'", category), folderID, false, pos,bucket, false);
m_TransferModule.SendInstantMessage(msg, delegate(bool success) {});
}
ScriptSleep(3000);
}
public LSL_Key osGetLastChangedEventKey()
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, 0);
if (detectedParams is null)
return string.Empty;
return detectedParams.Key.ToString();
}
// returns PST or PDT wall clock
public LSL_Float osGetPSTWallclock()
{
if(PSTTimeZone is null)
return DateTime.Now.TimeOfDay.TotalSeconds;
DateTime time = TimeZoneInfo.ConvertTime(DateTime.UtcNow, PSTTimeZone);
return time.TimeOfDay.TotalSeconds;
}
public LSL_Rotation osSlerp(LSL_Rotation a, LSL_Rotation b, LSL_Float amount)
{
if(amount < 0)
amount= 0;
else if(amount > 1.0)
amount = 1.0;
a.Normalize();
b.Normalize();
return LSL_Rotation.Slerp(a, b, amount);
}
public LSL_Vector osSlerp(LSL_Vector a, LSL_Vector b, LSL_Float amount)
{
if (amount < 0)
amount = 0;
else if (amount > 1.0)
amount = 1.0;
return LSL_Vector.Slerp(a, b, amount);
}
public void osResetAllScripts(LSL_Integer linkset)
{
UUID me = m_item.ItemID;
List<TaskInventoryItem> scripts = new();
if(linkset != 0)
{
SceneObjectGroup sog = m_host.ParentGroup;
if(sog.inTransit || sog.IsDeleted)
return;
SceneObjectPart[] parts = sog.Parts;
for (int i = 0; i < parts.Length; i++)
{
scripts.AddRange(parts[i].Inventory.GetInventoryItems(InventoryType.LSL));
}
}
else
scripts.AddRange(m_host.Inventory.GetInventoryItems(InventoryType.LSL));
foreach(TaskInventoryItem script in scripts)
{
if(script.ItemID.Equals(me))
continue;
m_ScriptEngine.ResetScript(script.ItemID);
}
m_UrlModule?.ScriptRemoved(me);
m_ScriptEngine.ApiResetScript(me);
}
public LSL_Integer osIsNotValidNumber(LSL_Float v)
{
double d = v.value;
if (double.IsNaN(d))
return 1;
if (double.IsNegativeInfinity(d))
return 2;
if (double.IsPositiveInfinity(d))
return 3;
return 0;
}
public LSL_Vector osGetSitTargetPos()
{
return m_host.SitTargetPosition;
}
public LSL_Rotation osGetSitTargetRot()
{
return m_host.SitTargetOrientation;
}
public void osSetSitActiveRange(LSL_Float v)
{
float fv = (float)v.value;
if (fv > 128f)
fv = 128f;
if(m_host.SitActiveRange != fv)
{
m_host.SitActiveRange = fv;
m_host.ParentGroup.HasGroupChanged = true;
}
}
public void osSetLinkSitActiveRange(LSL_Integer linkNumber, LSL_Float v)
{
InitLSL();
float fv = (float)v.value;
if (fv > 128f)
fv = 128f;
bool changed = false;
List<SceneObjectPart> parts = m_LSL_Api.GetLinkParts(linkNumber);
for(int i = 0; i < parts.Count; ++i)
{
SceneObjectPart sop = parts[i];
if(sop.SitActiveRange != fv)
{
sop.SitActiveRange = fv;
changed = true;
}
}
if (changed)
m_host.ParentGroup.HasGroupChanged = true;
}
public LSL_Float osGetSitActiveRange()
{
return m_host.SitActiveRange;
}
public LSL_Float osGetLinkSitActiveRange(LSL_Integer linkNumber)
{
if (linkNumber == ScriptBaseClass.LINK_THIS)
return m_host.SitActiveRange;
if (linkNumber < 0)
return int.MinValue;
if (linkNumber < 2)
return m_host.ParentGroup.RootPart.SitActiveRange;
SceneObjectPart target = m_host.ParentGroup.GetLinkNumPart(linkNumber);
if (target is null)
return int.MinValue;
return target.SitActiveRange;
}
public void osSetStandTarget(LSL_Vector v)
{
// todo add limits ?
Vector3 old = m_host.StandOffset;
m_host.StandOffset = v;
if(!old.ApproxEquals(v))
m_host.ParentGroup.HasGroupChanged = true;
}
public void osSetLinkStandTarget(LSL_Integer linkNumber, LSL_Vector v)
{
// todo add limits ?
SceneObjectPart target;
if (linkNumber == ScriptBaseClass.LINK_THIS)
target = m_host;
else if (linkNumber < 0)
return;
else if (linkNumber < 2)
target = m_host.ParentGroup.RootPart;
else
target = m_host.ParentGroup.GetLinkNumPart(linkNumber);
if (target is null)
return;
Vector3 newv = v;
if (!newv.ApproxEquals(target.StandOffset))
{
target.StandOffset = v;
m_host.ParentGroup.HasGroupChanged = true;
}
}
public LSL_Vector osGetStandTarget()
{
return m_host.StandOffset;
}
public LSL_Vector osGetLinkStandTarget(LSL_Integer linkNumber)
{
if (linkNumber == ScriptBaseClass.LINK_THIS)
return m_host.StandOffset;
if (linkNumber < 0)
return Vector3.Zero;
if (linkNumber < 2)
return m_host.ParentGroup.RootPart.StandOffset;
SceneObjectPart target = m_host.ParentGroup.GetLinkNumPart(linkNumber);
if (target is null)
return Vector3.Zero;
return target.StandOffset;
}
public LSL_Integer osClearObjectAnimations()
{
return m_host.ClearObjectAnimations();
}
public LSL_Integer osReplaceAgentEnvironment(LSL_Key agentkey, LSL_Integer transition, LSL_String daycycle)
{
if(!string.IsNullOrEmpty(CheckThreatLevelTest(ThreatLevel.Moderate, "osReplaceAgentEnvironment")))
return -2;
if (!UUID.TryParse(agentkey, out UUID agentid))
return -4;
ScenePresence sp = World.GetScenePresence(agentid);
if(sp is null || sp.IsChildAgent || sp.IsNPC || sp.IsInTransit)
return -4;
if(string.IsNullOrEmpty(daycycle) || daycycle == ScriptBaseClass.NULL_KEY)
{
sp.Environment = null;
m_envModule.WindlightRefreshForced(sp, transition);
return 1;
}
UUID envID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, daycycle);
if (envID.IsZero())
return -3;
AssetBase asset = World.AssetService.Get(envID.ToString());
if(asset is null || asset.Type != (byte)AssetType.Settings)
return -3;
// cant use stupid broken asset flags for subtype
try
{
OSD oenv = OSDParser.Deserialize(asset.Data);
ViewerEnvironment VEnv = m_envModule.GetRegionEnvironment().Clone();
if(!VEnv.CycleFromOSD(oenv))
return -3;
sp.Environment = VEnv;
m_envModule.WindlightRefreshForced(sp, transition);
}
catch
{
sp.Environment = null;
m_envModule.WindlightRefreshForced(sp, transition);
return -5;
}
return 1;
}
public LSL_Integer osReplaceParcelEnvironment(LSL_Integer transition, LSL_String daycycle)
{
if (!World.RegionInfo.EstateSettings.AllowEnvironmentOverride)
return -1;
ILandObject parcel = World.LandChannel.GetLandObject(m_host.GetWorldPosition().X, m_host.GetWorldPosition().Y);
if (parcel is null)
return -2;
if (!World.Permissions.CanEditParcelProperties(m_host.OwnerID, parcel, (GroupPowers.AllowEnvironment), true))
return -3;
ViewerEnvironment VEnv;
if (parcel.LandData.Environment is null)
VEnv = m_envModule.GetRegionEnvironment().Clone();
else
VEnv = parcel.LandData.Environment;
bool changed = false;
if (!string.IsNullOrEmpty(daycycle) && (daycycle != ScriptBaseClass.NULL_KEY))
{
UUID envID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, daycycle);
if (envID.IsZero())
return -4;
AssetBase asset = World.AssetService.Get(envID.ToString());
if (asset is null || asset.Type != (byte)AssetType.Settings)
return -4;
// cant use stupid broken asset flags for subtype
try
{
OSD oenv = OSDParser.Deserialize(asset.Data);
if (!VEnv.CycleFromOSD(oenv))
return -5;
changed = true;
}
catch
{
return -5;
}
}
if (changed)
{
parcel.StoreEnvironment(VEnv);
m_envModule.WindlightRefresh(transition, false);
}
return 1;
}
public LSL_Integer osReplaceRegionEnvironment(LSL_Integer transition, LSL_String daycycle,
LSL_Float daylen, LSL_Float dayoffset,
LSL_Float altitude1, LSL_Float altitude2, LSL_Float altitude3)
{
if (!World.Permissions.CanIssueEstateCommand(m_host.OwnerID, true))
return -3;
ViewerEnvironment VEnv = m_envModule.GetRegionEnvironment().Clone();
bool changed = false;
if (!string.IsNullOrEmpty(daycycle) && (daycycle != ScriptBaseClass.NULL_KEY))
{
UUID envID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, daycycle);
if (envID.IsZero())
return -4;
AssetBase asset = World.AssetService.Get(envID.ToString());
if (asset is null || asset.Type != (byte)AssetType.Settings)
return -4;
// cant use stupid broken asset flags for subtype
try
{
OSD oenv = OSDParser.Deserialize(asset.Data);
if (!VEnv.CycleFromOSD(oenv))
return -5;
changed = true;
}
catch
{
return -5;
}
}
if (daylen >= 4 && daylen <= 24 * 7)
{
int ll = VEnv.DayLength;
VEnv.DayLength = (int)(daylen * 3600f);
changed |= ll != VEnv.DayLength;
}
if (dayoffset >= -11.5 && dayoffset <= 11.5)
{
int lo = VEnv.DayLength;
if (dayoffset <= 0)
dayoffset+= 24;
VEnv.DayOffset = (int)(dayoffset * 3600f);
changed |= lo != VEnv.DayOffset;
}
bool needSort = false;
if (altitude1 > 0 && altitude1 < 4000 && VEnv.Altitudes[0] != (float)altitude1)
{
VEnv.Altitudes[0] = (float)altitude1;
needSort = true;
}
if (altitude2 > 0 && altitude2 < 4000 && VEnv.Altitudes[1] != (float)altitude2)
{
VEnv.Altitudes[1] = (float)altitude2;
needSort = true;
}
if (altitude3 > 0 && altitude2 < 4000 && VEnv.Altitudes[2] != (float)altitude3)
{
VEnv.Altitudes[2] = (float)altitude3;
needSort = true;
}
if(needSort)
{
VEnv.SortAltitudes();
changed = true;
}
if(changed)
{
m_envModule.StoreOnRegion(VEnv);
m_envModule.WindlightRefresh(transition);
}
return 1;
}
public LSL_Integer osResetEnvironment(LSL_Integer parcelOrRegion, LSL_Integer transition)
{
if (parcelOrRegion > 0)
{
if (!World.RegionInfo.EstateSettings.AllowEnvironmentOverride)
return -1;
ILandObject parcel = World.LandChannel.GetLandObject(m_host.GetWorldPosition().X, m_host.GetWorldPosition().Y);
if (parcel is null)
return -2;
if (!World.Permissions.CanEditParcelProperties(m_host.OwnerID, parcel, (GroupPowers.AllowEnvironment), true))
return -3;
if (parcel.LandData.Environment is null)
return 1;
parcel.StoreEnvironment(null);
m_envModule.WindlightRefresh(transition, false);
return 1;
}
if (!World.Permissions.CanIssueEstateCommand(m_host.OwnerID, true))
return -3;
m_envModule.StoreOnRegion(null);
m_envModule.WindlightRefresh(transition);
return 1;
}
public void osParticleSystem(LSL_List rules)
{
InitLSL();
m_LSL_Api.SetParticleSystem(m_host, rules, "osParticleSystem", true);
}
public void osLinkParticleSystem(LSL_Integer linknumber, LSL_List rules)
{
InitLSL();
List<SceneObjectPart> parts = m_LSL_Api.GetLinkParts(linknumber);
foreach (SceneObjectPart part in parts)
{
m_LSL_Api.SetParticleSystem(part, rules, "osLinkParticleSystem", true);
}
}
public LSL_Integer osNpcLookAt(LSL_Key npckey, LSL_Integer ltype, LSL_Key objkey, LSL_Vector offset)
{
if (World.GetNumberOfClients() == 0)
return 0;
if (ltype < 0 || ltype > ScriptBaseClass.NPCLOOKAT_CLEAR)
return -1;
if (!UUID.TryParse(npckey, out UUID npc))
return -2;
ScenePresence npcSP = World.GetScenePresence(npc);
if((npcSP is null) || !npcSP.IsNPC)
return -3;
if (!UUID.TryParse(objkey, out UUID obj))
return -4;
if(!obj.IsZero())
{
ScenePresence objSP = World.GetScenePresence(obj);
if(objSP == null)
{
SceneObjectPart objSOP = World.GetSceneObjectPart(obj);
if(objSOP == null)
return -5;
}
}
byte[] data = new byte[57];
npc.ToBytes(data, 0);
obj.ToBytes(data, 16);
Vector3d vd = new(offset.x, offset.y, offset.z);
vd.ToBytes(data, 32);
data[56] = (byte)(int)ltype;
OpenMetaverse.Packets.ViewerEffectPacket.EffectBlock effect = new()
{
AgentID = npc,
Color = new byte[4],
Duration = 0,
ID = UUID.Random(),
Type = 14,
TypeData = data
};
OpenMetaverse.Packets.ViewerEffectPacket.EffectBlock[] effectblock = new OpenMetaverse.Packets.ViewerEffectPacket.EffectBlock[1];
effectblock[0] = effect;
World.ForEachScenePresence(
sp =>
{
if(!sp.IsNPC && !sp.IsDeleted)
{
sp.ControllingClient.SendViewerEffect(effectblock);
}
});
return 0;
}
public LSL_Integer osAvatarType(LSL_Key avkey)
{
if (!UUID.TryParse(avkey.m_string, out UUID avId))
return -1;
ScenePresence av = World.GetScenePresence(avId);
if (av is null || av.IsDeleted || av.IsChildAgent)
return 0;
return av.IsNPC ? 2 : 1;
}
public LSL_Integer osAvatarType(LSL_String sFirstName, LSL_String sLastName)
{
ScenePresence av = World.GetScenePresence(sFirstName.m_string, sLastName.m_string);
if (av is null || av.IsDeleted || av.IsChildAgent)
return 0;
return av.IsNPC ? 2 : 1;
}
public void osListSortInPlace(LSL_List src, LSL_Integer stride, LSL_Integer ascending)
{
src.SortInPlace(stride, ascending == 1);
}
public void osListSortInPlaceStrided(LSL_List src, LSL_Integer stride, LSL_Integer stride_index, LSL_Integer ascending)
{
src.SortInPlace(stride, stride_index, ascending == 1);
}
public LSL_List osGetParcelDetails(LSL_Key id, LSL_List param)
{
if (!UUID.TryParse(id, out UUID parcelID))
return new LSL_List(0);
InitLSL();
if (m_LSL_Api is null)
return new LSL_List(0);
ILandObject parcel = World.LandChannel.GetLandObject(parcelID);
if (parcel is null)
return new LSL_List(0);
return m_LSL_Api.GetParcelDetails(parcel, param);
}
public LSL_List osGetParcelIDs()
{
//if(!World.Permissions.IsEstateManager(m_host.OwnerID))
// return new LSL_List();
List<ILandObject> parcels = World.LandChannel.AllParcels();
if (parcels is null || parcels.Count == 0)
return new LSL_List();
LSL_List ret = new();
foreach (ILandObject obj in parcels)
{
// some sanity check
if(obj.GlobalID.IsZero())
continue;
if(obj.LandData is null || obj.LandData.Area == 0)
continue;
ret.Add(new LSL_Key(obj.GlobalID.ToString()));
}
return ret;
}
public LSL_Key osGetParcelID()
{
ILandObject parcel = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
return parcel is null ? ScriptBaseClass.NULL_KEY : new LSL_Key(parcel.GlobalID.ToString());
}
// old List2ListStrided for compatibility
/// <summary>
/// Elements in the source list starting with 0 and then
/// every i+stride. If the stride is negative then the scan
/// Only those elements that are also in the specified
/// range are included in the result.
/// </summary>
public LSL_List osOldList2ListStrided(LSL_List src, int start, int end, int stride)
{
LSL_List result = new();
int[] si = new int[2];
int[] ei = new int[2];
bool twopass = false;
// First step is always to deal with negative indices
if (start < 0)
start += src.Length;
if (end < 0)
end += src.Length;
// Out of bounds indices are OK, just trim them
// accordingly
if (start > src.Length)
start = src.Length;
if (end > src.Length)
end = src.Length;
if (stride == 0)
stride = 1;
// There may be one or two ranges to be considered
if (start != end)
{
if (start <= end)
{
si[0] = start;
ei[0] = end;
}
else
{
si[1] = start;
ei[1] = src.Length;
si[0] = 0;
ei[0] = end;
twopass = true;
}
// The scan always starts from the beginning of the
// source list, but members are only selected if they
// fall within the specified sub-range. The specified
// range values are inclusive.
// A negative stride reverses the direction of the
// scan producing an inverted list as a result.
if (stride > 0)
{
for (int i = 0; i < src.Length; i += stride)
{
if (i<=ei[0] && i>=si[0])
result.Add(src.Data[i]);
if (twopass && i>=si[1] && i<=ei[1])
result.Add(src.Data[i]);
}
}
else if (stride < 0)
{
for (int i = src.Length - 1; i >= 0; i += stride)
{
if (i <= ei[0] && i >= si[0])
result.Add(src.Data[i]);
if (twopass && i >= si[1] && i <= ei[1])
result.Add(src.Data[i]);
}
}
}
else
{
if (start%stride == 0)
{
result.Add(src.Data[start]);
}
}
return result;
}
public LSL_Integer osGetPrimCount()
{
return m_host.ParentGroup.PrimCount;
}
public LSL_Integer osGetPrimCount(LSL_Key object_id)
{
if (!UUID.TryParse(object_id.m_string, out UUID id) || id.IsZero())
return 0;
return World.TryGetSceneObjectPart(id, out SceneObjectPart part) ? part.ParentGroup.PrimCount : 0;
}
public LSL_Integer osGetSittingAvatarsCount()
{
return m_host.ParentGroup.GetSittingAvatarsCount();
}
public LSL_Integer osGetSittingAvatarsCount(LSL_Key object_id)
{
if (!UUID.TryParse(object_id.m_string, out UUID id) || id.IsZero())
return 0;
return World.TryGetSceneObjectPart(id, out SceneObjectPart part) ? part.ParentGroup.GetSittingAvatarsCount() : 0;
}
public LSL_String osAESEncrypt(string secret, string plainText)
{
if(string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(plainText))
return LSL_String.Empty;
ReadOnlySpan<char> ret = Util.AESEncrypt(secret.AsSpan(), plainText.AsSpan());
if(ret.Length == 0)
{
OSSLShoutError("osAESEncrypt: Failed to encrypt!");
return LSL_String.Empty;
}
return ret.ToString();
}
public LSL_String osAESDecrypt(string secret, string encryptedText)
{
if(string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(encryptedText))
return LSL_String.Empty;
ReadOnlySpan<char> ret = Util.AESDecrypt(secret.AsSpan(), encryptedText.AsSpan());
if(ret.Length == 0)
{
OSSLShoutError("osAESDecrypt: Failed to Decrypt!");
return LSL_String.Empty;
}
return ret.ToString();
}
public LSL_String osAESEncryptTo(string secret, string plainText, string ivString)
{
if(string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(plainText) || string.IsNullOrEmpty(ivString))
return LSL_String.Empty;
ReadOnlySpan<char> ret = Util.AESEncryptTo(secret.AsSpan(), plainText.AsSpan(), ivString.AsSpan());
if (ret.Length == 0)
{
OSSLShoutError("osAESEncryptTo: Failed to encrypt!");
return LSL_String.Empty;
}
return ret.ToString();
}
public LSL_String osAESDecryptFrom(string secret, string encryptedText, string ivString)
{
if(string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(encryptedText) || string.IsNullOrEmpty(ivString))
return LSL_String.Empty;
ReadOnlySpan<char> ret = Util.AESDecryptFrom(secret.AsSpan(), encryptedText.AsSpan(), ivString.AsSpan());
if (ret.Length == 0)
{
OSSLShoutError("osAESDecryptFrom: Failed to decrypt!");
return LSL_String.Empty;
}
return ret.ToString();
}
public LSL_Vector osGetLinkColor(LSL_Integer link, LSL_Integer face)
{
SceneObjectPart linkedPart = link.value switch
{
ScriptBaseClass.LINK_ROOT => m_host.ParentGroup.RootPart,
ScriptBaseClass.LINK_THIS => m_host,
_ => m_host.ParentGroup.GetLinkNumPart(link.value)
};
if (linkedPart != null)
{
InitLSL();
return m_LSL_Api.GetColor(linkedPart, face.value);
}
return LSL_Vector.Zero;
}
}
}