/*
* 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.Assets;
using OpenMetaverse.Packets;
using OpenMetaverse.Rendering;
using OpenSim.Framework;
using OpenSim.Region.CoreModules.World.Land;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.Framework.Scenes.Animation;
using OpenSim.Region.Framework.Scenes.Scripting;
using OpenSim.Region.Framework.Scenes.Serialization;
using OpenSim.Region.PhysicsModules.SharedBase;
using OpenSim.Region.ScriptEngine.Interfaces;
using OpenSim.Region.ScriptEngine.Shared.Api.Interfaces;
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using OpenSim.Services.Interfaces;
using OpenSim.Services.Connectors.Hypergrid;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using AssetLandmark = OpenSim.Framework.AssetLandmark;
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 PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo;
using PrimType = OpenSim.Region.Framework.Scenes.PrimType;
using RegionFlags = OpenSim.Framework.RegionFlags;
using RegionInfo = OpenSim.Framework.RegionInfo;
using System.Runtime.CompilerServices;
#pragma warning disable IDE1006
namespace OpenSim.Region.ScriptEngine.Shared.Api
{
///
/// Contains all LSL ll-functions. This class will be in Default AppDomain.
///
public class LSL_Api : ILSL_Api, IScriptApi
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private int m_llRequestAgentDataCacheTimeout;
public int LlRequestAgentDataCacheTimeoutMs
{
get
{
return 1000 * m_llRequestAgentDataCacheTimeout;
}
set
{
m_llRequestAgentDataCacheTimeout = value / 1000;
}
}
protected IScriptEngine m_ScriptEngine;
public Scene World;
protected SceneObjectPart m_host;
protected UUID RegionScopeID = UUID.Zero;
protected string m_regionName = String.Empty;
///
/// The item that hosts this script
///
protected TaskInventoryItem m_item;
protected bool throwErrorOnNotImplemented = false;
protected float m_ScriptDelayFactor = 1.0f;
protected float m_Script10mDistance = 10.0f;
protected float m_Script10mDistanceSquare = 100.0f;
protected float m_MinTimerInterval = 0.5f;
protected float m_recoilScaleFactor = 0.0f;
protected bool m_AllowGodFunctions;
protected double m_timer = Util.GetTimeStampMS();
protected bool m_waitingForScriptAnswer = false;
protected bool m_automaticLinkPermission = false;
protected int m_notecardLineReadCharsMax = 255;
protected int m_scriptConsoleChannel = 0;
protected bool m_scriptConsoleChannelEnabled = false;
protected bool m_debuggerSafe = false;
protected AsyncCommandManager m_AsyncCommands = null;
protected IUrlModule m_UrlModule = null;
protected IMaterialsModule m_materialsModule = null;
protected IEnvironmentModule m_envModule = null;
protected IEmailModule m_emailModule = null;
protected IUserAccountService m_userAccountService = null;
protected IMessageTransferModule m_TransferModule = null;
protected ExpiringCacheOS m_PresenceInfoCache = new(10000);
protected int EMAIL_PAUSE_TIME = 20; // documented delay value for smtp.
protected int m_sleepMsOnSetTexture = 200;
protected int m_sleepMsOnSetLinkTexture = 200;
protected int m_sleepMsOnScaleTexture = 200;
protected int m_sleepMsOnOffsetTexture = 200;
protected int m_sleepMsOnRotateTexture = 200;
protected int m_sleepMsOnSetPos = 200;
protected int m_sleepMsOnSetRot = 200;
protected int m_sleepMsOnSetLocalRot = 200;
protected int m_sleepMsOnPreloadSound = 1000;
protected int m_sleepMsOnMakeExplosion = 100;
protected int m_sleepMsOnMakeFountain = 100;
protected int m_sleepMsOnMakeSmoke = 100;
protected int m_sleepMsOnMakeFire = 100;
protected int m_sleepMsOnRezAtRoot = 100;
protected int m_sleepMsOnInstantMessage = 2000;
protected int m_sleepMsOnEmail = 30000;
protected int m_sleepMsOnCreateLink = 1000;
protected int m_sleepMsOnGiveInventory = 3000;
protected int m_sleepMsOnRequestAgentData = 100;
protected int m_sleepMsOnRequestInventoryData = 1000;
protected int m_sleepMsOnSetDamage = 5000;
protected int m_sleepMsOnTextBox = 1000;
protected int m_sleepMsOnAdjustSoundVolume = 100;
protected int m_sleepMsOnEjectFromLand = 1000;
protected int m_sleepMsOnAddToLandPassList = 100;
protected int m_sleepMsOnDialog = 1000;
protected int m_sleepMsOnRemoteLoadScript = 3000;
protected int m_sleepMsOnRemoteLoadScriptPin = 3000;
protected int m_sleepMsOnOpenRemoteDataChannel = 1000;
protected int m_sleepMsOnSendRemoteData = 3000;
protected int m_sleepMsOnRemoteDataReply = 3000;
protected int m_sleepMsOnCloseRemoteDataChannel = 1000;
protected int m_sleepMsOnSetPrimitiveParams = 200;
protected int m_sleepMsOnSetLinkPrimitiveParams = 200;
protected int m_sleepMsOnXorBase64Strings = 300;
protected int m_sleepMsOnSetParcelMusicURL = 2000;
protected int m_sleepMsOnGetPrimMediaParams = 1000;
protected int m_sleepMsOnGetLinkMedia = 1000;
protected int m_sleepMsOnSetPrimMediaParams = 1000;
protected int m_sleepMsOnSetLinkMedia = 1000;
protected int m_sleepMsOnClearPrimMedia = 1000;
protected int m_sleepMsOnClearLinkMedia = 1000;
protected int m_sleepMsOnRequestSimulatorData = 1000;
protected int m_sleepMsOnLoadURL = 1000;
protected int m_sleepMsOnParcelMediaCommandList = 2000;
protected int m_sleepMsOnParcelMediaQuery = 2000;
protected int m_sleepMsOnModPow = 1000;
protected int m_sleepMsOnSetPrimURL = 2000;
protected int m_sleepMsOnRefreshPrimURL = 20000;
protected int m_sleepMsOnMapDestination = 1000;
protected int m_sleepMsOnAddToLandBanList = 100;
protected int m_sleepMsOnRemoveFromLandPassList = 100;
protected int m_sleepMsOnRemoveFromLandBanList = 100;
protected int m_sleepMsOnResetLandBanList = 100;
protected int m_sleepMsOnResetLandPassList = 100;
protected int m_sleepMsOnGetParcelPrimOwners = 2000;
protected int m_sleepMsOnGetNumberOfNotecardLines = 100;
protected int m_sleepMsOnGetNotecardLine = 100;
protected string m_internalObjectHost = "lsl.opensim.local";
protected bool m_restrictEmail = false;
protected ISoundModule m_SoundModule = null;
protected float m_avatarHeightCorrection = 0.2f;
protected bool m_useSimpleBoxesInGetBoundingBox = false;
protected bool m_addStatsInGetBoundingBox = false;
//LSL Avatar Bounding Box (lABB), lower (1) and upper (2),
//standing (Std), Groundsitting (Grs), Sitting (Sit),
//along X, Y and Z axes, constants (0) and coefficients (1)
protected float m_lABB1StdX0 = -0.275f;
protected float m_lABB2StdX0 = 0.275f;
protected float m_lABB1StdY0 = -0.35f;
protected float m_lABB2StdY0 = 0.35f;
protected float m_lABB1StdZ0 = -0.1f;
protected float m_lABB1StdZ1 = -0.5f;
protected float m_lABB2StdZ0 = 0.1f;
protected float m_lABB2StdZ1 = 0.5f;
protected float m_lABB1GrsX0 = -0.3875f;
protected float m_lABB2GrsX0 = 0.3875f;
protected float m_lABB1GrsY0 = -0.5f;
protected float m_lABB2GrsY0 = 0.5f;
protected float m_lABB1GrsZ0 = -0.05f;
protected float m_lABB1GrsZ1 = -0.375f;
protected float m_lABB2GrsZ0 = 0.5f;
protected float m_lABB2GrsZ1 = 0.0f;
protected float m_lABB1SitX0 = -0.5875f;
protected float m_lABB2SitX0 = 0.1875f;
protected float m_lABB1SitY0 = -0.35f;
protected float m_lABB2SitY0 = 0.35f;
protected float m_lABB1SitZ0 = -0.35f;
protected float m_lABB1SitZ1 = -0.375f;
protected float m_lABB2SitZ0 = -0.25f;
protected float m_lABB2SitZ1 = 0.25f;
protected float m_primSafetyCoeffX = 2.414214f;
protected float m_primSafetyCoeffY = 2.414214f;
protected float m_primSafetyCoeffZ = 1.618034f;
protected float m_floatToleranceInCastRay = 0.00001f;
protected float m_floatTolerance2InCastRay = 0.001f;
protected DetailLevel m_primLodInCastRay = DetailLevel.Medium;
protected DetailLevel m_sculptLodInCastRay = DetailLevel.Medium;
protected DetailLevel m_meshLodInCastRay = DetailLevel.Highest;
protected DetailLevel m_avatarLodInCastRay = DetailLevel.Medium;
protected int m_maxHitsInCastRay = 16;
protected int m_maxHitsPerPrimInCastRay = 16;
protected int m_maxHitsPerObjectInCastRay = 16;
protected bool m_detectExitsInCastRay = false;
protected bool m_doAttachmentsInCastRay = false;
protected int m_msThrottleInCastRay = 200;
protected int m_msPerRegionInCastRay = 40;
protected int m_msPerAvatarInCastRay = 10;
protected int m_msMinInCastRay = 2;
protected int m_msMaxInCastRay = 40;
protected static List m_castRayCalls = new();
protected bool m_useMeshCacheInCastRay = true;
protected static Dictionary m_cachedMeshes = new();
//protected Timer m_ShoutSayTimer;
protected int m_SayShoutCount = 0;
DateTime m_lastSayShoutCheck;
private int m_whisperdistance = 10;
private int m_saydistance = 20;
private int m_shoutdistance = 100;
bool m_disable_underground_movement = true;
private string m_lsl_shard = "OpenSim";
private string m_lsl_user_agent = string.Empty;
private int m_linksetDataLimit = 32 * 1024;
private static readonly Dictionary MovementAnimationsForLSL = new(StringComparer.InvariantCultureIgnoreCase)
{
{"CROUCH", "Crouching"},
{"CROUCHWALK", "CrouchWalking"},
{"FALLDOWN", "Falling Down"},
{"FLY", "Flying"},
{"FLYSLOW", "FlyingSlow"},
{"HOVER", "Hovering"},
{"HOVER_UP", "Hovering Up"},
{"HOVER_DOWN", "Hovering Down"},
{"JUMP", "Jumping"},
{"LAND", "Landing"},
{"PREJUMP", "PreJumping"},
{"RUN", "Running"},
{"SIT","Sitting"},
{"SITGROUND","Sitting on Ground"},
{"STAND", "Standing"},
{"STANDUP", "Standing Up"},
{"STRIDE","Striding"},
{"SOFT_LAND", "Soft Landing"},
{"TURNLEFT", "Turning Left"},
{"TURNRIGHT", "Turning Right"},
{"WALK", "Walking"}
};
//llHTTPRequest custom headers use control
// true means fatal error,
// false means ignore,
// missing means allowed
private static readonly Dictionary HttpForbiddenHeaders = new(StringComparer.InvariantCultureIgnoreCase)
{
{"Accept", true},
{"Accept-Charset", true},
{"Accept-CH", false},
{"Accept-CH-Lifetime", false},
{"Access-Control-Request-Headers", false},
{"Access-Control-Request-Method", false},
{"Accept-Encoding", false},
//{"Accept-Language", false},
{"Accept-Patch", false}, // it is server side
{"Accept-Post", false}, // it is server side
{"Accept-Ranges", false}, // it is server side
//{"Age", false},
//{"Allow", false},
//{"Authorization", false},
{"Cache-Control", false},
{"Connection", false},
{"Content-Length", false},
//{"Content-Encoding", false},
//{"Content-Location", false},
//{"Content-MD5", false},
//{"Content-Range", false},
{"Content-Type", true},
{"Cookie", false},
{"Cookie2", false},
{"Date", false},
{"Device-Memory", false},
{"DTN", false},
{"Early-Data", false},
//{"ETag", false},
{"Expect", false},
//{"Expires", false},
{"Feature-Policy", false},
{"From", true},
{"Host", true},
{"Keep-Alive", false},
{"If-Match", false},
{"If-Modified-Since", false},
{"If-None-Match", false},
//{"If-Range", false},
{"If-Unmodified-Since", false},
//{"Last-Modified", false},
//{"Location", false},
{"Max-Forwards", false},
{"Origin", false},
{"Pragma", false},
//{"Proxy-Authenticate", false},
//{"Proxy-Authorization", false},
//{"Range", false},
{"Referer", true},
//{"Retry-After", false},
{"Server", false},
{"Set-Cookie", false},
{"Set-Cookie2", false},
{"TE", true},
{"Trailer", true},
{"Transfer-Encoding", false},
{"Upgrade", true},
{"User-Agent", true},
{"Vary", false},
{"Via", true},
{"Viewport-Width", false},
{"Warning", false},
{"Width", false},
//{"WWW-Authenticate", false},
{"X-Forwarded-For", false},
{"X-Forwarded-Host", false},
{"X-Forwarded-Proto", false},
{"x-secondlife-shard", false},
{"x-secondlife-object-name", false},
{"x-secondlife-object-key", false},
{"x-secondlife-region", false},
{"x-secondlife-local-position", false},
{"x-secondlife-local-velocity", false},
{"x-secondlife-local-rotation", false},
{"x-secondlife-owner-name", false},
{"x-secondlife-owner-key", false},
};
private static readonly HashSet HttpForbiddenInHeaders = new(StringComparer.InvariantCultureIgnoreCase)
{
"x-secondlife-shard", "x-secondlife-object-name", "x-secondlife-object-key",
"x-secondlife-region", "x-secondlife-local-position", "x-secondlife-local-velocity",
"x-secondlife-local-rotation", "x-secondlife-owner-name", "x-secondlife-owner-key",
"connection", "content-length", "from", "host", "proxy-authorization",
"referer", "trailer", "transfer-encoding", "via", "authorization"
};
public void Initialize(IScriptEngine scriptEngine, SceneObjectPart host, TaskInventoryItem item)
{
m_lastSayShoutCheck = DateTime.UtcNow;
m_ScriptEngine = scriptEngine;
World = m_ScriptEngine.World;
m_host = host;
m_item = item;
m_debuggerSafe = m_ScriptEngine.Config.GetBoolean("DebuggerSafe", false);
LoadConfig();
m_TransferModule = m_ScriptEngine.World.RequestModuleInterface();
m_UrlModule = m_ScriptEngine.World.RequestModuleInterface();
m_SoundModule = m_ScriptEngine.World.RequestModuleInterface();
m_materialsModule = m_ScriptEngine.World.RequestModuleInterface();
m_emailModule = m_ScriptEngine.World.RequestModuleInterface();
m_envModule = m_ScriptEngine.World.RequestModuleInterface< IEnvironmentModule>();
m_AsyncCommands = new AsyncCommandManager(m_ScriptEngine);
m_userAccountService = World.UserAccountService;
if(World.RegionInfo != null)
{
RegionScopeID = World.RegionInfo.ScopeID;
m_regionName = World.RegionInfo.RegionName;
}
}
///
/// Load configuration items that affect script, object and run-time behavior. */
///
private void LoadConfig()
{
LlRequestAgentDataCacheTimeoutMs = 20000;
IConfig seConfig = m_ScriptEngine.Config;
if (seConfig is not null)
{
float scriptDistanceFactor = seConfig.GetFloat("ScriptDistanceLimitFactor", 1.0f);
m_Script10mDistance = 10.0f * scriptDistanceFactor;
m_Script10mDistanceSquare = m_Script10mDistance * m_Script10mDistance;
m_ScriptDelayFactor = seConfig.GetFloat("ScriptDelayFactor", m_ScriptDelayFactor);
m_MinTimerInterval = seConfig.GetFloat("MinTimerInterval", m_MinTimerInterval);
m_automaticLinkPermission = seConfig.GetBoolean("AutomaticLinkPermission", m_automaticLinkPermission);
m_notecardLineReadCharsMax = seConfig.GetInt("NotecardLineReadCharsMax", m_notecardLineReadCharsMax);
// Rezzing an object with a velocity can create recoil. This feature seems to have been
// removed from recent versions of SL. The code computes recoil (vel*mass) and scales
// it by this factor. May be zero to turn off recoil all together.
m_recoilScaleFactor = seConfig.GetFloat("RecoilScaleFactor", m_recoilScaleFactor);
m_AllowGodFunctions = seConfig.GetBoolean("AllowGodFunctions", false);
m_disable_underground_movement = seConfig.GetBoolean("DisableUndergroundMovement", true);
m_linksetDataLimit = seConfig.GetInt("LinksetDataLimit", m_linksetDataLimit);
}
if (m_notecardLineReadCharsMax > 65535)
m_notecardLineReadCharsMax = 65535;
// load limits for particular subsystems.
IConfigSource seConfigSource = m_ScriptEngine.ConfigSource;
if (seConfigSource != null)
{
IConfig netConfig = seConfigSource.Configs["Network"];
if (netConfig != null)
{
m_lsl_shard = netConfig.GetString("shard", m_lsl_shard);
m_lsl_user_agent = netConfig.GetString("user_agent", m_lsl_user_agent);
}
IConfig lslConfig = seConfigSource.Configs["LL-Functions"];
if (lslConfig != null)
{
m_restrictEmail = lslConfig.GetBoolean("RestrictEmail", m_restrictEmail);
m_avatarHeightCorrection = lslConfig.GetFloat("AvatarHeightCorrection", m_avatarHeightCorrection);
m_useSimpleBoxesInGetBoundingBox = lslConfig.GetBoolean("UseSimpleBoxesInGetBoundingBox", m_useSimpleBoxesInGetBoundingBox);
m_addStatsInGetBoundingBox = lslConfig.GetBoolean("AddStatsInGetBoundingBox", m_addStatsInGetBoundingBox);
m_lABB1StdX0 = lslConfig.GetFloat("LowerAvatarBoundingBoxStandingXconst", m_lABB1StdX0);
m_lABB2StdX0 = lslConfig.GetFloat("UpperAvatarBoundingBoxStandingXconst", m_lABB2StdX0);
m_lABB1StdY0 = lslConfig.GetFloat("LowerAvatarBoundingBoxStandingYconst", m_lABB1StdY0);
m_lABB2StdY0 = lslConfig.GetFloat("UpperAvatarBoundingBoxStandingYconst", m_lABB2StdY0);
m_lABB1StdZ0 = lslConfig.GetFloat("LowerAvatarBoundingBoxStandingZconst", m_lABB1StdZ0);
m_lABB1StdZ1 = lslConfig.GetFloat("LowerAvatarBoundingBoxStandingZcoeff", m_lABB1StdZ1);
m_lABB2StdZ0 = lslConfig.GetFloat("UpperAvatarBoundingBoxStandingZconst", m_lABB2StdZ0);
m_lABB2StdZ1 = lslConfig.GetFloat("UpperAvatarBoundingBoxStandingZcoeff", m_lABB2StdZ1);
m_lABB1GrsX0 = lslConfig.GetFloat("LowerAvatarBoundingBoxGroundsittingXconst", m_lABB1GrsX0);
m_lABB2GrsX0 = lslConfig.GetFloat("UpperAvatarBoundingBoxGroundsittingXconst", m_lABB2GrsX0);
m_lABB1GrsY0 = lslConfig.GetFloat("LowerAvatarBoundingBoxGroundsittingYconst", m_lABB1GrsY0);
m_lABB2GrsY0 = lslConfig.GetFloat("UpperAvatarBoundingBoxGroundsittingYconst", m_lABB2GrsY0);
m_lABB1GrsZ0 = lslConfig.GetFloat("LowerAvatarBoundingBoxGroundsittingZconst", m_lABB1GrsZ0);
m_lABB1GrsZ1 = lslConfig.GetFloat("LowerAvatarBoundingBoxGroundsittingZcoeff", m_lABB1GrsZ1);
m_lABB2GrsZ0 = lslConfig.GetFloat("UpperAvatarBoundingBoxGroundsittingZconst", m_lABB2GrsZ0);
m_lABB2GrsZ1 = lslConfig.GetFloat("UpperAvatarBoundingBoxGroundsittingZcoeff", m_lABB2GrsZ1);
m_lABB1SitX0 = lslConfig.GetFloat("LowerAvatarBoundingBoxSittingXconst", m_lABB1SitX0);
m_lABB2SitX0 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingXconst", m_lABB2SitX0);
m_lABB1SitY0 = lslConfig.GetFloat("LowerAvatarBoundingBoxSittingYconst", m_lABB1SitY0);
m_lABB2SitY0 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingYconst", m_lABB2SitY0);
m_lABB1SitZ0 = lslConfig.GetFloat("LowerAvatarBoundingBoxSittingZconst", m_lABB1SitZ0);
m_lABB1SitZ1 = lslConfig.GetFloat("LowerAvatarBoundingBoxSittingZcoeff", m_lABB1SitZ1);
m_lABB2SitZ0 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingZconst", m_lABB2SitZ0);
m_lABB2SitZ1 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingZcoeff", m_lABB2SitZ1);
m_primSafetyCoeffX = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientX", m_primSafetyCoeffX);
m_primSafetyCoeffY = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientY", m_primSafetyCoeffY);
m_primSafetyCoeffZ = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientZ", m_primSafetyCoeffZ);
m_floatToleranceInCastRay = lslConfig.GetFloat("FloatToleranceInLlCastRay", m_floatToleranceInCastRay);
m_floatTolerance2InCastRay = lslConfig.GetFloat("FloatTolerance2InLlCastRay", m_floatTolerance2InCastRay);
m_primLodInCastRay = (DetailLevel)lslConfig.GetInt("PrimDetailLevelInLlCastRay", (int)m_primLodInCastRay);
m_sculptLodInCastRay = (DetailLevel)lslConfig.GetInt("SculptDetailLevelInLlCastRay", (int)m_sculptLodInCastRay);
m_meshLodInCastRay = (DetailLevel)lslConfig.GetInt("MeshDetailLevelInLlCastRay", (int)m_meshLodInCastRay);
m_avatarLodInCastRay = (DetailLevel)lslConfig.GetInt("AvatarDetailLevelInLlCastRay", (int)m_avatarLodInCastRay);
m_maxHitsInCastRay = lslConfig.GetInt("MaxHitsInLlCastRay", m_maxHitsInCastRay);
m_maxHitsPerPrimInCastRay = lslConfig.GetInt("MaxHitsPerPrimInLlCastRay", m_maxHitsPerPrimInCastRay);
m_maxHitsPerObjectInCastRay = lslConfig.GetInt("MaxHitsPerObjectInLlCastRay", m_maxHitsPerObjectInCastRay);
m_detectExitsInCastRay = lslConfig.GetBoolean("DetectExitHitsInLlCastRay", m_detectExitsInCastRay);
m_doAttachmentsInCastRay = lslConfig.GetBoolean("DoAttachmentsInLlCastRay", m_doAttachmentsInCastRay);
m_msThrottleInCastRay = lslConfig.GetInt("ThrottleTimeInMsInLlCastRay", m_msThrottleInCastRay);
m_msPerRegionInCastRay = lslConfig.GetInt("AvailableTimeInMsPerRegionInLlCastRay", m_msPerRegionInCastRay);
m_msPerAvatarInCastRay = lslConfig.GetInt("AvailableTimeInMsPerAvatarInLlCastRay", m_msPerAvatarInCastRay);
m_msMinInCastRay = lslConfig.GetInt("RequiredAvailableTimeInMsInLlCastRay", m_msMinInCastRay);
m_msMaxInCastRay = lslConfig.GetInt("MaximumAvailableTimeInMsInLlCastRay", m_msMaxInCastRay);
m_useMeshCacheInCastRay = lslConfig.GetBoolean("UseMeshCacheInLlCastRay", m_useMeshCacheInCastRay);
}
IConfig smtpConfig = seConfigSource.Configs["SMTP"];
if (smtpConfig != null)
{
// there's an smtp config, so load in the snooze time.
EMAIL_PAUSE_TIME = smtpConfig.GetInt("email_pause_time", EMAIL_PAUSE_TIME);
m_internalObjectHost = smtpConfig.GetString("internal_object_host", m_internalObjectHost);
}
IConfig chatConfig = seConfigSource.Configs["SMTP"];
if(chatConfig != null)
{
m_whisperdistance = chatConfig.GetInt("whisper_distance", m_whisperdistance);
m_saydistance = chatConfig.GetInt("say_distance", m_saydistance);
m_shoutdistance = chatConfig.GetInt("shout_distance", m_shoutdistance);
}
}
m_sleepMsOnEmail = EMAIL_PAUSE_TIME * 1000;
}
protected SceneObjectPart MonitoringObject()
{
UUID m = m_host.ParentGroup.MonitoringObject;
if (m.IsZero())
return null;
SceneObjectPart p = m_ScriptEngine.World.GetSceneObjectPart(m);
if (p == null)
m_host.ParentGroup.MonitoringObject = UUID.Zero;
return p;
}
protected virtual void ScriptSleep(int delay)
{
delay = (int)(delay * m_ScriptDelayFactor);
if (delay < 10)
return;
Sleep(delay);
}
protected virtual void Sleep(int delay)
{
if (m_item == null) // Some unit tests don't set this
Thread.Sleep(delay);
else
m_ScriptEngine.SleepScript(m_item.ItemID, delay);
}
[DebuggerNonUserCode]
public void state(string newState)
{
m_ScriptEngine.SetState(m_item.ItemID, newState);
}
///
/// Reset the named script. The script must be present
/// in the same prim.
///
[DebuggerNonUserCode]
public void llResetScript()
{
// We need to tell the URL module, if we hav one, to release
// the allocated URLs
m_UrlModule?.ScriptRemoved(m_item.ItemID);
m_ScriptEngine.ApiResetScript(m_item.ItemID);
}
public void llResetOtherScript(string name)
{
UUID item = GetScriptByName(name);
if (item.IsZero())
{
Error("llResetOtherScript", "Can't find script '" + name + "'");
return;
}
if(item.Equals(m_item.ItemID))
llResetScript();
else
{
m_ScriptEngine.ResetScript(item);
}
}
public LSL_Integer llGetScriptState(string name)
{
UUID item = GetScriptByName(name);
if (!item.IsZero())
{
return m_ScriptEngine.GetScriptState(item) ?1:0;
}
Error("llGetScriptState", "Can't find script '" + name + "'");
// If we didn't find it, then it's safe to
// assume it is not running.
return 0;
}
public void llSetScriptState(string name, int run)
{
UUID item = GetScriptByName(name);
// These functions are supposed to be robust,
// so get the state one step at a time.
if (!item.IsZero())
{
m_ScriptEngine.SetScriptState(item, run != 0, item.Equals(m_item.ItemID));
}
else
{
Error("llSetScriptState", "Can't find script '" + name + "'");
}
}
public List GetLinkAvatars(int linkType)
{
if (m_host is null)
return new List();
return GetLinkAvatars(linkType, m_host.ParentGroup);
}
public static List GetLinkAvatars(int linkType, SceneObjectGroup sog)
{
List ret = new();
if (sog is null || sog.IsDeleted)
return ret;
List avs = sog.GetSittingAvatars();
switch (linkType)
{
case ScriptBaseClass.LINK_SET:
return avs;
case ScriptBaseClass.LINK_ROOT:
return ret;
case ScriptBaseClass.LINK_ALL_OTHERS:
return avs;
case ScriptBaseClass.LINK_ALL_CHILDREN:
return avs;
case ScriptBaseClass.LINK_THIS:
return ret;
default:
if (linkType < 0)
return ret;
int partCount = sog.GetPartCount();
linkType -= partCount;
if (linkType <= 0)
{
return ret;
}
else
{
if (linkType > avs.Count)
{
return ret;
}
else
{
ret.Add(avs[linkType - 1]);
return ret;
}
}
}
}
///
/// Get a given link entity from a linkset (linked objects and any sitting avatars).
///
///
/// If there are any ScenePresence's in the linkset (i.e. because they are sat upon one of the prims), then
/// these are counted as extra entities that correspond to linknums beyond the number of prims in the linkset.
/// The ScenePresences receive linknums in the order in which they sat.
///
///
/// The link entity. null if not found.
///
///
///
/// Can be either a non-negative integer or ScriptBaseClass.LINK_THIS (-4).
/// If ScriptBaseClass.LINK_THIS then the entity containing the script is returned.
/// If the linkset has one entity and a linknum of zero is given, then the single entity is returned. If any
/// positive integer is given in this case then null is returned.
/// If the linkset has more than one entity and a linknum greater than zero but equal to or less than the number
/// of entities, then the entity which corresponds to that linknum is returned.
/// Otherwise, if a positive linknum is given which is greater than the number of entities in the linkset, then
/// null is returned.
///
public static ISceneEntity GetLinkEntity(SceneObjectPart part, int linknum)
{
if (linknum == ScriptBaseClass.LINK_THIS)
return part;
if (linknum <= part.ParentGroup.PrimCount)
return part.ParentGroup.GetLinkNumPart(linknum);
return part.ParentGroup.GetLinkSitingAvatar(linknum);
}
public List GetLinkParts(int linkType)
{
return GetLinkParts(m_host, linkType);
}
public static List GetLinkParts(SceneObjectPart part, int linkType)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return new List();
List ret;
switch (linkType)
{
case ScriptBaseClass.LINK_SET:
return new List(part.ParentGroup.Parts);
case ScriptBaseClass.LINK_ROOT:
return new List { part.ParentGroup.RootPart };
case ScriptBaseClass.LINK_ALL_OTHERS:
ret = new List(part.ParentGroup.Parts);
ret.Remove(part);
return ret;
case ScriptBaseClass.LINK_ALL_CHILDREN:
ret = new List(part.ParentGroup.Parts);
ret.Remove(part.ParentGroup.RootPart);
return ret;
case ScriptBaseClass.LINK_THIS:
return new List { part };
default:
SceneObjectPart target = part.ParentGroup.GetLinkNumPart(linkType);
if (target is not null)
return new List { target };
return new List();
}
}
public List GetLinkEntities(int linkType)
{
return GetLinkEntities(m_host, linkType);
}
public static List GetLinkEntities(SceneObjectPart part, int linkType)
{
List ret;
switch (linkType)
{
case ScriptBaseClass.LINK_SET:
return new List(part.ParentGroup.Parts);
case ScriptBaseClass.LINK_ROOT:
return new List() { part.ParentGroup.RootPart };
case ScriptBaseClass.LINK_ALL_OTHERS:
ret = new List(part.ParentGroup.Parts);
ret.Remove(part);
return ret;
case ScriptBaseClass.LINK_ALL_CHILDREN:
ret = new List(part.ParentGroup.Parts);
ret.Remove(part.ParentGroup.RootPart);
List avs = part.ParentGroup.GetSittingAvatars();
if(avs is not null && avs.Count > 0)
ret.AddRange(avs);
return ret;
case ScriptBaseClass.LINK_THIS:
return new List() { part };
default:
if (linkType < 0)
return new List();
ISceneEntity target = GetLinkEntity(part, linkType);
if (target is null)
return new List();
return new List() { target };
}
}
//These are the implementations of the various ll-functions used by the LSL scripts.
public LSL_Float llSin(double f)
{
return Math.Sin(f);
}
public LSL_Float llCos(double f)
{
return Math.Cos(f);
}
public LSL_Float llTan(double f)
{
return Math.Tan(f);
}
public LSL_Float llAtan2(LSL_Float y, LSL_Float x)
{
return Math.Atan2(y, x);
}
public LSL_Float llSqrt(double f)
{
return Math.Sqrt(f);
}
public LSL_Float llPow(double fbase, double fexponent)
{
return (double)Math.Pow(fbase, fexponent);
}
public LSL_Integer llAbs(LSL_Integer i)
{
// changed to replicate LSL behaviour whereby minimum int value is returned untouched.
if (i == Int32.MinValue)
return i;
else
return Math.Abs(i);
}
public LSL_Float llFabs(double f)
{
return (double)Math.Abs(f);
}
public LSL_Float llFrand(double mag)
{
return Random.Shared.NextDouble() * mag;
}
public LSL_Integer llFloor(double f)
{
return (int)Math.Floor(f);
}
public LSL_Integer llCeil(double f)
{
return (int)Math.Ceiling(f);
}
// Xantor 01/May/2008 fixed midpointrounding (2.5 becomes 3.0 instead of 2.0, default = ToEven)
public LSL_Integer llRound(double f)
{
return (int)Math.Round(f, MidpointRounding.AwayFromZero);
}
//This next group are vector operations involving squaring and square root. ckrinke
public LSL_Float llVecMag(LSL_Vector v)
{
return LSL_Vector.Mag(v);
}
public LSL_Vector llVecNorm(LSL_Vector v)
{
return LSL_Vector.Norm(v);
}
private static double VecDist(LSL_Vector a, LSL_Vector b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
private static double VecDistSquare(LSL_Vector a, LSL_Vector b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
}
public LSL_Float llVecDist(LSL_Vector a, LSL_Vector b)
{
return VecDist(a, b);
}
public LSL_Vector llRot2Euler(LSL_Rotation q1)
{
double sqw = q1.s * q1.s;
double sqx = q1.x * q1.x;
double sqy = q1.z * q1.z;
double sqz = q1.y * q1.y;
double norm = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
double halfnorm = 0.49999 * norm;
double test = q1.x * q1.z + q1.y * q1.s;
if (test > halfnorm) // singularity at north pole
{
return new LSL_Vector(
0,
Math.PI / 2,
2 * Math.Atan2(q1.x, q1.s)
);
}
if (test < -halfnorm) // singularity at south pole
{
return new LSL_Vector(
0,
-Math.PI / 2,
-2 * Math.Atan2(q1.x, q1.s)
);
}
return new LSL_Vector(
Math.Atan2(2 * q1.x * q1.s - 2 * q1.z * q1.y , -sqx + sqy - sqz + sqw),
Math.Asin( 2 * test / norm),
Math.Atan2(2 * q1.z * q1.s - 2 * q1.x * q1.y, sqx - sqy - sqz + sqw)
);
}
public LSL_Rotation llEuler2Rot(LSL_Vector v)
{
double a = v.x * 0.5;
double s1 = Math.Sin(a);
double c1 = Math.Cos(a);
a = v.y * 0.5;
double s2 = Math.Sin(a);
double c2 = Math.Cos(a);
a = v.z * 0.5;
double s3 = Math.Sin(a);
double c3 = Math.Cos(a);
return new LSL_Rotation(
s1 * c2 * c3 + c1 * s2 * s3,
c1 * s2 * c3 - s1 * c2 * s3,
s1 * s2 * c3 + c1 * c2 * s3,
c1 * c2 * c3 - s1 * s2 * s3
);
}
public LSL_Rotation llAxes2Rot(LSL_Vector fwd, LSL_Vector left, LSL_Vector up)
{
double s;
double tr = fwd.x + left.y + up.z + 1.0;
if (tr >= 1.0)
{
s = 0.5 / Math.Sqrt(tr);
return new LSL_Rotation(
(left.z - up.y) * s,
(up.x - fwd.z) * s,
(fwd.y - left.x) * s,
0.25 / s);
}
else
{
double max = (left.y > up.z) ? left.y : up.z;
if (max < fwd.x)
{
s = Math.Sqrt(fwd.x - (left.y + up.z) + 1.0);
double x = s * 0.5;
s = 0.5 / s;
return new LSL_Rotation(
x,
(fwd.y + left.x) * s,
(up.x + fwd.z) * s,
(left.z - up.y) * s);
}
else if (max == left.y)
{
s = Math.Sqrt(left.y - (up.z + fwd.x) + 1.0);
double y = s * 0.5;
s = 0.5 / s;
return new LSL_Rotation(
(fwd.y + left.x) * s,
y,
(left.z + up.y) * s,
(up.x - fwd.z) * s);
}
else
{
s = Math.Sqrt(up.z - (fwd.x + left.y) + 1.0);
double z = s * 0.5;
s = 0.5 / s;
return new LSL_Rotation(
(up.x + fwd.z) * s,
(left.z + up.y) * s,
z,
(fwd.y - left.x) * s);
}
}
}
public LSL_Vector llRot2Fwd(LSL_Rotation r)
{
double m = r.x * r.x + r.y * r.y + r.z * r.z + r.s * r.s;
if (Math.Abs(1.0 - m) > 0.000001)
{
m = 1.0 / Math.Sqrt(m);
r.x *= m;
r.y *= m;
r.z *= m;
r.s *= m;
}
return new LSL_Vector(
r.x * r.x - r.y * r.y - r.z * r.z + r.s * r.s,
2 * (r.x * r.y + r.z * r.s),
2 * (r.x * r.z - r.y * r.s)
);
}
public LSL_Vector llRot2Left(LSL_Rotation r)
{
double m = r.x * r.x + r.y * r.y + r.z * r.z + r.s * r.s;
if (Math.Abs(1.0 - m) > 0.000001) // allow a little slop here for calculation precision
{
m = 1.0 / Math.Sqrt(m);
r.x *= m;
r.y *= m;
r.z *= m;
r.s *= m;
}
return new LSL_Vector(
2 * (r.x * r.y - r.z * r.s),
-r.x * r.x + r.y * r.y - r.z * r.z + r.s * r.s,
2 * (r.x * r.s + r.y * r.z)
);
}
public LSL_Vector llRot2Up(LSL_Rotation r)
{
double m = r.x * r.x + r.y * r.y + r.z * r.z + r.s * r.s;
if (Math.Abs(1.0 - m) > 0.000001) // allow a little slop here for calculation precision
{
m = 1.0 / Math.Sqrt(m);
r.x *= m;
r.y *= m;
r.z *= m;
r.s *= m;
}
return new LSL_Vector(
2 * (r.x * r.z + r.y * r.s),
2 * (-r.x * r.s + r.y * r.z),
-r.x * r.x - r.y * r.y + r.z * r.z + r.s * r.s
);
}
public LSL_Rotation llRotBetween(LSL_Vector a, LSL_Vector b)
{
//A and B should both be normalized
// This method mimics the 180 errors found in SL
// See www.euclideanspace.com... angleBetween
// Eliminate zero length
LSL_Float vec_a_mag = LSL_Vector.MagSquare(a);
LSL_Float vec_b_mag = LSL_Vector.MagSquare(b);
if (vec_a_mag < 1e-12 ||
vec_b_mag < 1e-12)
{
return new LSL_Rotation(0.0f, 0.0f, 0.0f, 1.0f);
}
// Normalize
a = llVecNorm(a);
b = llVecNorm(b);
// Calculate axis and rotation angle
LSL_Vector axis = a % b;
LSL_Float cos_theta = a * b;
// Check if parallel
if (cos_theta > 0.99999)
{
return new LSL_Rotation(0.0f, 0.0f, 0.0f, 1.0f);
}
// Check if anti-parallel
else if (cos_theta < -0.99999)
{
LSL_Vector orthog_axis = new LSL_Vector(1.0, 0.0, 0.0) - (a.x / (a * a) * a);
if (LSL_Vector.MagSquare(orthog_axis) < 1e-12)
orthog_axis = new LSL_Vector(0.0, 0.0, 1.0);
return new LSL_Rotation((float)orthog_axis.x, (float)orthog_axis.y, (float)orthog_axis.z, 0.0);
}
else // other rotation
{
LSL_Float theta = (LSL_Float)Math.Acos(cos_theta) * 0.5f;
axis = llVecNorm(axis);
double x, y, z, s, t;
s = Math.Cos(theta);
t = Math.Sin(theta);
x = axis.x * t;
y = axis.y * t;
z = axis.z * t;
return new LSL_Rotation(x,y,z,s);
}
}
public void llWhisper(int channelID, string text)
{
byte[] binText = Utils.StringToBytesNoTerm(text, 1023);
World.SimChat(binText,
ChatTypeEnum.Whisper, channelID, m_host.AbsolutePosition, m_host.Name, m_host.UUID, false);
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.DeliverMessage(ChatTypeEnum.Whisper, channelID, m_host.Name, m_host.UUID, Util.UTF8.GetString(binText), m_host.AbsolutePosition);
}
private void CheckSayShoutTime()
{
DateTime now = DateTime.UtcNow;
if ((now - m_lastSayShoutCheck).Ticks > 10000000) // 1sec
{
m_lastSayShoutCheck = now;
m_SayShoutCount = 0;
}
else
m_SayShoutCount++;
}
public void ThrottleSay(int channelID, int timeMs)
{
if (channelID == 0)
CheckSayShoutTime();
if (m_SayShoutCount >= 11)
ScriptSleep(timeMs);
}
public void llSay(int channelID, string text)
{
if (channelID == 0)
// m_SayShoutCount++;
CheckSayShoutTime();
if (m_SayShoutCount >= 11)
ScriptSleep(2000);
if (m_scriptConsoleChannelEnabled && (channelID == m_scriptConsoleChannel))
{
Console.WriteLine(text);
}
else
{
byte[] binText = Utils.StringToBytesNoTerm(text, 1023);
World.SimChat(binText,
ChatTypeEnum.Say, channelID, m_host.AbsolutePosition, m_host.Name, m_host.UUID, false);
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.DeliverMessage(ChatTypeEnum.Say, channelID, m_host.Name, m_host.UUID, Util.UTF8.GetString(binText), m_host.AbsolutePosition);
}
}
public void llShout(int channelID, string text)
{
if (channelID == 0)
// m_SayShoutCount++;
CheckSayShoutTime();
if (m_SayShoutCount >= 11)
ScriptSleep(2000);
byte[] binText = Utils.StringToBytesNoTerm(text, 1023);
World.SimChat(binText,
ChatTypeEnum.Shout, channelID, m_host.AbsolutePosition, m_host.Name, m_host.UUID, true);
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.DeliverMessage(ChatTypeEnum.Shout, channelID, m_host.Name, m_host.UUID, Util.UTF8.GetString(binText), m_host.AbsolutePosition);
}
public void llRegionSay(int channelID, string text)
{
if (channelID == 0)
{
Error("llRegionSay", "Cannot use on channel 0");
return;
}
byte[] binText = Utils.StringToBytesNoTerm(text, 1023);
// debug channel is also sent to avatars
if (channelID == ScriptBaseClass.DEBUG_CHANNEL)
{
World.SimChat(binText,
ChatTypeEnum.Shout, channelID, m_host.ParentGroup.RootPart.AbsolutePosition, m_host.Name, m_host.UUID, true);
}
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.DeliverMessage(ChatTypeEnum.Region, channelID, m_host.Name, m_host.UUID, Util.UTF8.GetString(binText));
}
public void llRegionSayTo(string target, int channel, string msg)
{
if (channel == ScriptBaseClass.DEBUG_CHANNEL)
return;
if(UUID.TryParse(target, out UUID TargetID) && TargetID.IsNotZero())
{
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
if (wComm != null)
{
if (msg.Length > 1023)
msg = msg[..1023];
wComm.DeliverMessageTo(TargetID, channel, m_host.AbsolutePosition, m_host.Name, m_host.UUID, msg);
}
}
}
public LSL_Integer llListen(int channelID, string name, string ID, string msg)
{
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
if (wComm is not null)
{
_ = UUID.TryParse(ID, out UUID keyID);
return wComm.Listen(m_item.ItemID, m_host.UUID, channelID, name, keyID, msg);
}
return -1;
}
public void llListenControl(int number, int active)
{
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.ListenControl(m_item.ItemID, number, active);
}
public void llListenRemove(int number)
{
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface();
wComm?.ListenRemove(m_item.ItemID, number);
}
public void llSensor(string name, string id, int type, double range, double arc)
{
_ = UUID.TryParse(id, out UUID keyID);
m_AsyncCommands.SensorRepeatPlugin.SenseOnce(m_host.LocalId, m_item.ItemID, name, keyID, type, range, arc, m_host);
}
public void llSensorRepeat(string name, string id, int type, double range, double arc, double rate)
{
_ = UUID.TryParse(id, out UUID keyID);
m_AsyncCommands.SensorRepeatPlugin.SetSenseRepeatEvent(m_host.LocalId, m_item.ItemID, name, keyID, type, range, arc, rate, m_host);
}
public void llSensorRemove()
{
m_AsyncCommands.SensorRepeatPlugin.UnSetSenseRepeaterEvents(m_host.LocalId, m_item.ItemID);
}
public string resolveName(UUID objecUUID)
{
// try avatar username surname
UserAccount account = m_userAccountService.GetUserAccount(RegionScopeID, objecUUID);
if (account is not null)
{
string avatarname = account.Name;
return avatarname;
}
// try an scene object
SceneObjectPart SOP = World.GetSceneObjectPart(objecUUID);
if (SOP is not null)
{
string objectname = SOP.Name;
return objectname;
}
World.Entities.TryGetValue(objecUUID, out EntityBase SensedObject);
if (SensedObject is null)
{
IGroupsModule groups = World.RequestModuleInterface();
if (groups is not null)
{
GroupRecord gr = groups.GetGroupRecord(objecUUID);
if (gr is not null)
return gr.GroupName;
}
return String.Empty;
}
return SensedObject.Name;
}
public LSL_String llDetectedName(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? LSL_String.Empty : detectedParams.Name;
}
public LSL_Key llDetectedKey(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? LSL_String.Empty : detectedParams.Key.ToString();
}
public LSL_Key llDetectedOwner(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? LSL_String.Empty : detectedParams.Owner.ToString();
}
public LSL_Integer llDetectedType(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? 0 : new LSL_Integer(detectedParams.Type);
}
public LSL_Vector llDetectedPos(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? new LSL_Vector() : detectedParams.Position;
}
public LSL_Vector llDetectedVel(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams == null ? new LSL_Vector() : detectedParams.Velocity;
}
public LSL_Vector llDetectedGrab(int number)
{
DetectParams parms = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return parms is null ? new LSL_Vector() : parms.OffsetPos;
}
public LSL_Rotation llDetectedRot(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return detectedParams is null ? new LSL_Rotation() : detectedParams.Rotation;
}
public LSL_Integer llDetectedGroup(int number)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
if (detectedParams is null)
return new LSL_Integer(0);
if (m_host.GroupID.Equals(detectedParams.Group))
return new LSL_Integer(1);
return new LSL_Integer(0);
}
public LSL_Integer llDetectedLinkNumber(int number)
{
DetectParams parms = m_ScriptEngine.GetDetectParams(m_item.ItemID, number);
return parms is null ? new LSL_Integer() : new LSL_Integer(parms.LinkNum);
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchBinormal for details
///
public LSL_Vector llDetectedTouchBinormal(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Vector() : detectedParams.TouchBinormal;
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchFace for details
///
public LSL_Integer llDetectedTouchFace(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Integer(-1) : new LSL_Integer(detectedParams.TouchFace);
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchNormal for details
///
public LSL_Vector llDetectedTouchNormal(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Vector() : detectedParams.TouchNormal;
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchPos for details
///
public LSL_Vector llDetectedTouchPos(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Vector() : detectedParams.TouchPos;
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchST for details
///
public LSL_Vector llDetectedTouchST(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Vector(-1.0, -1.0, 0.0) : detectedParams.TouchST;
}
///
/// See http://wiki.secondlife.com/wiki/LlDetectedTouchUV for details
///
public LSL_Vector llDetectedTouchUV(int index)
{
DetectParams detectedParams = m_ScriptEngine.GetDetectParams(m_item.ItemID, index);
return detectedParams is null ? new LSL_Vector(-1.0, -1.0, 0.0) : detectedParams.TouchUV;
}
[DebuggerNonUserCode]
public virtual void llDie()
{
if (!m_host.ParentGroup.IsAttachment) throw new SelfDeleteException();
}
public LSL_Float llGround(LSL_Vector offset)
{
Vector3 pos = m_host.GetWorldPosition();
pos.X += (float)offset.x;
pos.Y += (float)offset.y;
return World.GetGroundHeight(pos.X, pos.Y);
}
public LSL_Float llCloud(LSL_Vector offset)
{
return 0;
}
public LSL_Vector llWind(LSL_Vector offset)
{
LSL_Vector wind = new();
IWindModule module = World.RequestModuleInterface();
if (module is not null)
{
Vector3 pos = m_host.GetWorldPosition();
int x = (int)(pos.X + offset.x);
int y = (int)(pos.Y + offset.y);
Vector3 windSpeed = module.WindSpeed(x, y, 0);
wind.x = windSpeed.X;
wind.y = windSpeed.Y;
}
return wind;
}
public void llSetStatus(int status, int value)
{
if (m_host is null || m_host.ParentGroup is null || m_host.ParentGroup.IsDeleted)
return;
if ((status & ScriptBaseClass.STATUS_CAST_SHADOWS) != 0)
m_host.AddFlag(PrimFlags.CastShadows);
if ((status & ScriptBaseClass.STATUS_BLOCK_GRAB) != 0)
m_host.BlockGrab = value != 0;
if ((status & ScriptBaseClass.STATUS_BLOCK_GRAB_OBJECT) != 0)
m_host.ParentGroup.BlockGrabOverride = value != 0;
if ((status & ScriptBaseClass.STATUS_DIE_AT_EDGE) != 0)
m_host.SetDieAtEdge(value != 0);
if ((status & ScriptBaseClass.STATUS_RETURN_AT_EDGE) != 0)
m_host.SetReturnAtEdge(value != 0);
if ((status & ScriptBaseClass.STATUS_SANDBOX) != 0)
m_host.SetStatusSandbox(value != 0);
int statusrotationaxis = status & (ScriptBaseClass.STATUS_ROTATE_X | ScriptBaseClass.STATUS_ROTATE_Y | ScriptBaseClass.STATUS_ROTATE_Z);
if (statusrotationaxis != 0)
m_host.ParentGroup.SetAxisRotation(statusrotationaxis, value);
if ((status & ScriptBaseClass.STATUS_PHANTOM) != 0)
m_host.ParentGroup.ScriptSetPhantomStatus(value != 0);
if ((status & ScriptBaseClass.STATUS_PHYSICS) != 0)
{
if (value != 0)
{
SceneObjectGroup group = m_host.ParentGroup;
if (group.RootPart.PhysActor is null || !group.RootPart.PhysActor.IsPhysical)
{
bool allow = true;
int maxprims = World.m_linksetPhysCapacity;
bool checkShape = (maxprims > 0 && group.PrimCount > maxprims);
foreach (SceneObjectPart part in group.Parts)
{
if (part.PhysicsShapeType == (byte)PhysicsShapeType.None)
continue;
if (checkShape)
{
if (--maxprims < 0)
{
allow = false;
break;
}
}
if (part.Scale.X > World.m_maxPhys || part.Scale.Y > World.m_maxPhys || part.Scale.Z > World.m_maxPhys)
{
allow = false;
break;
}
}
if (allow)
m_host.ScriptSetPhysicsStatus(true);
}
}
else
{
m_host.ScriptSetPhysicsStatus(false);
}
}
}
private bool IsPhysical()
{
return ((m_host.GetEffectiveObjectFlags() & (uint)PrimFlags.Physics) != 0);
}
public LSL_Integer llGetStatus(int status)
{
// m_log.Debug(m_host.ToString() + " status is " + m_host.GetEffectiveObjectFlags().ToString());
return status switch
{
ScriptBaseClass.STATUS_PHYSICS => (LSL_Integer)(IsPhysical() ? 1 : 0),
ScriptBaseClass.STATUS_PHANTOM => (LSL_Integer)((m_host.GetEffectiveObjectFlags() & (uint)PrimFlags.Phantom) != 0 ? 1 : 0),
ScriptBaseClass.STATUS_CAST_SHADOWS => (LSL_Integer)((m_host.GetEffectiveObjectFlags() & (uint)PrimFlags.CastShadows) != 0 ? 1 : 0),
ScriptBaseClass.STATUS_BLOCK_GRAB => (LSL_Integer)(m_host.BlockGrab ? 1 : 0),
ScriptBaseClass.STATUS_BLOCK_GRAB_OBJECT => (LSL_Integer)(m_host.ParentGroup.BlockGrabOverride ? 1 : 0),
ScriptBaseClass.STATUS_DIE_AT_EDGE => (LSL_Integer)(m_host.GetDieAtEdge() ? 1 : 0),
ScriptBaseClass.STATUS_RETURN_AT_EDGE => (LSL_Integer)(m_host.GetReturnAtEdge() ? 1 : 0),
ScriptBaseClass.STATUS_ROTATE_X => (LSL_Integer)(m_host.GetAxisRotation((int)SceneObjectGroup.axisSelect.STATUS_ROTATE_X) != 0 ? 1 : 0),
ScriptBaseClass.STATUS_ROTATE_Y => (LSL_Integer)(m_host.GetAxisRotation((int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Y) != 0 ? 1 : 0),
ScriptBaseClass.STATUS_ROTATE_Z => (LSL_Integer)(m_host.GetAxisRotation((int)SceneObjectGroup.axisSelect.STATUS_ROTATE_Z) != 0 ? 1 : 0),
ScriptBaseClass.STATUS_SANDBOX => (LSL_Integer)(m_host.GetStatusSandbox() ? 1 : 0),
_ => (LSL_Integer)0,
};
}
public LSL_Integer llScaleByFactor(double scaling_factor)
{
SceneObjectGroup group = m_host.ParentGroup;
if(scaling_factor < 1e-6)
return ScriptBaseClass.FALSE;
if(scaling_factor > 1e6)
return ScriptBaseClass.FALSE;
if (group is null || group.IsDeleted || group.inTransit)
return ScriptBaseClass.FALSE;
if (group.RootPart.PhysActor is not null && group.RootPart.PhysActor.IsPhysical)
return ScriptBaseClass.FALSE;
if (group.RootPart.KeyframeMotion is not null)
return ScriptBaseClass.FALSE;
return group.GroupResize(scaling_factor) ? 1 : 0;
}
public LSL_Float llGetMaxScaleFactor()
{
SceneObjectGroup group = m_host.ParentGroup;
if (group is null || group.IsDeleted || group.inTransit)
return 1.0f;
return (LSL_Float)group.GetMaxGroupResizeScale();
}
public LSL_Float llGetMinScaleFactor()
{
SceneObjectGroup group = m_host.ParentGroup;
if (group is null || group.IsDeleted || group.inTransit)
return 1.0f;
return (LSL_Float)group.GetMinGroupResizeScale();
}
public void llSetScale(LSL_Vector scale)
{
SetScale(m_host, scale);
}
protected void SetScale(SceneObjectPart part, LSL_Vector scale)
{
// TODO: this needs to trigger a persistance save as well
if (part is null || part.ParentGroup.IsDeleted)
return;
// First we need to check whether or not we need to clamp the size of a physics-enabled prim
PhysicsActor pa = part.ParentGroup.RootPart.PhysActor;
if (pa != null && pa.IsPhysical)
{
scale.x = Math.Max(World.m_minPhys, Math.Min(World.m_maxPhys, scale.x));
scale.y = Math.Max(World.m_minPhys, Math.Min(World.m_maxPhys, scale.y));
scale.z = Math.Max(World.m_minPhys, Math.Min(World.m_maxPhys, scale.z));
}
else
{
// If not physical, then we clamp the scale to the non-physical min/max
scale.x = Math.Max(World.m_minNonphys, Math.Min(World.m_maxNonphys, scale.x));
scale.y = Math.Max(World.m_minNonphys, Math.Min(World.m_maxNonphys, scale.y));
scale.z = Math.Max(World.m_minNonphys, Math.Min(World.m_maxNonphys, scale.z));
}
Vector3 tmp = part.Scale;
tmp.X = (float)scale.x;
tmp.Y = (float)scale.y;
tmp.Z = (float)scale.z;
part.Scale = tmp;
part.ParentGroup.HasGroupChanged = true;
part.SendFullUpdateToAllClients();
}
public LSL_Vector llGetScale()
{
return new LSL_Vector(m_host.Scale.X, m_host.Scale.Y, m_host.Scale.Z);
}
public void llSetClickAction(int action)
{
m_host.ClickAction = (byte)action;
m_host.ParentGroup.HasGroupChanged = true;
m_host.ScheduleFullUpdate();
return;
}
public void llSetColor(LSL_Vector color, int face)
{
SetColor(m_host, color, face);
}
protected void SetColor(SceneObjectPart part, LSL_Vector color, int face)
{
if (part == null || part.ParentGroup == null || part.ParentGroup.IsDeleted)
return;
m_host.SetFaceColorAlpha(face, color, null);
}
public void llSetContentType(LSL_Key reqid, LSL_Integer type)
{
if (m_UrlModule == null)
return;
if(!UUID.TryParse(reqid, out UUID id) || id.IsZero())
return;
// Make sure the content type is text/plain to start with
m_UrlModule.HttpContentType(id, "text/plain");
// Is the object owner online and in the region
ScenePresence agent = World.GetScenePresence(m_host.ParentGroup.OwnerID);
if (agent == null || agent.IsChildAgent || agent.IsDeleted)
return; // Fail if the owner is not in the same region
// Is it the embeded browser?
string userAgent = m_UrlModule.GetHttpHeader(id, "user-agent");
if(string.IsNullOrEmpty(userAgent))
return;
if (userAgent.IndexOf("SecondLife") < 0)
return; // Not the embedded browser
// Use the IP address of the client and check against the request
// seperate logins from the same IP will allow all of them to get non-text/plain as long
// as the owner is in the region. Same as SL!
string logonFromIPAddress = agent.ControllingClient.RemoteEndPoint.Address.ToString();
if (string.IsNullOrEmpty(logonFromIPAddress))
return;
string requestFromIPAddress = m_UrlModule.GetHttpHeader(id, "x-remote-ip");
//m_log.Debug("IP from header='" + requestFromIPAddress + "' IP from endpoint='" + logonFromIPAddress + "'");
if (requestFromIPAddress == null)
return;
requestFromIPAddress = requestFromIPAddress.Trim();
// If the request isnt from the same IP address then the request cannot be from the owner
if (!requestFromIPAddress.Equals(logonFromIPAddress))
return;
switch (type)
{
case ScriptBaseClass.CONTENT_TYPE_HTML:
m_UrlModule.HttpContentType(id, "text/html");
break;
case ScriptBaseClass.CONTENT_TYPE_XML:
m_UrlModule.HttpContentType(id, "application/xml");
break;
case ScriptBaseClass.CONTENT_TYPE_XHTML:
m_UrlModule.HttpContentType(id, "application/xhtml+xml");
break;
case ScriptBaseClass.CONTENT_TYPE_ATOM:
m_UrlModule.HttpContentType(id, "application/atom+xml");
break;
case ScriptBaseClass.CONTENT_TYPE_JSON:
m_UrlModule.HttpContentType(id, "application/json");
break;
case ScriptBaseClass.CONTENT_TYPE_LLSD:
m_UrlModule.HttpContentType(id, "application/llsd+xml");
break;
case ScriptBaseClass.CONTENT_TYPE_FORM:
m_UrlModule.HttpContentType(id, "application/x-www-form-urlencoded");
break;
case ScriptBaseClass.CONTENT_TYPE_RSS:
m_UrlModule.HttpContentType(id, "application/rss+xml");
break;
default:
m_UrlModule.HttpContentType(id, "text/plain");
break;
}
}
public static void SetTexGen(SceneObjectPart part, int face,int style)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
MappingType textype;
textype = MappingType.Default;
if (style == ScriptBaseClass.PRIM_TEXGEN_PLANAR)
textype = MappingType.Planar;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
tex.CreateFace((uint) face);
tex.FaceTextures[face].TexMapType = textype;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (uint i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
tex.FaceTextures[i].TexMapType = textype;
}
tex.DefaultTexture.TexMapType = textype;
part.UpdateTextureEntry(tex);
return;
}
}
public static void SetGlow(SceneObjectPart part, int face, float glow)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
tex.CreateFace((uint) face);
tex.FaceTextures[face].Glow = glow;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (uint i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
tex.FaceTextures[i].Glow = glow;
}
tex.DefaultTexture.Glow = glow;
part.UpdateTextureEntry(tex);
return;
}
}
public static void SetShiny(SceneObjectPart part, int face, int shiny, Bumpiness bump)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
var sval = shiny switch
{
0 => Shininess.None,
1 => Shininess.Low,
2 => Shininess.Medium,
3 => Shininess.High,
_ => Shininess.None,
};
int nsides = GetNumberOfSides(part);
Primitive.TextureEntry tex = part.Shape.Textures;
if (face >= 0 && face < nsides)
{
tex.CreateFace((uint) face);
tex.FaceTextures[face].Shiny = sval;
tex.FaceTextures[face].Bump = bump;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (uint i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
{
tex.FaceTextures[i].Shiny = sval;
tex.FaceTextures[i].Bump = bump;
}
}
tex.DefaultTexture.Shiny = sval;
tex.DefaultTexture.Bump = bump;
part.UpdateTextureEntry(tex);
return;
}
}
public static void SetFullBright(SceneObjectPart part, int face, bool bright)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
int nsides = GetNumberOfSides(part);
Primitive.TextureEntry tex = part.Shape.Textures;
if (face >= 0 && face < nsides)
{
tex.CreateFace((uint) face);
tex.FaceTextures[face].Fullbright = bright;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
tex.DefaultTexture.Fullbright = bright;
for (uint i = 0; i < nsides; i++)
{
if(tex.FaceTextures[i] is not null)
tex.FaceTextures[i].Fullbright = bright;
}
part.UpdateTextureEntry(tex);
return;
}
}
public LSL_Float llGetAlpha(int face)
{
return GetAlpha(m_host, face);
}
protected static LSL_Float GetAlpha(SceneObjectPart part, int face)
{
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face == ScriptBaseClass.ALL_SIDES)
{
int i;
double sum = 0.0;
for (i = 0 ; i < nsides; i++)
sum += tex.GetFace((uint)i).RGBA.A;
return sum;
}
if (face >= 0 && face < nsides)
{
return tex.GetFace((uint)face).RGBA.A;
}
return 0.0;
}
public void llSetAlpha(double alpha, int face)
{
SetAlpha(m_host, alpha, face);
}
public void llSetLinkAlpha(int linknumber, double alpha, int face)
{
List parts = GetLinkParts(linknumber);
if (parts.Count > 0)
{
try
{
foreach (SceneObjectPart part in parts)
SetAlpha(part, alpha, face);
}
finally { }
}
}
protected static void SetAlpha(SceneObjectPart part, double alpha, int face)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
Color4 texcolor;
if (face >= 0 && face < nsides)
{
texcolor = tex.CreateFace((uint)face).RGBA;
texcolor.A = Utils.Clamp((float)alpha, 0.0f, 1.0f);
tex.FaceTextures[face].RGBA = texcolor;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (int i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
{
texcolor = tex.FaceTextures[i].RGBA;
texcolor.A = Utils.Clamp((float)alpha, 0.0f, 1.0f);
tex.FaceTextures[i].RGBA = texcolor;
}
}
// In some cases, the default texture can be null, eg when every face
// has a unique texture
if (tex.DefaultTexture is not null)
{
texcolor = tex.DefaultTexture.RGBA;
texcolor.A = Utils.Clamp((float)alpha, 0.0f, 1.0f);
tex.DefaultTexture.RGBA = texcolor;
}
part.UpdateTextureEntry(tex);
return;
}
}
///
/// Set flexi parameters of a part.
///
/// FIXME: Much of this code should probably be within the part itself.
///
///
///
///
///
///
///
///
///
protected static void SetFlexi(SceneObjectPart part, bool flexi, int softness, float gravity, float friction,
float wind, float tension, LSL_Vector Force)
{
if (part == null)
return;
SceneObjectGroup sog = part.ParentGroup;
if(sog == null || sog.IsDeleted || sog.inTransit)
return;
PrimitiveBaseShape pbs = part.Shape;
pbs.FlexiSoftness = softness;
pbs.FlexiGravity = gravity;
pbs.FlexiDrag = friction;
pbs.FlexiWind = wind;
pbs.FlexiTension = tension;
pbs.FlexiForceX = (float)Force.x;
pbs.FlexiForceY = (float)Force.y;
pbs.FlexiForceZ = (float)Force.z;
pbs.FlexiEntry = flexi;
if (!pbs.SculptEntry && (pbs.PathCurve == (byte)Extrusion.Straight || pbs.PathCurve == (byte)Extrusion.Flexible))
{
if(flexi)
{
pbs.PathCurve = (byte)Extrusion.Flexible;
if(!sog.IsPhantom)
{
sog.ScriptSetPhantomStatus(true);
return;
}
}
else
{
// Other values not set, they do not seem to be sent to the viewer
// Setting PathCurve appears to be what actually toggles the check box and turns Flexi on and off
pbs.PathCurve = (byte)Extrusion.Straight;
}
}
part.ParentGroup.HasGroupChanged = true;
part.ScheduleFullUpdate();
}
///
/// Set a light point on a part
///
/// FIXME: Much of this code should probably be in SceneObjectGroup
///
///
///
///
///
///
///
protected static void SetPointLight(SceneObjectPart part, bool light, LSL_Vector color, float intensity, float radius, float falloff)
{
if (part == null || part.ParentGroup == null || part.ParentGroup.IsDeleted)
return;
PrimitiveBaseShape pbs = part.Shape;
if (light)
{
pbs.LightEntry = true;
pbs.LightColorR = Utils.Clamp((float)color.x, 0.0f, 1.0f);
pbs.LightColorG = Utils.Clamp((float)color.y, 0.0f, 1.0f);
pbs.LightColorB = Utils.Clamp((float)color.z, 0.0f, 1.0f);
pbs.LightIntensity = Utils.Clamp(intensity, 0.0f, 1.0f);
pbs.LightRadius = Utils.Clamp(radius, 0.1f, 20.0f);
pbs.LightFalloff = Utils.Clamp(falloff, 0.01f, 2.0f);
}
else
{
pbs.LightEntry = false;
}
part.ParentGroup.HasGroupChanged = true;
part.ScheduleFullUpdate();
}
public LSL_Vector llGetColor(int face)
{
return GetColor(m_host, face);
}
public LSL_Vector GetColor(SceneObjectPart part, int face)
{
Primitive.TextureEntry tex = part.Shape.Textures;
Color4 texcolor;
LSL_Vector rgb = new();
int nsides = GetNumberOfSides(part);
if (face == ScriptBaseClass.ALL_SIDES)
{
int i;
for (i = 0; i < nsides; i++)
{
texcolor = tex.GetFace((uint)i).RGBA;
rgb.x += texcolor.R;
rgb.y += texcolor.G;
rgb.z += texcolor.B;
}
float invnsides = 1.0f / (float)nsides;
rgb.x *= invnsides;
rgb.y *= invnsides;
rgb.z *= invnsides;
return rgb;
}
if (face >= 0 && face < nsides)
{
texcolor = tex.GetFace((uint)face).RGBA;
rgb.x = texcolor.R;
rgb.y = texcolor.G;
rgb.z = texcolor.B;
return rgb;
}
return LSL_Vector.Zero;
}
public void llSetTexture(string texture, int face)
{
SetTexture(m_host, texture, face);
ScriptSleep(m_sleepMsOnSetTexture);
}
public void llSetLinkTexture(int linknumber, string texture, int face)
{
List parts = GetLinkParts(linknumber);
if (parts.Count > 0)
{
try
{
foreach (SceneObjectPart part in parts)
SetTexture(part, texture, face);
}
finally { }
}
ScriptSleep(m_sleepMsOnSetLinkTexture);
}
protected void SetTextureParams(SceneObjectPart part, string texture, double scaleU, double ScaleV,
double offsetU, double offsetV, double rotation, int face)
{
if (part == null || part.ParentGroup == null || part.ParentGroup.IsDeleted)
return;
UUID textureID = UUID.Zero;
bool dotexture = false;
if(!string.IsNullOrEmpty(texture) && texture != ScriptBaseClass.NULL_KEY)
{
textureID = ScriptUtils.GetAssetIdFromItemName(m_host, texture, (int)AssetType.Texture);
dotexture = textureID.IsNotZero() ||
(UUID.TryParse(texture, out textureID) && textureID.IsNotZero());
}
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface = tex.CreateFace((uint)face);
if (dotexture)
texface.TextureID = textureID;
texface.RepeatU = (float)scaleU;
texface.RepeatV = (float)ScaleV;
texface.OffsetU = (float)offsetU;
texface.OffsetV = (float)offsetV;
texface.Rotation = (float)rotation;
tex.FaceTextures[face] = texface;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (uint i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] != null)
{
if (dotexture)
tex.FaceTextures[i].TextureID = textureID;
tex.FaceTextures[i].RepeatU = (float)scaleU;
tex.FaceTextures[i].RepeatV = (float)ScaleV;
tex.FaceTextures[i].OffsetU = (float)offsetU;
tex.FaceTextures[i].OffsetV = (float)offsetV;
tex.FaceTextures[i].Rotation = (float)rotation;
}
}
if (dotexture)
tex.DefaultTexture.TextureID = textureID;
tex.DefaultTexture.RepeatU = (float)scaleU;
tex.DefaultTexture.RepeatV = (float)ScaleV;
tex.DefaultTexture.OffsetU = (float)offsetU;
tex.DefaultTexture.OffsetV = (float)offsetV;
tex.DefaultTexture.Rotation = (float)rotation;
part.UpdateTextureEntry(tex);
return;
}
}
protected void SetTexture(SceneObjectPart part, string texture, int face)
{
if (part == null || part.ParentGroup == null || part.ParentGroup.IsDeleted)
return;
UUID textureID = ScriptUtils.GetAssetIdFromItemName(m_host, texture, (int)AssetType.Texture);
if (textureID.IsZero())
{
if (!UUID.TryParse(texture, out textureID) || textureID.IsZero())
return;
}
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface = tex.CreateFace((uint)face);
texface.TextureID = textureID;
tex.FaceTextures[face] = texface;
part.UpdateTextureEntry(tex);
return;
}
else if (face == ScriptBaseClass.ALL_SIDES)
{
for (uint i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] != null)
{
tex.FaceTextures[i].TextureID = textureID;
}
}
tex.DefaultTexture.TextureID = textureID;
part.UpdateTextureEntry(tex);
return;
}
}
public void llScaleTexture(double u, double v, int face)
{
ScaleTexture(m_host, u, v, face);
ScriptSleep(m_sleepMsOnScaleTexture);
}
protected static void ScaleTexture(SceneObjectPart part, double u, double v, int face)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface = tex.CreateFace((uint)face);
texface.RepeatU = (float)u;
texface.RepeatV = (float)v;
tex.FaceTextures[face] = texface;
part.UpdateTextureEntry(tex);
return;
}
if (face == ScriptBaseClass.ALL_SIDES)
{
for (int i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
{
tex.FaceTextures[i].RepeatU = (float)u;
tex.FaceTextures[i].RepeatV = (float)v;
}
}
tex.DefaultTexture.RepeatU = (float)u;
tex.DefaultTexture.RepeatV = (float)v;
part.UpdateTextureEntry(tex);
return;
}
}
public void llOffsetTexture(double u, double v, int face)
{
OffsetTexture(m_host, u, v, face);
ScriptSleep(m_sleepMsOnOffsetTexture);
}
protected static void OffsetTexture(SceneObjectPart part, double u, double v, int face)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface = tex.CreateFace((uint)face);
texface.OffsetU = (float)u;
texface.OffsetV = (float)v;
tex.FaceTextures[face] = texface;
part.UpdateTextureEntry(tex);
return;
}
if (face == ScriptBaseClass.ALL_SIDES)
{
for (int i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
{
tex.FaceTextures[i].OffsetU = (float)u;
tex.FaceTextures[i].OffsetV = (float)v;
}
}
tex.DefaultTexture.OffsetU = (float)u;
tex.DefaultTexture.OffsetV = (float)v;
part.UpdateTextureEntry(tex);
return;
}
}
public void llRotateTexture(double rotation, int face)
{
RotateTexture(m_host, rotation, face);
ScriptSleep(m_sleepMsOnRotateTexture);
}
protected static void RotateTexture(SceneObjectPart part, double rotation, int face)
{
if (part is null || part.ParentGroup is null || part.ParentGroup.IsDeleted)
return;
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface = tex.CreateFace((uint)face);
texface.Rotation = (float)rotation;
tex.FaceTextures[face] = texface;
part.UpdateTextureEntry(tex);
return;
}
if (face == ScriptBaseClass.ALL_SIDES)
{
for (int i = 0; i < nsides; i++)
{
if (tex.FaceTextures[i] is not null)
{
tex.FaceTextures[i].Rotation = (float)rotation;
}
}
tex.DefaultTexture.Rotation = (float)rotation;
part.UpdateTextureEntry(tex);
return;
}
}
public LSL_String llGetTexture(int face)
{
return GetTexture(m_host, face);
}
protected static LSL_String GetTexture(SceneObjectPart part, int face)
{
Primitive.TextureEntry tex = part.Shape.Textures;
int nsides = GetNumberOfSides(part);
if (face == ScriptBaseClass.ALL_SIDES)
{
face = 0;
}
if (face >= 0 && face < nsides)
{
Primitive.TextureEntryFace texface;
texface = tex.GetFace((uint)face);
string texture = texface.TextureID.ToString();
lock (part.TaskInventory)
{
foreach (KeyValuePair inv in part.TaskInventory)
{
if (inv.Value.AssetID.Equals(texface.TextureID))
{
texture = inv.Value.Name.ToString();
break;
}
}
}
return texture;
}
else
{
return ScriptBaseClass.NULL_KEY;
}
}
public void llSetPos(LSL_Vector pos)
{
SetPos(m_host, pos, true);
ScriptSleep(m_sleepMsOnSetPos);
}
///
/// Tries to move the entire object so that the root prim is within 0.1m of position. http://wiki.secondlife.com/wiki/LlSetRegionPos
/// Documentation indicates that the use of x/y coordinates up to 10 meters outside the bounds of a region will work but do not specify what happens if there is no adjacent region for the object to move into.
/// Uses the RegionSize constant here rather than hard-coding 266.0 to alert any developer modifying OpenSim to support variable-sized regions that this method will need tweaking.
///
///
/// 1 if successful, 0 otherwise.
public LSL_Integer llSetRegionPos(LSL_Vector pos)
{
// BEGIN WORKAROUND
// IF YOU GET REGION CROSSINGS WORKING WITH THIS FUNCTION, REPLACE THE WORKAROUND.
//
// This workaround is to prevent silent failure of this function.
// According to the specification on the SL Wiki, providing a position outside of the
if (pos.x < 0 || pos.x > World.RegionInfo.RegionSizeX || pos.y < 0 || pos.y > World.RegionInfo.RegionSizeY)
{
return 0;
}
// END WORK AROUND
else if ( // this is not part of the workaround if-block because it's not related to the workaround.
IsPhysical() ||
m_host.ParentGroup.IsAttachment || // return FALSE if attachment
(
pos.x < -10.0 || // return FALSE if more than 10 meters into a west-adjacent region.
pos.x > (World.RegionInfo.RegionSizeX + 10) || // return FALSE if more than 10 meters into a east-adjacent region.
pos.y < -10.0 || // return FALSE if more than 10 meters into a south-adjacent region.
pos.y > (World.RegionInfo.RegionSizeY + 10) || // return FALSE if more than 10 meters into a north-adjacent region.
pos.z > Constants.RegionHeight // return FALSE if altitude than 4096m
)
)
{
return 0;
}
// if we reach this point, then the object is not physical, it's not an attachment, and the destination is within the valid range.
// this could possibly be done in the above else-if block, but we're doing the check here to keep the code easier to read.
Vector3 objectPos = m_host.ParentGroup.RootPart.AbsolutePosition;
LandData here = World.GetLandData(objectPos);
LandData there = World.GetLandData(pos);
// we're only checking prim limits if it's moving to a different parcel under the assumption that if the object got onto the parcel without exceeding the prim limits.
bool sameParcel = here.GlobalID.Equals(there.GlobalID);
if (!sameParcel && !World.Permissions.CanRezObject(
m_host.ParentGroup.PrimCount, m_host.ParentGroup.OwnerID, pos))
{
return 0;
}
SetPos(m_host.ParentGroup.RootPart, pos, false);
return VecDistSquare(pos, llGetRootPosition()) <= 0.01 ? 1 : 0;
}
// Capped movemment if distance > 10m (http://wiki.secondlife.com/wiki/LlSetPos)
// note linked setpos is capped "differently"
private LSL_Vector SetPosAdjust(LSL_Vector start, LSL_Vector end)
{
if (VecDistSquare(start, end) > m_Script10mDistanceSquare)
return start + m_Script10mDistance * llVecNorm(end - start);
else
return end;
}
protected LSL_Vector GetSetPosTarget(SceneObjectPart part, LSL_Vector targetPos, LSL_Vector fromPos, bool adjust)
{
if (part == null)
return targetPos;
SceneObjectGroup grp = part.ParentGroup;
if (grp == null || grp.IsDeleted || grp.inTransit)
return targetPos;
if (adjust)
targetPos = SetPosAdjust(fromPos, targetPos);
if (m_disable_underground_movement && grp.AttachmentPoint == 0)
{
if (part.IsRoot)
{
float ground = World.GetGroundHeight((float)targetPos.x, (float)targetPos.y);
if ((targetPos.z < ground))
targetPos.z = ground;
}
}
return targetPos;
}
///
/// set object position, optionally capping the distance.
///
///
///
/// if TRUE, will cap the distance to 10m.
protected void SetPos(SceneObjectPart part, LSL_Vector targetPos, bool adjust)
{
if (part == null)
return;
SceneObjectGroup grp = part.ParentGroup;
if (grp == null || grp.IsDeleted || grp.inTransit)
return;
LSL_Vector currentPos = GetPartLocalPos(part);
LSL_Vector toPos = GetSetPosTarget(part, targetPos, currentPos, adjust);
if (part.IsRoot)
{
if (!grp.IsAttachment && !World.Permissions.CanObjectEntry(grp, false, (Vector3)toPos))
return;
grp.UpdateGroupPosition((Vector3)toPos);
}
else
{
part.OffsetPosition = (Vector3)toPos;
// SceneObjectGroup parent = part.ParentGroup;
// parent.HasGroupChanged = true;
// parent.ScheduleGroupForTerseUpdate();
part.ScheduleTerseUpdate();
}
}
public LSL_Vector llGetPos()
{
return m_host.GetWorldPosition();
}
public LSL_Vector llGetLocalPos()
{
return GetPartLocalPos(m_host);
}
protected static LSL_Vector GetPartLocalPos(SceneObjectPart part)
{
if (part.IsRoot)
{
if (part.ParentGroup.IsAttachment)
return new LSL_Vector(part.AttachedPos);
return new LSL_Vector(part.AbsolutePosition);
}
return new LSL_Vector(part.OffsetPosition);
}
public void llSetRot(LSL_Rotation rot)
{
// try to let this work as in SL...
if (m_host.ParentID == 0 || (m_host.ParentGroup != null && m_host == m_host.ParentGroup.RootPart))
{
// special case: If we are root, rotate complete SOG to new rotation
SetRot(m_host, rot);
}
else
{
// we are a child. The rotation values will be set to the one of root modified by rot, as in SL. Don't ask.
SceneObjectPart rootPart = m_host.ParentGroup.RootPart;
if (rootPart != null) // better safe than sorry
{
SetRot(m_host, rootPart.RotationOffset * (Quaternion)rot);
}
}
ScriptSleep(m_sleepMsOnSetRot);
}
public void llSetLocalRot(LSL_Rotation rot)
{
SetRot(m_host, rot);
ScriptSleep(m_sleepMsOnSetLocalRot);
}
protected static void SetRot(SceneObjectPart part, Quaternion rot)
{
if (part == null || part.ParentGroup == null || part.ParentGroup.IsDeleted)
return;
bool isroot = (part == part.ParentGroup.RootPart);
bool isphys;
PhysicsActor pa = part.PhysActor;
// keep using physactor ideia of isphysical
// it should be SOP ideia of that
// not much of a issue with ubOde
if (pa != null && pa.IsPhysical)
isphys = true;
else
isphys = false;
// SL doesn't let scripts rotate root of physical linksets
if (isroot && isphys)
return;
part.UpdateRotation(rot);
// Update rotation does not move the object in the physics engine if it's a non physical linkset
// so do a nasty update of parts positions if is a root part rotation
if (isroot && pa != null) // with if above implies non physical root part
{
part.ParentGroup.ResetChildPrimPhysicsPositions();
}
else // fix sitting avatars. This is only needed bc of how we link avas to child parts, not root part
{
// List sittingavas = part.ParentGroup.GetLinkedAvatars();
List sittingavas = part.ParentGroup.GetSittingAvatars();
if (sittingavas.Count > 0)
{
foreach (ScenePresence av in sittingavas)
{
if (isroot || part.LocalId == av.ParentID)
av.SendTerseUpdateToAllClients();
}
}
}
}
///
/// See http://lslwiki.net/lslwiki/wakka.php?wakka=ChildRotation
///
public LSL_Rotation llGetRot()
{
// unlinked or root prim then use llRootRotation
// see llRootRotaion for references.
if (m_host.LinkNum == 0 || m_host.LinkNum == 1)
{
return llGetRootRotation();
}
Quaternion q = m_host.GetWorldRotation();
if (m_host.ParentGroup != null && m_host.ParentGroup.AttachmentPoint != 0)
{
ScenePresence avatar = World.GetScenePresence(m_host.ParentGroup.AttachedAvatar);
if (avatar != null)
{
if ((avatar.AgentControlFlags & (uint)AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) != 0)
q = avatar.CameraRotation * q; // Mouselook
else
q = avatar.Rotation * q; // Currently infrequently updated so may be inaccurate
}
}
return new LSL_Rotation(q.X, q.Y, q.Z, q.W);
}
private LSL_Rotation GetPartRot(SceneObjectPart part)
{
Quaternion q;
if (part.LinkNum == 0 || part.LinkNum == 1) // unlinked or root prim
{
if (part.ParentGroup.AttachmentPoint != 0)
{
ScenePresence avatar = World.GetScenePresence(part.ParentGroup.AttachedAvatar);
if (avatar != null)
{
if ((avatar.AgentControlFlags & (uint)AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) != 0)
q = avatar.CameraRotation; // Mouselook
else
q = avatar.GetWorldRotation(); // Currently infrequently updated so may be inaccurate
}
else
q = part.ParentGroup.GroupRotation; // Likely never get here but just in case
}
else
q = part.ParentGroup.GroupRotation; // just the group rotation
return new LSL_Rotation(q);
}
q = part.GetWorldRotation();
if (part.ParentGroup.AttachmentPoint != 0)
{
ScenePresence avatar = World.GetScenePresence(part.ParentGroup.AttachedAvatar);
if (avatar != null)
{
if ((avatar.AgentControlFlags & (uint)AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK) != 0)
q = avatar.CameraRotation * q; // Mouselook
else
q = avatar.Rotation * q; // Currently infrequently updated so may be inaccurate
}
}
return new LSL_Rotation(q.X, q.Y, q.Z, q.W);
}
public LSL_Rotation llGetLocalRot()
{
return GetPartLocalRot(m_host);
}
private static LSL_Rotation GetPartLocalRot(SceneObjectPart part)
{
return new LSL_Rotation(part.RotationOffset);
}
public void llSetForce(LSL_Vector force, int local)
{
if (!m_host.ParentGroup.IsDeleted)
{
if (local != 0)
force *= llGetRot();
m_host.ParentGroup.RootPart.SetForce(force);
}
}
public LSL_Vector llGetForce()
{
if (!m_host.ParentGroup.IsDeleted)
return m_host.ParentGroup.RootPart.GetForce();
return LSL_Vector.Zero;
}
public void llSetVelocity(LSL_Vector vel, int local)
{
m_host.SetVelocity(new Vector3((float)vel.x, (float)vel.y, (float)vel.z), local != 0);
}
public void llSetAngularVelocity(LSL_Vector avel, int local)
{
m_host.SetAngularVelocity(new Vector3((float)avel.x, (float)avel.y, (float)avel.z), local != 0);
}
public LSL_Integer llTarget(LSL_Vector position, double range)
{
return m_host.ParentGroup.RegisterTargetWaypoint(m_item.ItemID, position, (float)range);
}
public void llTargetRemove(int number)
{
m_host.ParentGroup.UnregisterTargetWaypoint(number);
}
public LSL_Integer llRotTarget(LSL_Rotation rot, double error)
{
return m_host.ParentGroup.RegisterRotTargetWaypoint(m_item.ItemID, rot, (float)error);
}
public void llRotTargetRemove(int number)
{
m_host.ParentGroup.UnRegisterRotTargetWaypoint(number);
}
public void llMoveToTarget(LSL_Vector target, double tau)
{
m_host.ParentGroup.MoveToTarget(target, (float)tau);
}
public void llStopMoveToTarget()
{
m_host.ParentGroup.StopMoveToTarget();
}
public void llApplyImpulse(LSL_Vector force, LSL_Integer local)
{
//No energy force yet
Vector3 v = force;
if (v.Length() > 20000.0f)
{
v.Normalize();
v *= 20000.0f;
}
m_host.ApplyImpulse(v, local != 0);
}
public void llApplyRotationalImpulse(LSL_Vector force, int local)
{
m_host.ParentGroup.RootPart.ApplyAngularImpulse(force, local != 0);
}
public void llSetTorque(LSL_Vector torque, int local)
{
m_host.ParentGroup.RootPart.SetAngularImpulse(torque, local != 0);
}
public LSL_Vector llGetTorque()
{
return new LSL_Vector(m_host.ParentGroup.GetTorque());
}
public void llSetForceAndTorque(LSL_Vector force, LSL_Vector torque, int local)
{
llSetForce(force, local);
llSetTorque(torque, local);
}
public LSL_Vector llGetVel()
{
Vector3 vel = Vector3.Zero;
if (m_host.ParentGroup.IsAttachment)
{
ScenePresence avatar = m_host.ParentGroup.Scene.GetScenePresence(m_host.ParentGroup.AttachedAvatar);
if (avatar != null)
vel = avatar.GetWorldVelocity();
}
else
{
vel = m_host.ParentGroup.RootPart.Velocity;
}
return new LSL_Vector(vel);
}
public LSL_Vector llGetAccel()
{
return new LSL_Vector(m_host.Acceleration);
}
public LSL_Vector llGetOmega()
{
Vector3 avel = m_host.AngularVelocity;
return new LSL_Vector(avel.X, avel.Y, avel.Z);
}
public LSL_Float llGetTimeOfDay()
{
return (double)((DateTime.Now.TimeOfDay.TotalMilliseconds / 1000) % (3600 * 4));
}
public LSL_Float llGetWallclock()
{
return DateTime.Now.TimeOfDay.TotalSeconds;
}
public LSL_Float llGetTime()
{
double ScriptTime = Util.GetTimeStampMS() - m_timer;
return (float)Math.Round((ScriptTime / 1000.0), 3);
}
public void llResetTime()
{
m_timer = Util.GetTimeStampMS();
}
public LSL_Float llGetAndResetTime()
{
double now = Util.GetTimeStampMS();
double ScriptTime = now - m_timer;
m_timer = now;
return (float)Math.Round((ScriptTime / 1000.0), 3);
}
public void llSound(string sound, double volume, int queue, int loop)
{
Deprecated("llSound", "Use llPlaySound instead");
}
// Xantor 20080528 PlaySound updated so it accepts an objectinventory name -or- a key to a sound
// 20080530 Updated to remove code duplication
public void llPlaySound(string sound, double volume)
{
if (m_SoundModule == null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsNotZero())
m_SoundModule.SendSound(m_host, soundID, volume, false, 0, false, false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void llLinkPlaySound(LSL_Integer linknumber, string sound, double volume)
{
llLinkPlaySound(linknumber, sound, volume, 0);
}
public void llLinkPlaySound(LSL_Integer linknumber, string sound, double volume, LSL_Integer flags)
{
if (m_SoundModule is null)
return;
if (m_host.ParentGroup is null || m_host.ParentGroup.IsDeleted)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if (soundID.IsZero())
return;
List parts = GetLinkParts(m_host, linknumber.value);
if (parts.Count == 0)
return;
switch (flags)
{
case ScriptBaseClass.SOUND_PLAY: // play
foreach (SceneObjectPart sop in parts)
m_SoundModule.SendSound(sop, soundID, volume, false, 0, false, false);
break;
case ScriptBaseClass.SOUND_LOOP: // loop
foreach (SceneObjectPart sop in parts)
m_SoundModule.LoopSound(sop, soundID, volume, false, false);
break;
case ScriptBaseClass.SOUND_TRIGGER: //trigger
foreach (SceneObjectPart sop in parts)
m_SoundModule.SendSound(sop, soundID, volume, true, 0, false, false);
break;
case ScriptBaseClass.SOUND_SYNC: // play slave
foreach (SceneObjectPart sop in parts)
m_SoundModule.SendSound(sop, soundID, volume, false, 0, true, false);
break;
case ScriptBaseClass.SOUND_SYNC | ScriptBaseClass.SOUND_LOOP: // loop slave
foreach (SceneObjectPart sop in parts)
m_SoundModule.LoopSound(sop, soundID, volume, false, true);
break;
}
}
public void llLoopSound(string sound, double volume)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
m_SoundModule.LoopSound(m_host, soundID, volume, false, false);
}
public void llLoopSoundMaster(string sound, double volume)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
m_SoundModule.LoopSound(m_host, soundID, volume, true, false);
}
public void llLoopSoundSlave(string sound, double volume)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
m_SoundModule.LoopSound(m_host, soundID, volume, false, true);
}
public void llPlaySoundSlave(string sound, double volume)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
// send the sound, once, to all clients in range
m_SoundModule.SendSound(m_host, soundID, volume, false, 0, true, false);
}
public void llTriggerSound(string sound, double volume)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
// send the sound, once, to all clients in rangeTrigger or play an attached sound in this part's inventory.
m_SoundModule.SendSound(m_host, soundID, volume, true, 0, false, false);
}
public void llStopSound()
{
m_SoundModule?.StopSound(m_host);
}
public void llLinkStopSound(LSL_Integer linknumber)
{
if (m_SoundModule is not null)
{
foreach(SceneObjectPart sop in GetLinkParts(linknumber))
m_SoundModule.StopSound(sop);
}
}
public void llPreloadSound(string sound)
{
if (m_SoundModule is null)
return;
UUID soundID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, sound, AssetType.Sound);
if(soundID.IsZero())
return;
m_SoundModule.PreloadSound(m_host, soundID);
ScriptSleep(m_sleepMsOnPreloadSound);
}
///
/// Return a portion of the designated string bounded by
/// inclusive indices (start and end). As usual, the negative
/// indices, and the tolerance for out-of-bound values, makes
/// this more complicated than it might otherwise seem.
///
public LSL_String llGetSubString(string src, int start, int end)
{
// 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 (end < 0)
{
end = src.Length+end;
}
// Conventional substring
if (start <= end)
{
// Implies both bounds are out-of-range.
if (end < 0 || start >= src.Length)
{
return String.Empty;
}
// If end is positive, then it directly
// corresponds to the lengt of the substring
// needed (plus one of course). BUT, it
// must be within bounds.
if (end >= src.Length)
{
end = src.Length-1;
}
if (start < 0)
{
return src[..(end + 1)];
}
// Both indices are positive
return src[start..(end + 1)];
}
// Inverted substring (end < start)
else
{
// Implies both indices are below the
// lower bound. In the inverted case, that
// means the entire string will be returned
// unchanged.
if (start < 0)
{
return src;
}
// If both indices are greater than the upper
// bound the result may seem initially counter
// intuitive.
if (end >= src.Length)
{
return src;
}
if (end < 0)
{
if (start < src.Length)
{
return src[start..];
}
else
{
return String.Empty;
}
}
else
{
if (start < src.Length)
{
return src[..(end + 1)] + src[start..];
}
else
{
return src[..(end + 1)];
}
}
}
}
///
/// Delete substring removes the specified substring bounded
/// by the inclusive indices start and end. Indices may be
/// negative (indicating end-relative) and may be inverted,
/// i.e. end < start.
///
public LSL_String llDeleteSubString(string src, int start, int end)
{
// 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 (end < 0)
{
end = src.Length+end;
}
// Conventionally delimited substring
if (start <= end)
{
// If both bounds are outside of the existing
// string, then return unchanged.
if (end < 0 || start >= src.Length)
{
return src;
}
// At least one bound is in-range, so we
// need to clip the out-of-bound argument.
if (start < 0)
{
start = 0;
}
if (end >= src.Length)
{
end = src.Length-1;
}
return src.Remove(start,end-start+1);
}
// Inverted substring
else
{
// In this case, out of bounds means that
// the existing string is part of the cut.
if (start < 0 || end >= src.Length)
{
return String.Empty;
}
if (end > 0)
{
if (start < src.Length)
{
return src.Remove(start).Remove(0, end + 1);
}
else
{
return src.Remove(0, end + 1);
}
}
else
{
if (start < src.Length)
{
return src.Remove(start);
}
else
{
return src;
}
}
}
}
///
/// Insert string inserts the specified string identified by src
/// at the index indicated by index. Index may be negative, in
/// which case it is end-relative. The index may exceed either
/// string bound, with the result being a concatenation.
///
// this is actually wrong. according to SL wiki, this function should not support negative indexes.
public LSL_String llInsertString(string dest, int index, string src)
{
// 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.
char c;
if (index < 0)
{
index = dest.Length+index;
// Negative now means it is less than the lower
// bound of the string.
if(index > 0)
{
c = dest[index];
if (c >= 0xDC00 && c <= 0xDFFF)
--index;
}
if (index < 0)
{
return src+dest;
}
}
else
{
c = dest[index];
if (c >= 0xDC00 && c <= 0xDFFF)
++index;
}
if (index >= dest.Length)
{
return dest + src;
}
// The index is in bounds.
// In this case the index refers to the index that will
// be assigned to the first character of the inserted string.
// So unlike the other string operations, we do not add one
// to get the correct string length.
return dest[..index] + src + dest[index..];
}
public LSL_String llToUpper(string src)
{
return src.ToUpper();
}
public LSL_String llToLower(string src)
{
return src.ToLower();
}
public LSL_Integer llGiveMoney(LSL_Key destination, LSL_Integer amount)
{
if (m_item.PermsGranter.IsZero())
return 0;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_DEBIT) == 0)
{
Error("llGiveMoney", "No permissions to give money");
return 0;
}
if (!UUID.TryParse(destination, out UUID toID))
{
Error("llGiveMoney", "Bad key in llGiveMoney");
return 0;
}
IMoneyModule money = World.RequestModuleInterface();
if (money is null)
{
NotImplemented("llGiveMoney");
return 0;
}
void act(string _)
{
money.ObjectGiveMoney(m_host.ParentGroup.RootPart.UUID, m_host.ParentGroup.RootPart.OwnerID,
toID, amount, UUID.Zero, out _);
}
m_AsyncCommands.DataserverPlugin.RegisterRequest(m_host.LocalId, m_item.ItemID, act);
return 0;
}
public void llMakeExplosion(int particles, double scale, double vel, double lifetime, double arc, string texture, LSL_Vector offset)
{
Deprecated("llMakeExplosion", "Use llParticleSystem instead");
ScriptSleep(m_sleepMsOnMakeExplosion);
}
public void llMakeFountain(int particles, double scale, double vel, double lifetime, double arc, int bounce, string texture, LSL_Vector offset, double bounce_offset)
{
Deprecated("llMakeFountain", "Use llParticleSystem instead");
ScriptSleep(m_sleepMsOnMakeFountain);
}
public void llMakeSmoke(int particles, double scale, double vel, double lifetime, double arc, string texture, LSL_Vector offset)
{
Deprecated("llMakeSmoke", "Use llParticleSystem instead");
ScriptSleep(m_sleepMsOnMakeSmoke);
}
public void llMakeFire(int particles, double scale, double vel, double lifetime, double arc, string texture, LSL_Vector offset)
{
Deprecated("llMakeFire", "Use llParticleSystem instead");
ScriptSleep(m_sleepMsOnMakeFire);
}
public void llRezAtRoot(string inventory, LSL_Vector pos, LSL_Vector vel, LSL_Rotation rot, int param)
{
doObjectRez(inventory, pos, vel, rot, param, true);
}
public void doObjectRez(string inventory, LSL_Vector pos, LSL_Vector vel, LSL_Rotation rot, int param, bool atRoot)
{
if (string.IsNullOrEmpty(inventory) || Double.IsNaN(rot.x) || Double.IsNaN(rot.y) || Double.IsNaN(rot.z) || Double.IsNaN(rot.s))
return;
if (VecDistSquare(llGetPos(), pos) > m_Script10mDistanceSquare)
return;
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(inventory);
if (item == null)
{
Error("llRez(AtRoot/Object)", "Can't find object '" + inventory + "'");
return;
}
if (item.InvType != (int)InventoryType.Object)
{
Error("llRez(AtRoot/Object)", "Can't create requested object; object is missing from database");
return;
}
Util.FireAndForget(x =>
{
Quaternion wrot = rot;
wrot.Normalize();
List new_groups = World.RezObject(m_host, item, pos, wrot, vel, param, atRoot);
// If either of these are null, then there was an unknown error.
if (new_groups == null)
return;
bool notAttachment = !m_host.ParentGroup.IsAttachment;
foreach (SceneObjectGroup group in new_groups)
{
// objects rezzed with this method are die_at_edge by default.
group.RootPart.SetDieAtEdge(true);
group.ResumeScripts();
m_ScriptEngine.PostObjectEvent(m_host.LocalId, new EventParams(
"object_rez", new Object[] {
new LSL_String(
group.RootPart.UUID.ToString()) },
Array.Empty()));
if (notAttachment)
{
float groupmass = group.GetMass();
PhysicsActor pa = group.RootPart.PhysActor;
//Recoil.
if (pa != null && pa.IsPhysical && !((Vector3)vel).IsZero())
{
Vector3 recoil = -vel * groupmass * m_recoilScaleFactor;
if (!recoil.IsZero())
{
llApplyImpulse(recoil, 0);
}
}
}
}
}, null, "LSL_Api.doObjectRez");
//ScriptSleep((int)((groupmass * velmag) / 10));
ScriptSleep(m_sleepMsOnRezAtRoot);
}
public void llRezObject(string inventory, LSL_Vector pos, LSL_Vector vel, LSL_Rotation rot, int param)
{
doObjectRez(inventory, pos, vel, rot, param, false);
}
public void llLookAt(LSL_Vector target, double strength, double damping)
{
SceneObjectGroup sog = m_host.ParentGroup;
if (sog is null || sog.IsDeleted)
return;
// Get the normalized vector to the target
LSL_Vector from = llGetPos();
// normalized direction to target
LSL_Vector dir = llVecNorm(target - from);
LSL_Vector left = new(-dir.y, dir.x, 0.0f);
left = llVecNorm(left);
// make up orthogonal to left and dir
LSL_Vector up = LSL_Vector.Cross(dir, left);
// compute rotation based on orthogonal axes
// and rotate so Z points to target with X below horizont
LSL_Rotation rot = new LSL_Rotation(0.0, 0.707107, 0.0, 0.707107) * llAxes2Rot(dir, left, up);
if (!sog.UsesPhysics || sog.IsAttachment)
{
// Do nothing if either value is 0 (this has been checked in SL)
if (strength <= 0.0 || damping <= 0.0)
return;
llSetLocalRot(rot);
}
else
{
if (strength == 0)
{
llSetLocalRot(rot);
return;
}
sog.StartLookAt(rot, (float)strength, (float)damping);
}
}
public void llStopLookAt()
{
m_host.StopLookAt();
}
public void llSetTimerEvent(double sec)
{
if (sec != 0.0 && sec < m_MinTimerInterval)
sec = m_MinTimerInterval;
// Setting timer repeat
m_AsyncCommands.TimerPlugin.SetTimerEvent(m_host.LocalId, m_item.ItemID, sec);
}
public virtual void llSleep(double sec)
{
// m_log.Info("llSleep snoozing " + sec + "s.");
Sleep((int)(sec * 1000));
}
public LSL_Float llGetMass()
{
if (m_host.ParentGroup.IsAttachment)
{
ScenePresence attachedAvatar = World.GetScenePresence(m_host.ParentGroup.AttachedAvatar);
return attachedAvatar is null ? 0 : attachedAvatar.GetMass();
}
else
return m_host.ParentGroup.GetMass();
}
public LSL_Float llGetMassMKS()
{
return 100f * llGetMass();
}
public void llCollisionFilter(LSL_String name, LSL_Key id, LSL_Integer accept)
{
_ = UUID.TryParse(id, out UUID objectID);
if(objectID.IsZero())
m_host.SetCollisionFilter(accept != 0, name.m_string.ToLower(CultureInfo.InvariantCulture), string.Empty);
else
m_host.SetCollisionFilter(accept != 0, name.m_string.ToLower(CultureInfo.InvariantCulture), objectID.ToString());
}
public void llTakeControls(int controls, int accept, int pass_on)
{
if (!m_item.PermsGranter.IsZero())
{
ScenePresence presence = World.GetScenePresence(m_item.PermsGranter);
if (presence != null)
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)
{
presence.RegisterControlEventsToScript(controls, accept, pass_on, m_host.LocalId, m_item.ItemID);
}
}
}
}
public void llReleaseControls()
{
if (!m_item.PermsGranter.IsZero())
{
ScenePresence presence = World.GetScenePresence(m_item.PermsGranter);
if (presence != null)
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)
{
// Unregister controls from Presence
presence.UnRegisterControlEventsToScript(m_host.LocalId, m_item.ItemID);
// Remove Take Control permission.
m_item.PermsMask &= ~ScriptBaseClass.PERMISSION_TAKE_CONTROLS;
}
}
}
}
public void llReleaseURL(string url)
{
m_UrlModule?.ReleaseURL(url);
}
///
/// Attach the object containing this script to the avatar that owns it.
///
///
/// The attachment point (e.g. ATTACH_CHEST)
///
/// true if the attach suceeded, false if it did not
public bool AttachToAvatar(int attachmentPoint)
{
SceneObjectGroup grp = m_host.ParentGroup;
ScenePresence presence = World.GetScenePresence(m_host.OwnerID);
IAttachmentsModule attachmentsModule = m_ScriptEngine.World.AttachmentsModule;
if (attachmentsModule != null)
return attachmentsModule.AttachObject(presence, grp, (uint)attachmentPoint, false, true, true);
else
return false;
}
///
/// Detach the object containing this script from the avatar it is attached to.
///
///
/// Nothing happens if the object is not attached.
///
public void DetachFromAvatar()
{
Util.FireAndForget(DetachWrapper, m_host, "LSL_Api.DetachFromAvatar");
}
private void DetachWrapper(object o)
{
if (World.AttachmentsModule != null)
{
SceneObjectPart host = (SceneObjectPart)o;
ScenePresence presence = World.GetScenePresence(host.OwnerID);
World.AttachmentsModule.DetachSingleAttachmentToInv(presence, host.ParentGroup);
}
}
public void llAttachToAvatar(LSL_Integer attachmentPoint)
{
if (m_item.PermsGranter != m_host.OwnerID)
return;
SceneObjectGroup grp = m_host.ParentGroup;
if (grp == null || grp.IsDeleted || grp.IsAttachment)
return;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0)
AttachToAvatar(attachmentPoint);
}
public void llAttachToAvatarTemp(LSL_Integer attachmentPoint)
{
IAttachmentsModule attachmentsModule = World.RequestModuleInterface();
if (attachmentsModule == null)
return;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) == 0)
return;
SceneObjectGroup grp = m_host.ParentGroup;
if (grp == null || grp.IsDeleted || grp.IsAttachment)
return;
if (!World.TryGetScenePresence(m_item.PermsGranter, out ScenePresence target))
return;
if (target.UUID != grp.OwnerID)
{
uint effectivePerms = grp.EffectiveOwnerPerms;
if ((effectivePerms & (uint)PermissionMask.Transfer) == 0)
return;
UUID permsgranter = m_item.PermsGranter;
int permsmask = m_item.PermsMask;
grp.SetOwner(target.UUID, target.ControllingClient.ActiveGroupId);
if (World.Permissions.PropagatePermissions())
{
foreach (SceneObjectPart child in grp.Parts)
{
child.Inventory.ChangeInventoryOwner(target.UUID);
child.TriggerScriptChangedEvent(Changed.OWNER);
child.ApplyNextOwnerPermissions();
}
grp.InvalidateEffectivePerms();
}
m_item.PermsMask = permsmask;
m_item.PermsGranter = permsgranter;
grp.RootPart.ObjectSaleType = 0;
grp.RootPart.SalePrice = 10;
grp.HasGroupChanged = true;
grp.RootPart.SendPropertiesToClient(target.ControllingClient);
grp.RootPart.ScheduleFullUpdate();
}
attachmentsModule.AttachObject(target, grp, (uint)attachmentPoint, false, false, true);
}
public void llDetachFromAvatar()
{
if (m_host.ParentGroup.AttachmentPoint == 0)
return;
if (m_item.PermsGranter != m_host.OwnerID)
return;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_ATTACH) != 0)
DetachFromAvatar();
}
public void llTakeCamera(string avatar)
{
Deprecated("llTakeCamera", "Use llSetCameraParams instead");
}
public void llReleaseCamera(string avatar)
{
Deprecated("llReleaseCamera", "Use llClearCameraParams instead");
}
public LSL_Key llGetOwner()
{
return m_host.OwnerID.ToString();
}
public void llInstantMessage(string userKey, string message)
{
if (m_TransferModule == null || String.IsNullOrEmpty(message))
return;
if (!UUID.TryParse(userKey, out UUID userID) || userID.IsZero())
{
Error("llInstantMessage","An invalid key was passed to llInstantMessage");
ScriptSleep(2000);
return;
}
Vector3 pos = m_host.AbsolutePosition;
GridInstantMessage msg = new()
{
fromAgentID = m_host.OwnerID.Guid,
toAgentID = userID.Guid,
imSessionID = m_host.UUID.Guid, // This is the item we're mucking with here
timestamp = (uint)Util.UnixTimeSinceEpoch(),
fromAgentName = m_host.Name, //client.FirstName + " " + client.LastName;// fromAgentName;
dialog = 19, // MessageFromObject
fromGroup = false,
offline = 0,
ParentEstateID = World.RegionInfo.EstateSettings.EstateID,
Position = pos,
RegionID = World.RegionInfo.RegionID.Guid,
message = (message.Length > 1024) ? message[..1024] : message,
binaryBucket = Util.StringToBytes256($"{m_regionName}/{(int)pos.X}/{(int)pos.Y}/{(int)pos.Z}")
};
m_TransferModule?.SendInstantMessage(msg, delegate(bool success) {});
ScriptSleep(m_sleepMsOnInstantMessage);
}
public void llEmail(string address, string subject, string message)
{
if (m_emailModule == null)
{
Error("llEmail", "Email module not configured");
return;
}
// this is a fire and forget no event is sent to script
void act(string eventID)
{
//Restrict email destination to the avatars registered email address?
//The restriction only applies if the destination address is not local.
if (m_restrictEmail == true && address.Contains(m_internalObjectHost) == false)
{
UserAccount account = m_userAccountService.GetUserAccount(RegionScopeID, m_host.OwnerID);
if (account == null)
return;
if (String.IsNullOrEmpty(account.Email))
return;
address = account.Email;
}
m_emailModule.SendEmail(m_host.UUID, m_host.ParentGroup.OwnerID, address, subject, message);
// no dataserver event
}
m_AsyncCommands.DataserverPlugin.RegisterRequest(m_host.LocalId,
m_item.ItemID, act);
ScriptSleep(m_sleepMsOnEmail);
}
public void llGetNextEmail(string address, string subject)
{
if (m_emailModule == null)
{
Error("llGetNextEmail", "Email module not configured");
return;
}
Email email;
email = m_emailModule.GetNextEmail(m_host.UUID, address, subject);
if (email == null)
return;
m_ScriptEngine.PostObjectEvent(m_host.LocalId,
new EventParams("email",
new Object[] {
new LSL_String(email.time),
new LSL_String(email.sender),
new LSL_String(email.subject),
new LSL_String(email.message),
new LSL_Integer(email.numLeft)},
Array.Empty()));
}
public void llTargetedEmail(LSL_Integer target, LSL_String subject, LSL_String message)
{
SceneObjectGroup parent = m_host.ParentGroup;
if (parent == null || parent.IsDeleted)
return;
if (m_emailModule == null)
{
Error("llTargetedEmail", "Email module not configured");
return;
}
if (subject.Length + message.Length > 4096)
{
Error("llTargetedEmail", "Message is too large");
return;
}
// this is a fire and forget no event is sent to script
void act(string eventID)
{
UserAccount account = null;
if (target == ScriptBaseClass.TARGETED_EMAIL_OBJECT_OWNER)
{
if (parent.OwnerID.Equals(parent.GroupID))
return;
account = m_userAccountService.GetUserAccount(RegionScopeID, parent.OwnerID);
}
else if (target == ScriptBaseClass.TARGETED_EMAIL_ROOT_CREATOR)
{
// non standard avoid creator spam
if (m_item.CreatorID.Equals(parent.RootPart.CreatorID))
account = m_userAccountService.GetUserAccount(RegionScopeID, parent.RootPart.CreatorID);
else
return;
}
else
return;
if (account == null)
return;
if (String.IsNullOrEmpty(account.Email))
return;
m_emailModule.SendEmail(m_host.UUID, m_host.ParentGroup.OwnerID, account.Email, subject, message);
}
m_AsyncCommands.DataserverPlugin.RegisterRequest(m_host.LocalId,
m_item.ItemID, act);
ScriptSleep(m_sleepMsOnEmail);
}
public LSL_Key llGetKey()
{
return m_host.UUID.ToString();
}
public LSL_Key llGenerateKey()
{
return UUID.Random().ToString();
}
public void llSetBuoyancy(double buoyancy)
{
if (!m_host.ParentGroup.IsDeleted)
{
m_host.ParentGroup.RootPart.SetBuoyancy((float)buoyancy);
}
}
///
/// Attempt to clamp the object on the Z axis at the given height over tau seconds.
///
/// Height to hover. Height of zero disables hover.
/// False if height is calculated just from ground, otherwise uses ground or water depending on whichever is higher
/// Number of seconds over which to reach target
public void llSetHoverHeight(double height, int water, double tau)
{
PIDHoverType hoverType = PIDHoverType.Ground;
if (water != 0)
{
hoverType = PIDHoverType.GroundAndWater;
}
m_host.SetHoverHeight((float)height, hoverType, (float)tau);
}
public void llStopHover()
{
m_host.SetHoverHeight(0f, PIDHoverType.Ground, 0f);
}
public void llMinEventDelay(double delay)
{
try
{
m_ScriptEngine.SetMinEventDelay(m_item.ItemID, delay);
}
catch (NotImplementedException)
{
// Currently not implemented in DotNetEngine only XEngine
NotImplemented("llMinEventDelay", "In DotNetEngine");
}
}
public void llSoundPreload(string sound)
{
Deprecated("llSoundPreload", "Use llPreloadSound instead");
}
public void llRotLookAt(LSL_Rotation target, double strength, double damping)
{
// Per discussion with Melanie, for non-physical objects llLookAt appears to simply
// set the rotation of the object, copy that behavior
SceneObjectGroup sog = m_host.ParentGroup;
if(sog == null || sog.IsDeleted)
return;
if (strength == 0 || !sog.UsesPhysics || sog.IsAttachment)
{
llSetLocalRot(target);
}
else
{
sog.RotLookAt(target, (float)strength, (float)damping);
}
}
public LSL_Integer llStringLength(string str)
{
if(str == null || str.Length <= 0)
return 0;
return str.Length;
}
public void llStartAnimation(string anim)
{
if (m_item.PermsGranter.IsZero())
return;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION) != 0)
{
ScenePresence presence = World.GetScenePresence(m_item.PermsGranter);
if (presence is not null)
{
// Do NOT try to parse UUID, animations cannot be triggered by ID
UUID animID = ScriptUtils.GetAssetIdFromItemName(m_host, anim, (int)AssetType.Animation);
if (animID.IsZero())
presence.Animator.AddAnimation(anim, m_host.UUID);
else
presence.Animator.AddAnimation(animID, m_host.UUID);
}
}
}
public void llStopAnimation(string anim)
{
if (m_item.PermsGranter.IsZero())
return;
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION) != 0)
{
ScenePresence presence = World.GetScenePresence(m_item.PermsGranter);
if (presence is not null)
{
UUID animID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, anim);
if (animID.IsNotZero())
presence.Animator.RemoveAnimation(animID, true);
else if (presence.TryGetAnimationOverride(anim.ToUpper(), out UUID sitanimID))
presence.Animator.RemoveAnimation(sitanimID, true);
else
presence.Animator.RemoveAnimation(anim);
}
}
}
public void llStartObjectAnimation(string anim)
{
// Do NOT try to parse UUID, animations cannot be triggered by ID
UUID animID = ScriptUtils.GetAssetIdFromItemName(m_host, anim, (int)AssetType.Animation);
if (animID.IsZero())
animID = DefaultAvatarAnimations.GetDefaultAnimation(anim);
if (animID.IsNotZero())
m_host.AddAnimation(animID, anim);
}
public void llStopObjectAnimation(string anim)
{
m_host.RemoveAnimation(anim);
}
public LSL_List llGetObjectAnimationNames()
{
LSL_List ret = new();
if(m_host.AnimationsNames is null || m_host.AnimationsNames.Count == 0)
return ret;
foreach (string name in m_host.AnimationsNames.Values)
ret.Add(new LSL_String(name));
return ret;
}
public void llPointAt(LSL_Vector pos)
{
}
public void llStopPointAt()
{
}
public void llTargetOmega(LSL_Vector axis, double spinrate, double gain)
{
TargetOmega(m_host, axis, (float)spinrate, (float)gain);
}
protected static void TargetOmega(SceneObjectPart part, LSL_Vector axis, float spinrate, float gain)
{
if(MathF.Abs(gain) < 1e-6f)
{
part.UpdateAngularVelocity(Vector3.Zero);
part.ScheduleFullAnimUpdate();
}
else
part.UpdateAngularVelocity((Vector3)axis * spinrate);
}
public LSL_Integer llGetStartParameter()
{
return m_ScriptEngine.GetStartParameter(m_item.ItemID);
}
public void llRequestPermissions(string agent, int perm)
{
if (!UUID.TryParse(agent, out UUID agentID) || agentID.IsZero())
return;
if (agentID.IsZero() || perm == 0) // Releasing permissions
{
llReleaseControls();
m_item.PermsGranter = UUID.Zero;
m_item.PermsMask = 0;
m_ScriptEngine.PostScriptEvent(m_item.ItemID, new EventParams(
"run_time_permissions", new Object[] {
new LSL_Integer(0) },
Array.Empty()));
return;
}
if (m_item.PermsGranter != agentID || (perm & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) == 0)
llReleaseControls();
int implicitPerms = 0;
if (m_host.ParentGroup.IsAttachment && (UUID)agent == m_host.ParentGroup.AttachedAvatar)
{
// When attached, certain permissions are implicit if requested from owner
implicitPerms = ScriptBaseClass.PERMISSION_TAKE_CONTROLS |
ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION |
ScriptBaseClass.PERMISSION_CONTROL_CAMERA |
ScriptBaseClass.PERMISSION_TRACK_CAMERA |
ScriptBaseClass.PERMISSION_ATTACH |
ScriptBaseClass.PERMISSION_OVERRIDE_ANIMATIONS;
}
else
{
if (m_host.ParentGroup.HasSittingAvatar(agentID))
{
// When agent is sitting, certain permissions are implicit if requested from sitting agent
implicitPerms = ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION |
ScriptBaseClass.PERMISSION_CONTROL_CAMERA |
ScriptBaseClass.PERMISSION_TRACK_CAMERA |
ScriptBaseClass.PERMISSION_TAKE_CONTROLS;
}
else
{
if (World.GetExtraSetting("auto_grant_attach_perms") == "true")
implicitPerms = ScriptBaseClass.PERMISSION_ATTACH;
}
if (World.GetExtraSetting("auto_grant_all_perms") == "true")
{
implicitPerms = perm;
}
}
if ((perm & (~implicitPerms)) == 0) // Requested only implicit perms
{
m_host.TaskInventory.LockItemsForWrite(true);
m_host.TaskInventory[m_item.ItemID].PermsGranter = agentID;
m_host.TaskInventory[m_item.ItemID].PermsMask = perm;
m_host.TaskInventory.LockItemsForWrite(false);
m_ScriptEngine.PostScriptEvent(m_item.ItemID, new EventParams(
"run_time_permissions", new Object[] {
new LSL_Integer(perm) },
Array.Empty()));
return;
}
ScenePresence presence = World.GetScenePresence(agentID);
if (presence != null)
{
// If permissions are being requested from an NPC and were not implicitly granted above then
// auto grant all requested permissions if the script is owned by the NPC or the NPCs owner
INPCModule npcModule = World.RequestModuleInterface();
if (npcModule != null && npcModule.IsNPC(agentID, World))
{
if (npcModule.CheckPermissions(agentID, m_host.OwnerID))
{
lock (m_host.TaskInventory)
{
m_host.TaskInventory[m_item.ItemID].PermsGranter = agentID;
m_host.TaskInventory[m_item.ItemID].PermsMask = perm;
}
m_ScriptEngine.PostScriptEvent(
m_item.ItemID,
new EventParams(
"run_time_permissions", new Object[] { new LSL_Integer(perm) }, Array.Empty()));
}
// it is an NPC, exit even if the permissions werent granted above, they are not going to answer
// the question!
return;
}
string ownerName = resolveName(m_host.ParentGroup.RootPart.OwnerID);
if (ownerName == String.Empty)
ownerName = "(hippos)";
if (!m_waitingForScriptAnswer)
{
m_host.TaskInventory.LockItemsForWrite(true);
m_host.TaskInventory[m_item.ItemID].PermsGranter = agentID;
m_host.TaskInventory[m_item.ItemID].PermsMask = 0;
m_host.TaskInventory.LockItemsForWrite(false);
presence.ControllingClient.OnScriptAnswer += handleScriptAnswer;
m_waitingForScriptAnswer=true;
}
presence.ControllingClient.SendScriptQuestion(
m_host.UUID, m_host.ParentGroup.RootPart.Name, ownerName, m_item.ItemID, perm);
return;
}
// Requested agent is not in range, refuse perms
m_ScriptEngine.PostScriptEvent(
m_item.ItemID,
new EventParams("run_time_permissions", new Object[] { new LSL_Integer(0) }, Array.Empty()));
}
void handleScriptAnswer(IClientAPI client, UUID taskID, UUID itemID, int answer)
{
if (taskID != m_host.UUID)
return;
client.OnScriptAnswer -= handleScriptAnswer;
m_waitingForScriptAnswer = false;
if ((answer & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) == 0)
llReleaseControls();
m_host.TaskInventory.LockItemsForWrite(true);
m_host.TaskInventory[m_item.ItemID].PermsMask = answer;
m_host.TaskInventory.LockItemsForWrite(false);
m_ScriptEngine.PostScriptEvent(m_item.ItemID, new EventParams(
"run_time_permissions", new Object[] {
new LSL_Integer(answer) },
Array.Empty()));
}
public LSL_Key llGetPermissionsKey()
{
return m_item.PermsGranter.ToString();
}
public LSL_Integer llGetPermissions()
{
int perms = m_item.PermsMask;
if (m_automaticLinkPermission)
perms |= ScriptBaseClass.PERMISSION_CHANGE_LINKS;
return perms;
}
public LSL_Integer llGetLinkNumber()
{
if (m_host.ParentGroup.PrimCount > 1)
{
return m_host.LinkNum;
}
else
{
return 0;
}
}
public void llSetLinkColor(int linknumber, LSL_Vector color, int face)
{
List parts = GetLinkParts(linknumber);
if (parts.Count > 0)
{
try
{
foreach (SceneObjectPart part in parts)
part.SetFaceColorAlpha(face, color, null);
}
finally { }
}
}
public void llCreateLink(LSL_Key target, LSL_Integer parent)
{
if (!m_automaticLinkPermission)
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0)
{
Error("llCreateLink", "PERMISSION_CHANGE_LINKS required");
return;
}
if (m_item.PermsGranter.NotEqual(m_host.ParentGroup.OwnerID))
{
Error("llCreateLink", "PERMISSION_CHANGE_LINKS not set by script owner");
return;
}
}
CreateLink(target, parent);
}
public void CreateLink(string target, int parent)
{
if (!UUID.TryParse(target, out UUID targetID) || targetID.IsZero())
return;
SceneObjectGroup hostgroup = m_host.ParentGroup;
if (hostgroup.AttachmentPoint != 0)
return; // Fail silently if attached
if ((hostgroup.RootPart.OwnerMask & (uint)PermissionMask.Modify) == 0)
return;
SceneObjectPart targetPart = World.GetSceneObjectPart(targetID);
if (targetPart == null)
return;
SceneObjectGroup targetgrp = targetPart.ParentGroup;
if (targetgrp == null || targetgrp.OwnerID.NotEqual(hostgroup.OwnerID))
return;
if (targetgrp.AttachmentPoint != 0)
return; // Fail silently if attached
if ((targetgrp.RootPart.OwnerMask & (uint)PermissionMask.Modify) == 0)
return;
SceneObjectGroup parentPrim, childPrim;
if (parent != 0)
{
parentPrim = hostgroup;
childPrim = targetgrp;
}
else
{
parentPrim = targetgrp;
childPrim = hostgroup;
}
// Required for linking
childPrim.RootPart.ClearUpdateSchedule();
parentPrim.LinkToGroup(childPrim, true);
parentPrim.TriggerScriptChangedEvent(Changed.LINK);
parentPrim.RootPart.CreateSelected = false;
parentPrim.HasGroupChanged = true;
parentPrim.ScheduleGroupForFullUpdate();
IClientAPI client = null;
ScenePresence sp = World.GetScenePresence(m_host.OwnerID);
if (sp != null)
client = sp.ControllingClient;
if (client != null)
parentPrim.SendPropertiesToClient(client);
ScriptSleep(m_sleepMsOnCreateLink);
}
public void llBreakLink(int linknum)
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0
&& !m_automaticLinkPermission)
{
Error("llBreakLink", "PERMISSION_CHANGE_LINKS permission not set");
return;
}
BreakLink(linknum);
}
public void BreakLink(int linknum)
{
if (linknum < ScriptBaseClass.LINK_THIS)
return;
SceneObjectGroup parentSOG = m_host.ParentGroup;
if (parentSOG.AttachmentPoint != 0)
return; // Fail silently if attached
if ((parentSOG.RootPart.OwnerMask & (uint)PermissionMask.Modify) == 0)
return;
SceneObjectPart childPrim = null;
switch (linknum)
{
case ScriptBaseClass.LINK_ROOT:
case ScriptBaseClass.LINK_SET:
case ScriptBaseClass.LINK_ALL_OTHERS:
case ScriptBaseClass.LINK_ALL_CHILDREN:
break;
case ScriptBaseClass.LINK_THIS: // not as spec
childPrim = m_host;
break;
default:
childPrim = parentSOG.GetLinkNumPart(linknum);
break;
}
if (linknum == ScriptBaseClass.LINK_ROOT)
{
List avs = parentSOG.GetSittingAvatars();
foreach (ScenePresence av in avs)
av.StandUp();
List parts = new(parentSOG.Parts);
parts.Remove(parentSOG.RootPart);
if (parts.Count > 0)
{
try
{
foreach (SceneObjectPart part in parts)
{
parentSOG.DelinkFromGroup(part.LocalId, true);
}
}
finally { }
}
parentSOG.HasGroupChanged = true;
parentSOG.ScheduleGroupForFullUpdate();
parentSOG.TriggerScriptChangedEvent(Changed.LINK);
if (parts.Count > 0)
{
SceneObjectPart newRoot = parts[0];
parts.Remove(newRoot);
try
{
foreach (SceneObjectPart part in parts)
{
part.ClearUpdateSchedule();
newRoot.ParentGroup.LinkToGroup(part.ParentGroup);
}
}
finally { }
newRoot.ParentGroup.HasGroupChanged = true;
newRoot.ParentGroup.ScheduleGroupForFullUpdate();
}
}
else
{
if (childPrim == null)
return;
List avs = parentSOG.GetSittingAvatars();
foreach (ScenePresence av in avs)
av.StandUp();
parentSOG.DelinkFromGroup(childPrim.LocalId, true);
}
}
public void llBreakAllLinks()
{
TaskInventoryItem item = m_item;
if ((item.PermsMask & ScriptBaseClass.PERMISSION_CHANGE_LINKS) == 0
&& !m_automaticLinkPermission)
{
Error("llBreakAllLinks","Script trying to link but PERMISSION_CHANGE_LINKS permission not set!");
return;
}
BreakAllLinks();
}
public void BreakAllLinks()
{
SceneObjectGroup parentPrim = m_host.ParentGroup;
if (parentPrim.AttachmentPoint != 0)
return; // Fail silently if attached
List parts = new(parentPrim.Parts);
parts.Remove(parentPrim.RootPart);
foreach (SceneObjectPart part in parts)
{
parentPrim.DelinkFromGroup(part.LocalId, true);
parentPrim.TriggerScriptChangedEvent(Changed.LINK);
}
parentPrim.HasGroupChanged = true;
parentPrim.ScheduleGroupForFullUpdate();
}
public LSL_Key llGetLinkKey(int linknum)
{
if (linknum < 0)
{
if (linknum == ScriptBaseClass.LINK_THIS)
return m_host.UUID.ToString();
return ScriptBaseClass.NULL_KEY;
}
SceneObjectGroup sog = m_host.ParentGroup;
if (linknum < 2)
return sog.RootPart.UUID.ToString();
SceneObjectPart part = sog.GetLinkNumPart(linknum);
if (part is not null)
{
return part.UUID.ToString();
}
else
{
if (linknum > sog.PrimCount)
{
linknum -= sog.PrimCount + 1;
List avatars = GetLinkAvatars(ScriptBaseClass.LINK_SET, sog);
if (avatars.Count > linknum)
{
return avatars[linknum].UUID.ToString();
}
}
return ScriptBaseClass.NULL_KEY;
}
}
public LSL_Key llGetObjectLinkKey(LSL_Key objectid, int linknum)
{
if (!UUID.TryParse(objectid, out UUID oID) || oID.IsZero())
return ScriptBaseClass.NULL_KEY;
if (!World.TryGetSceneObjectPart(oID, out SceneObjectPart sop))
return ScriptBaseClass.NULL_KEY;
if (linknum < 0)
{
if (linknum == ScriptBaseClass.LINK_THIS)
return sop.UUID.ToString();
return ScriptBaseClass.NULL_KEY;
}
SceneObjectGroup sog = sop.ParentGroup;
if (linknum < 2)
return sog.RootPart.UUID.ToString();
SceneObjectPart part = sog.GetLinkNumPart(linknum);
if (part is not null)
{
return part.UUID.ToString();
}
else
{
if (linknum > sog.PrimCount)
{
linknum -= sog.PrimCount + 1;
List avatars = GetLinkAvatars(ScriptBaseClass.LINK_SET, sog);
if (avatars.Count > linknum)
{
return avatars[linknum].UUID.ToString();
}
}
return ScriptBaseClass.NULL_KEY;
}
}
///
/// Returns the name of the child prim or seated avatar matching the
/// specified link number.
///
///
/// The number of a link in the linkset or a link-related constant.
///
///
/// The name determined to match the specified link number.
///
///
/// The rules governing the returned name are not simple. The only
/// time a blank name is returned is if the target prim has a blank
/// name. If no prim with the given link number can be found then
/// usually NULL_KEY is returned but there are exceptions.
///
/// In a single unlinked prim, A call with 0 returns the name, all
/// other values for link number return NULL_KEY
///
/// In link sets it is more complicated.
///
/// If the script is in the root prim:-
/// A zero link number returns NULL_KEY.
/// Positive link numbers return the name of the prim, or NULL_KEY
/// if a prim does not exist at that position.
/// Negative link numbers return the name of the first child prim.
///
/// If the script is in a child prim:-
/// Link numbers 0 or 1 return the name of the root prim.
/// Positive link numbers return the name of the prim or NULL_KEY
/// if a prim does not exist at that position.
/// Negative numbers return the name of the root prim.
///
/// References
/// http://lslwiki.net/lslwiki/wakka.php?wakka=llGetLinkName
/// Mentions NULL_KEY being returned
/// http://wiki.secondlife.com/wiki/LlGetLinkName
/// Mentions using the LINK_* constants, some of which are negative
///
public LSL_String llGetLinkName(int linknum)
{
ISceneEntity entity = GetLinkEntity(m_host, linknum);
return (entity is null) ? ScriptBaseClass.NULL_KEY : entity.Name;
}
public LSL_Integer llGetInventoryNumber(int type)
{
int count = 0;
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair inv in m_host.TaskInventory)
{
if (inv.Value.Type == type || type == -1)
count++;
}
m_host.TaskInventory.LockItemsForRead(false);
return count;
}
public LSL_String llGetInventoryName(int type, int number)
{
ArrayList keys = new();
m_host.TaskInventory.LockItemsForRead(true);
foreach (KeyValuePair inv in m_host.TaskInventory)
{
if (inv.Value.Type == type || type == -1)
{
keys.Add(inv.Value.Name);
}
}
m_host.TaskInventory.LockItemsForRead(false);
if (keys.Count == 0)
{
return String.Empty;
}
keys.Sort();
if (keys.Count > number)
{
return (string)keys[number];
}
return String.Empty;
}
public LSL_Float llGetEnergy()
{
// TODO: figure out real energy value
return 1.0f;
}
public void llGiveInventory(LSL_Key destination, LSL_String inventory)
{
if (!UUID.TryParse(destination, out UUID destId) || destId.IsZero())
{
Error("llGiveInventory", "Can't parse destination key '" + destination + "'");
return;
}
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(inventory);
if (item is null)
{
Error("llGiveInventory", "Can't find inventory object '" + inventory + "'");
return;
}
// check if destination is an object
if (World.TryGetSceneObjectPart(destId, out _))
{
// destination is an object
World.MoveTaskInventoryItem(destId, m_host, item.ItemID);
return;
}
ScenePresence presence = World.GetScenePresence(destId);
if (presence is null)
{
UserAccount account = m_userAccountService.GetUserAccount(RegionScopeID, destId);
if (account is null)
{
GridUserInfo info = World.GridUserService.GetGridUserInfo(destId.ToString());
if(info is null || info.Online == false)
{
Error("llGiveInventory", "Can't find destination '" + destId.ToString() + "'");
return;
}
}
}
// destination is an avatar
InventoryItemBase agentItem = World.MoveTaskInventoryItem(destId, UUID.Zero, m_host, item.ItemID, out string message);
if (agentItem is null)
{
llSay(0, message);
return;
}
byte[] bucket = new byte[1];
bucket[0] = (byte)item.Type;
GridInstantMessage msg = new(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 "+
m_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(m_sleepMsOnGiveInventory);
}
[DebuggerNonUserCode]
public void llRemoveInventory(string name)
{
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(name);
if (item == null)
return;
if (item.ItemID == m_item.ItemID)
throw new ScriptDeleteException();
else
m_host.Inventory.RemoveInventoryItem(item.ItemID);
}
public void llSetText(string text, LSL_Vector color, double alpha)
{
Vector3 av3 = Vector3.Clamp(color, 0.0f, 1.0f);
m_host.SetText(text, av3, Utils.Clamp((float)alpha, 0.0f, 1.0f));
}
public LSL_Float llWater(LSL_Vector offset)
{
return World.RegionInfo.RegionSettings.WaterHeight;
}
public void llPassTouches(int pass)
{
if (pass != 0)
m_host.PassTouches = true;
else
m_host.PassTouches = false;
}
public LSL_Key llRequestAgentData(string id, int data)
{
if(data < 1 || data > ScriptBaseClass.DATA_PAYINFO)
return string.Empty;
if (UUID.TryParse(id, out UUID uuid) && uuid.IsNotZero())
{
//pre process fast local avatars
switch(data)
{
case ScriptBaseClass.DATA_RATING:
case ScriptBaseClass.DATA_NAME: // DATA_NAME (First Last)
case ScriptBaseClass.DATA_ONLINE:
World.TryGetScenePresence(uuid, out ScenePresence sp);
if (sp != null)
{
string reply = data switch
{
ScriptBaseClass.DATA_RATING => "0,0,0,0,0,0",
ScriptBaseClass.DATA_NAME => sp.Firstname + " " + sp.Lastname,
_ => "1"
};
string ftid = m_AsyncCommands.DataserverPlugin.RequestWithImediatePost(m_host.LocalId,
m_item.ItemID, reply);
ScriptSleep(m_sleepMsOnRequestAgentData);
return ftid;
}
break;
case ScriptBaseClass.DATA_BORN: // DATA_BORN (YYYY-MM-DD)
case 7: // DATA_USERLEVEL (integer). This is not available in LL and so has no constant.
case ScriptBaseClass.DATA_PAYINFO: // DATA_PAYINFO (0|1|2|3)
break;
default:
return string.Empty; // Raise no event
}
void act(string eventID)
{
IUserManagement umm = World.RequestModuleInterface();
if(umm == null)
return;
UserData udt = umm.GetUserData(uuid);
if (udt == null || udt.IsUnknownUser)
return;
string reply = null;
switch(data)
{
case ScriptBaseClass.DATA_ONLINE:
if (!m_PresenceInfoCache.TryGetValue(uuid, out PresenceInfo pinfo))
{
PresenceInfo[] pinfos = World.PresenceService.GetAgents([uuid.ToString()]);
if (pinfos != null && pinfos.Length > 0)
{
foreach (PresenceInfo p in pinfos)
{
if (!p.RegionID.IsZero())
{
pinfo = p;
}
}
}
m_PresenceInfoCache.AddOrUpdate(uuid, pinfo, m_llRequestAgentDataCacheTimeout);
}
reply = pinfo == null ? "0" : "1";
break;
case ScriptBaseClass.DATA_NAME:
reply = udt.FirstName + " " + udt.LastName;
break;
case ScriptBaseClass.DATA_RATING:
reply = "0,0,0,0,0,0";
break;
case 7:
case ScriptBaseClass.DATA_BORN:
case ScriptBaseClass.DATA_PAYINFO:
if (udt.IsLocal)
{
UserAccount account = m_userAccountService.GetUserAccount(RegionScopeID, uuid);
if (account is not null)
{
reply = data switch
{
7 => account.UserLevel.ToString(),
ScriptBaseClass.DATA_BORN => Util.ToDateTime(account.Created).ToString("yyyy-MM-dd"),
_ => ((account.UserFlags >> 2) & 0x03).ToString()
};
}
}
else
{
if (data == 7)
reply = "0";
}
break;
default:
break;
}
if(reply != null)
m_AsyncCommands.DataserverPlugin.DataserverReply(eventID, reply);
}
UUID tid = m_AsyncCommands.DataserverPlugin.RegisterRequest(m_host.LocalId,
m_item.ItemID, act);
ScriptSleep(m_sleepMsOnRequestAgentData);
return tid.ToString();
}
else
{
Error("llRequestAgentData","Invalid UUID passed to llRequestAgentData.");
}
return String.Empty;
}
//bad if lm is HG
public LSL_Key llRequestInventoryData(LSL_String name)
{
void act(string eventID)
{
string reply = String.Empty;
foreach (TaskInventoryItem item in m_host.Inventory.GetInventoryItems())
{
if (item.Type == 3 && item.Name == name)
{
AssetBase a = World.AssetService.Get(item.AssetID.ToString());
if (a is not null)
{
AssetLandmark lm = new(a);
if (lm is not null)
{
double rx = (lm.RegionHandle >> 32) - (double)World.RegionInfo.WorldLocX + (double)lm.Position.X;
double ry = (lm.RegionHandle & 0xffffffff) - (double)World.RegionInfo.WorldLocY + (double)lm.Position.Y;
LSL_Vector region = new(rx, ry, lm.Position.Z);
reply = region.ToString();
}
}
break;
}
}
m_AsyncCommands.DataserverPlugin.DataserverReply(eventID, reply);
}
UUID tid = m_AsyncCommands.DataserverPlugin.RegisterRequest(m_host.LocalId,
m_item.ItemID, act);
ScriptSleep(m_sleepMsOnRequestInventoryData);
return tid.ToString();
}
public void llSetDamage(double damage)
{
m_host.ParentGroup.Damage = (float)damage;
}
public void llTeleportAgentHome(string agent)
{
if (UUID.TryParse(agent, out UUID agentId) && agentId.IsNotZero())
{
ScenePresence presence = World.GetScenePresence(agentId);
if (presence == null || presence.IsDeleted || presence.IsChildAgent || presence.IsNPC || presence.IsInTransit)
return;
// agent must not be a god
if (presence.GodController.UserLevel >= 200)
return;
// agent must be over the owners land
if (m_host.OwnerID.Equals(World.LandChannel.GetLandObject(presence.AbsolutePosition).LandData.OwnerID))
{
World.TeleportClientHome(agentId, presence.ControllingClient);
}
}
ScriptSleep(m_sleepMsOnSetDamage);
}
public void llTeleportAgent(string agent, string destination, LSL_Vector targetPos, LSL_Vector targetLookAt)
{
// If attached using llAttachToAvatarTemp, cowardly refuse
if (m_host.ParentGroup.AttachmentPoint != 0 && m_host.ParentGroup.FromItemID.IsZero())
return;
if (UUID.TryParse(agent, out UUID agentId) && agentId.IsNotZero())
{
ScenePresence presence = World.GetScenePresence(agentId);
if (presence == null || presence.IsDeleted || presence.IsChildAgent || presence.IsNPC || presence.IsSatOnObject || presence.IsInTransit)
return;
if (m_item.PermsGranter.Equals(agentId))
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TELEPORT) != 0)
{
DoLLTeleport(presence, destination, targetPos, targetLookAt);
return;
}
}
// special opensim legacy extra permissions, possible to remove
// agent must be wearing the object
if (m_host.ParentGroup.AttachmentPoint != 0 && m_host.OwnerID.Equals(presence.UUID))
{
DoLLTeleport(presence, destination, targetPos, targetLookAt);
return;
}
// agent must not be a god
if (presence.IsViewerUIGod)
return;
// agent must be over the owners land
ILandObject agentLand = World.LandChannel.GetLandObject(presence.AbsolutePosition);
ILandObject objectLand = World.LandChannel.GetLandObject(m_host.AbsolutePosition);
if (m_host.OwnerID.Equals(objectLand.LandData.OwnerID) && m_host.OwnerID.Equals(agentLand.LandData.OwnerID))
{
DoLLTeleport(presence, destination, targetPos, targetLookAt);
}
}
}
public void llTeleportAgentGlobalCoords(string agent, LSL_Vector global_coords, LSL_Vector targetPos, LSL_Vector targetLookAt)
{
// If attached using llAttachToAvatarTemp, cowardly refuse
if (m_host.ParentGroup.AttachmentPoint != 0 && m_host.ParentGroup.FromItemID.IsZero())
return;
if (UUID.TryParse(agent, out UUID agentId) && agentId.IsNotZero())
{
// This function is owner only!
if (m_host.OwnerID.NotEqual(agentId))
return;
ScenePresence presence = World.GetScenePresence(agentId);
if (presence == null || presence.IsDeleted || presence.IsChildAgent || presence.IsNPC || presence.IsSatOnObject || presence.IsInTransit)
return;
if (m_item.PermsGranter.Equals(agentId))
{
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TELEPORT) != 0)
{
ulong regionHandle = Util.RegionWorldLocToHandle((uint)global_coords.x, (uint)global_coords.y);
World.RequestTeleportLocation(presence.ControllingClient, regionHandle, targetPos, targetLookAt, (uint)TeleportFlags.ViaLocation);
}
}
}
}
private void DoLLTeleport(ScenePresence sp, string destination, Vector3 targetPos, Vector3 targetLookAt)
{
UUID assetID = ScriptUtils.GetAssetIdFromKeyOrItemName(m_host, destination);
// The destination is not an asset ID and also doesn't name a landmark.
// Use it as a sim name
if (assetID.IsZero())
{
if(string.IsNullOrEmpty(destination))
World.RequestTeleportLocation(sp.ControllingClient, m_regionName, targetPos, targetLookAt, (uint)TeleportFlags.ViaLocation);
else
World.RequestTeleportLocation(sp.ControllingClient, destination, targetPos, targetLookAt, (uint)TeleportFlags.ViaLocation);
return;
}
AssetBase lma = World.AssetService.Get(assetID.ToString());
if (lma == null || lma.Data == null || lma.Data.Length == 0)
return;
if (lma.Type != (sbyte)AssetType.Landmark)
return;
AssetLandmark lm = new(lma);
World.RequestTeleportLandmark(sp.ControllingClient, lm, targetLookAt);
}
public void llTextBox(string agent, string message, int chatChannel)
{
IDialogModule dm = World.RequestModuleInterface();
if (dm == null)
return;
if (!UUID.TryParse(agent, out UUID av) || av.IsZero())
{
Error("llTextBox", "First parameter must be a valid agent key");
return;
}
if (message.Length == 0)
{
Error("llTextBox", "Empty message");
}
else if (Encoding.UTF8.GetByteCount(message) > 512)
{
Error("llTextBox", "Message longer than 512 bytes");
}
else if(m_host.GetOwnerName(out string fname, out string lname))
{
dm.SendTextBoxToUser(av, message, chatChannel, m_host.Name, m_host.UUID, fname, lname, m_host.OwnerID);
ScriptSleep(m_sleepMsOnTextBox);
}
}
public void llModifyLand(int action, int brush)
{
ITerrainModule tm = m_ScriptEngine.World.RequestModuleInterface();
tm?.ModifyTerrain(m_host.OwnerID, m_host.AbsolutePosition, (byte) brush, (byte) action);
}
public void llCollisionSound(LSL_String impact_sound, LSL_Float impact_volume)
{
if(String.IsNullOrEmpty(impact_sound.m_string))
{
m_host.CollisionSoundVolume = (float)impact_volume;
m_host.CollisionSound = m_host.invalidCollisionSoundUUID;
m_host.CollisionSoundType = -1; // disable all sounds
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();
}
public LSL_String llGetAnimation(LSL_Key id)
{
// This should only return a value if the avatar is in the same region
if(!UUID.TryParse(id, out UUID avatar) || avatar.IsZero())
return "";
ScenePresence presence = World.GetScenePresence(avatar);
if (presence == null || presence.IsChildAgent || presence.Animator == null)
return string.Empty;
//if (presence.SitGround)
// return "Sitting on Ground";
//if (presence.ParentID != 0 || presence.ParentUUID != UUID.Zero)
// return "Sitting";
string movementAnimation = presence.Animator.CurrentMovementAnimation;
if (MovementAnimationsForLSL.TryGetValue(movementAnimation, out string lslMovementAnimation))
return lslMovementAnimation;
return string.Empty;
}
public void llMessageLinked(int linknumber, int num, string msg, string id)
{
List parts = GetLinkParts(linknumber);
UUID partItemID;
foreach (SceneObjectPart part in parts)
{
foreach (TaskInventoryItem item in part.Inventory.GetInventoryItems())
{
if (item.Type == ScriptBaseClass.INVENTORY_SCRIPT)
{
partItemID = item.ItemID;
int linkNumber = m_host.LinkNum;
if (m_host.ParentGroup.PrimCount == 1)
linkNumber = 0;
object[] resobj = new object[]
{
new LSL_Integer(linkNumber), new LSL_Integer(num), new LSL_String(msg), new LSL_String(id)
};
m_ScriptEngine.PostScriptEvent(partItemID,
new EventParams("link_message",
resobj, Array.Empty()));
}
}
}
}
public void llPushObject(string target, LSL_Vector impulse, LSL_Vector ang_impulse, int local)
{
bool pushrestricted = World.RegionInfo.RegionSettings.RestrictPushing;
bool pushAllowed = false;
bool pusheeIsAvatar = false;
if (!UUID.TryParse(target, out UUID targetID) || targetID.IsZero())
return;
ScenePresence pusheeav = null;
Vector3 PusheePos = Vector3.Zero;
SceneObjectPart pusheeob = null;
ScenePresence avatar = World.GetScenePresence(targetID);
if (avatar != null)
{
pusheeIsAvatar = true;
// Pushee doesn't have a physics actor
if (avatar.PhysicsActor == null)
return;
// Pushee is in GodMode this pushing object isn't owned by them
if (avatar.IsViewerUIGod && m_host.OwnerID != targetID)
return;
pusheeav = avatar;
// Find pushee position
// Pushee Linked?
SceneObjectPart sitPart = pusheeav.ParentPart;
if (sitPart != null)
PusheePos = sitPart.AbsolutePosition;
else
PusheePos = pusheeav.AbsolutePosition;
}
if (!pusheeIsAvatar)
{
// not an avatar so push is not affected by parcel flags
pusheeob = World.GetSceneObjectPart((UUID)target);
// We can't find object
if (pusheeob == null)
return;
// Object not pushable. Not an attachment and has no physics component
if (!pusheeob.ParentGroup.IsAttachment && pusheeob.PhysActor == null)
return;
PusheePos = pusheeob.AbsolutePosition;
pushAllowed = true;
}
else
{
if (pushrestricted)
{
ILandObject targetlandObj = World.LandChannel.GetLandObject(PusheePos);
// We didn't find the parcel but region is push restricted so assume it is NOT ok
if (targetlandObj == null)
return;
// Need provisions for Group Owned here
if (m_host.OwnerID.Equals(targetlandObj.LandData.OwnerID) ||
targetlandObj.LandData.IsGroupOwned || m_host.OwnerID.Equals(targetID))
{
pushAllowed = true;
}
}
else
{
ILandObject targetlandObj = World.LandChannel.GetLandObject(PusheePos);
if (targetlandObj == null)
{
// We didn't find the parcel but region isn't push restricted so assume it's ok
pushAllowed = true;
}
else
{
// Parcel push restriction
if ((targetlandObj.LandData.Flags & (uint)ParcelFlags.RestrictPushObject) == (uint)ParcelFlags.RestrictPushObject)
{
// Need provisions for Group Owned here
if (m_host.OwnerID.Equals(targetlandObj.LandData.OwnerID) ||
targetlandObj.LandData.IsGroupOwned ||
m_host.OwnerID.Equals(targetID))
{
pushAllowed = true;
}
//ParcelFlags.RestrictPushObject
//pushAllowed = true;
}
else
{
// Parcel isn't push restricted
pushAllowed = true;
}
}
}
}
if (pushAllowed)
{
float distance = (PusheePos - m_host.AbsolutePosition).Length();
//float distance_term = distance * distance * distance; // Script Energy
// use total object mass and not part
//float pusher_mass = m_host.ParentGroup.GetMass();
float PUSH_ATTENUATION_DISTANCE = 17f;
float PUSH_ATTENUATION_SCALE = 5f;
float distance_attenuation = 1f;
if (distance > PUSH_ATTENUATION_DISTANCE)
{
float normalized_units = 1f + (distance - PUSH_ATTENUATION_DISTANCE) / PUSH_ATTENUATION_SCALE;
distance_attenuation = 1f / normalized_units;
}
Vector3 applied_linear_impulse = impulse;
{
//float impulse_length = applied_linear_impulse.Length();
//float desired_energy = impulse_length * pusher_mass;
float scaling_factor = 1f;
scaling_factor *= distance_attenuation;
applied_linear_impulse *= scaling_factor;
}
if (pusheeIsAvatar)
{
if (pusheeav is not null)
{
PhysicsActor pa = pusheeav.PhysicsActor;
if (pa is not null)
{
if (local != 0)
applied_linear_impulse *= pusheeav.GetWorldRotation();
pa.AddForce(applied_linear_impulse, true);
}
}
}
else
{
if (pusheeob is not null)
{
if (pusheeob.PhysActor is not null)
pusheeob.ApplyImpulse(applied_linear_impulse, local != 0);
}
}
}
}
public void llPassCollisions(int pass)
{
m_host.PassCollisions = pass == 1;
}
public LSL_String llGetScriptName()
{
return m_item.Name ?? String.Empty;
}
public LSL_Integer llGetLinkNumberOfSides(int link)
{
SceneObjectPart linkedPart;
if (link == ScriptBaseClass.LINK_ROOT)
linkedPart = m_host.ParentGroup.RootPart;
else if (link == ScriptBaseClass.LINK_THIS)
linkedPart = m_host;
else
linkedPart = m_host.ParentGroup.GetLinkNumPart(link);
return GetNumberOfSides(linkedPart);
}
public LSL_Integer llGetNumberOfSides()
{
return m_host.GetNumberOfSides();
}
protected static int GetNumberOfSides(SceneObjectPart part)
{
return part.GetNumberOfSides();
}
// Xantor 29/apr/2008
// Returns rotation described by rotating angle radians about axis.
// q = cos(a/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k (z * sin(a/2))
public LSL_Rotation llAxisAngle2Rot(LSL_Vector axis, double angle)
{
double x, y, z, s, t;
s = Math.Cos(angle * 0.5);
t = Math.Sin(angle * 0.5); // temp value to avoid 2 more sin() calcs
axis = LSL_Vector.Norm(axis);
x = axis.x * t;
y = axis.y * t;
z = axis.z * t;
return new LSL_Rotation(x,y,z,s);
}
///
/// Returns the axis of rotation for a quaternion
///
///
///
public LSL_Vector llRot2Axis(LSL_Rotation rot)
{
rot.Normalize();
double s = Math.Sqrt(1 - rot.s * rot.s);
if (s < 1e-8)
return new LSL_Vector(0, 0, 0);
double invS = 1.0 / s;
if (rot.s < 0)
invS = -invS;
return new LSL_Vector(rot.x * invS, rot.y * invS, rot.z * invS);
}
// Returns the angle of a quaternion (see llRot2Axis for the axis)
public LSL_Float llRot2Angle(LSL_Rotation rot)
{
rot.Normalize();
double angle = 2 * Math.Acos(rot.s);
if (angle > Math.PI)
angle = 2 * Math.PI - angle;
return angle;
}
public LSL_Float llAcos(LSL_Float val)
{
return Math.Acos(val);
}
public LSL_Float llAsin(LSL_Float val)
{
return Math.Asin(val);
}
// jcochran 5/jan/2012
public LSL_Float llAngleBetween(LSL_Rotation a, LSL_Rotation b)
{
double aa = (a.x * a.x + a.y * a.y + a.z * a.z + a.s * a.s);
double bb = (b.x * b.x + b.y * b.y + b.z * b.z + b.s * b.s);
double aa_bb = aa * bb;
if (aa_bb == 0) return 0.0;
double ab = (a.x * b.x + a.y * b.y + a.z * b.z + a.s * b.s);
double quotient = (ab * ab) / aa_bb;
if (quotient >= 1.0) return 0.0;
return Math.Acos(2 * quotient - 1);
}
public LSL_Key llGetInventoryKey(string name)
{
TaskInventoryItem item = m_host.Inventory.GetInventoryItem(name);
if (item is null)
return ScriptBaseClass.NULL_KEY;
if ((item.CurrentPermissions
& (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
== (uint)(PermissionMask.Copy | PermissionMask.Transfer | PermissionMask.Modify))
{
return item.AssetID.ToString();
}
return ScriptBaseClass.NULL_KEY;
}
public void llAllowInventoryDrop(LSL_Integer add)
{
m_host.ParentGroup.RootPart.AllowedDrop = add != 0;
m_host.ParentGroup.RootPart.aggregateScriptEvents();
}
public LSL_Vector llGetTextureOffset(int face)
{
return GetTextureOffset(m_host, face);
}
protected static LSL_Vector GetTextureOffset(SceneObjectPart part, int face)
{
if (face == ScriptBaseClass.ALL_SIDES)
face = 0;
if (face >= 0 && face < GetNumberOfSides(part))
{
Primitive.TextureEntryFace teface = part.Shape.Textures.GetFace((uint)face);
return new LSL_Vector (teface.OffsetU, teface.OffsetV, 0.0);
}
return LSL_Vector.Zero;
}
public LSL_Vector llGetTextureScale(int side)
{
Primitive.TextureEntryFace teface;
if (side == ScriptBaseClass.ALL_SIDES)
teface = m_host.Shape.Textures.GetFace(0);
else
teface = m_host.Shape.Textures.GetFace((uint)side);
return new LSL_Vector(teface.RepeatU, teface.RepeatV, 0.0);
}
public LSL_Float llGetTextureRot(int face)
{
return GetTextureRot(m_host, face);
}
protected static LSL_Float GetTextureRot(SceneObjectPart part, int face)
{
Primitive.TextureEntry tex = part.Shape.Textures;
if (face == ScriptBaseClass.ALL_SIDES)
face = 0;
if (face >= 0 && face < GetNumberOfSides(part))
return tex.GetFace((uint)face).Rotation;
return 0.0;
}
public LSL_Integer llSubStringIndex(string source, string pattern)
{
if (string.IsNullOrEmpty(source))
return -1;
if (string.IsNullOrEmpty(pattern))
return 0;
return source.IndexOf(pattern);
}
public LSL_Key llGetOwnerKey(string id)
{
if (UUID.TryParse(id, out UUID key))
{
if(key.IsZero())
return id;
SceneObjectPart obj = World.GetSceneObjectPart(key);
return (obj == null) ? id : obj.OwnerID.ToString();
}
else
{
return ScriptBaseClass.NULL_KEY;
}
}
public LSL_Vector llGetCenterOfMass()
{
return new LSL_Vector(m_host.GetCenterOfMass());
}
public LSL_List llListSort(LSL_List src, int stride, int ascending)
{
return src.Sort(stride, ascending == 1);
}
public LSL_List llListSortStrided(LSL_List src, int stride, int stride_index, int ascending)
{
return src.Sort(stride, stride_index, ascending == 1);
}
public LSL_Integer llGetListLength(LSL_List src)
{
return src.Length;
}
public LSL_Integer llList2Integer(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return 0;
object item = src.Data[index];
// Vectors & Rotations always return zero in SL, but
// keys don't always return zero, it seems to be a bit complex.
if (item is LSL_Vector || item is LSL_Rotation)
return 0;
try
{
if (item is LSL_Integer LSL_Integeritem)
return LSL_Integeritem;
if (item is int LSL_Intitem)
return new LSL_Integer(LSL_Intitem);
if (item is LSL_Float LSL_Floatitem)
return new LSL_Integer(LSL_Floatitem.value);
return new LSL_Integer(item.ToString());
}
catch (FormatException)
{
return 0;
}
}
public LSL_Float llList2Float(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return 0;
object item = src.Data[index];
// Vectors & Rotations always return zero in SL
if(item is LSL_Vector || item is LSL_Rotation)
return 0;
// valid keys seem to get parsed as integers then converted to floats
if (item is LSL_Key lslk)
{
if(UUID.TryParse(lslk.m_string, out UUID _))
return Convert.ToDouble(new LSL_Integer(lslk.m_string).value);
// we can't do this because a string is also a LSL_Key for now :(
//else
// return 0;
}
try
{
if (item is LSL_Float floatitem)
return floatitem;
if (item is LSL_Integer intitem)
return new LSL_Float(intitem.value);
if (item is int LSL_Intitem)
return new LSL_Float(LSL_Intitem);
if (item is LSL_String lstringitem)
{
Match m = Regex.Match(lstringitem.m_string, "^\\s*(-?\\+?[,0-9]+\\.?[0-9]*)");
if (m != Match.Empty)
{
if (Double.TryParse(m.Value, out double d))
return d;
}
return 0.0;
}
return Convert.ToDouble(item);
}
catch (FormatException)
{
return 0.0;
}
}
public LSL_String llList2String(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return String.Empty;
return src.Data[index].ToString();
}
public LSL_Key llList2Key(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return String.Empty;
object item = src.Data[index];
// SL spits out an empty string for types other than key & string
// At the time of patching, LSL_Key is currently LSL_String,
// so the OR check may be a little redundant, but it's being done
// for completion and should LSL_Key ever be implemented
// as it's own struct
// NOTE: 3rd case is needed because a NULL_KEY comes through as
// type 'obj' and wrongly returns ""
if (!(item is LSL_String ||
item is LSL_Key ||
item.ToString().Equals("00000000-0000-0000-0000-000000000000")))
{
return String.Empty;
}
return item.ToString();
}
public LSL_Vector llList2Vector(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return LSL_Vector.Zero;
object item = src.Data[index];
if(item is LSL_Vector vec)
return vec;
if (item is LSL_String lsv)
return new LSL_Vector(lsv);
if (item is string sv) // xengine sees string
return new LSL_Vector(sv);
return LSL_Vector.Zero;
}
public LSL_Rotation llList2Rot(LSL_List src, int index)
{
if (index < 0)
index = src.Length + index;
if (index >= src.Length || index < 0)
return LSL_Rotation.Identity;
object item = src.Data[index];
if (item is LSL_Rotation rot)
return rot;
if (item is LSL_String lls)
return new LSL_Rotation(lls);
if (item is string ls) // xengine sees string)
return new LSL_Rotation(ls);
return LSL_Rotation.Identity;
}
public LSL_List llList2List(LSL_List src, int start, int end)
{
return src.GetSublist(start, end);
}
public LSL_List llDeleteSubList(LSL_List src, int start, int end)
{
return src.DeleteSublist(start, end);
}
public LSL_Integer llGetListEntryType(LSL_List src, int index)
{
if (index < 0)
{
index = src.Length + index;
if (index < 0)
return 0;
}
else if (index >= src.Length)
return 0;
object o = src.Data[index];
if (o is null)
return 0;
if (o is LSL_Integer || o is Int32)
return 1;
if (o is LSL_Float || o is Single || o is Double)
return 2;
if (o is LSL_String || o is String)
return UUID.TryParse(o.ToString(), out UUID _) ? 4 : 3;
if (o is LSL_Key)
return 4;
if (o is LSL_Vector)
return 5;
if (o is LSL_Rotation)
return 6;
if (o is LSL_List)
return 7;
return 0;
}
///
/// Process the supplied list and return the
/// content of the list formatted as a comma
/// separated list. There is a space after
/// each comma.
///
public LSL_String llList2CSV(LSL_List src)
{
return string.Join(", ",
(new List