using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Reflection; using System.Threading; using log4net; using ModularRex.RexFramework; using Nwc.XmlRpc; using OpenMetaverse; using OpenMetaverse.Packets; using OpenSim.Framework; using OpenSim.Framework.Communications.Cache; using OpenSim.Region.ClientStack; using OpenSim.Region.ClientStack.LindenUDP; using ModularRex.RexNetwork.RexLogin; using System.Timers; namespace ModularRex.RexNetwork { /// /// Inherits from LLClientView the majority of functionality /// Overrides and extends for Rex-specific functionality. /// /// In the case whereby functionality uses the same packets but differs /// between Rex and LL, you can use a override on those specific functions /// to overload the request. /// public class RexClientView : LLClientView, IClientRexFaceExpression, IClientRexAppearance, IClientMediaURL, IRexClientCore { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); protected Dictionary m_genericMessageHandlers = new Dictionary(); private string m_rexAccountID; private string m_rexAvatarURL; private string m_rexAvatarURLOverride; private string m_rexAuthURL; private string m_rexSkypeURL; private float m_RexCharacterSpeedMod = 1.0f; private float m_RexVertMovementSpeedMod = 1.0f; public event RexAppearanceDelegate OnRexAppearance; public event RexGenericMessageDelegate OnRexFaceExpression; public event RexGenericMessageDelegate OnRexAvatarProperties; public event RexObjectPropertiesDelegate OnRexObjectProperties; public event RexStartUpDelegate OnRexStartUp; public event RexClientScriptCmdDelegate OnRexClientScriptCmd; public event ReceiveRexMediaURL OnReceiveRexMediaURL; public event RexGenericMessageDelegate OnPrimFreeData; public event ReceiveRexSkypeStore OnReceiveRexSkypeStore; public RexClientView(EndPoint remoteEP, IScene scene, IAssetCache assetCache, LLPacketServer packServer, AuthenticateResponse authenSessions, UUID agentId, UUID sessionId, uint circuitCode, EndPoint proxyEP, ClientStackUserSettings userSettings) : base(remoteEP, scene, assetCache, packServer, authenSessions, agentId, sessionId, circuitCode, proxyEP, userSettings) { // Rex communication now occurs via GenericMessage // We have a special handler here below. AddGenericPacketHandlers(); OnBinaryGenericMessage += RexClientView_BinaryGenericMessage; OnGenericMessage += RealXtendClientView_OnGenericMessage; } public RexClientView(EndPoint remoteEP, IScene scene, AssetCache assetCache, LLPacketServer packServer, AuthenticateResponse authenSessions, UUID agentId, UUID sessionId, uint circuitCode, EndPoint proxyEP, string rexAvatarURL, string rexAuthURL, ClientStackUserSettings userSettings) : base(remoteEP, scene, assetCache, packServer, authenSessions, agentId, sessionId, circuitCode, proxyEP, userSettings) { // Rex communication now occurs via GenericMessage // We need to register GenericMessage handlers AddGenericPacketHandlers(); OnBinaryGenericMessage += RexClientView_BinaryGenericMessage; RexAvatarURL = rexAvatarURL; RexAuthURL = rexAuthURL; } private void AddGenericPacketHandlers() { AddGenericPacketHandler("RexAppearance", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexFaceExpression", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexAvatarProp", RealXtendClientView_OnGenericMessage); //This added here only to disable warning about unhandled generic message //RexPrimData is actually handled in RexClientView_BinaryGenericMessage AddGenericPacketHandler("RexPrimData", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexData", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexMediaUrl", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("rexstartup", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexSkypeStore", RealXtendClientView_OnGenericMessage); m_genericMessageHandlers.Add("rexfaceexpression", OnRexFaceExpression); m_genericMessageHandlers.Add("rexavatarprop", OnRexAvatarProperties); m_genericMessageHandlers.Add("rexmediaurl", TriggerOnReceivedRexMediaURL); m_genericMessageHandlers.Add("rexdata", TriggerOnPrimFreeData); m_genericMessageHandlers.Add("rexskypestore", HandleOnSkypeStore); } /// /// Registers interfaces for IClientCore, /// every time you make a new Rex-specific /// Interface. Make sure to register it here. /// protected override void RegisterInterfaces() { RegisterInterface(this); RegisterInterface(this); RegisterInterface(this); RegisterInterface(this); // Register our own class 'as-is' so it can be // used via IClientCore.Get()... RegisterInterface(this); base.RegisterInterfaces(); } #region Properties /// /// The avatar URL for this avatar /// Eg: http://avatar.com:10000/uuid/ /// public string RexAvatarURL { get { return m_rexAvatarURL; } set { m_rexAvatarURL = value; if (OnRexAppearance != null) { OnRexAppearance(this); return; } } } /// /// The avatar URL override for this avatar /// Eg: http://avatar.com:10000/uuid/ /// public string RexAvatarURLOverride { get { return m_rexAvatarURLOverride; } set { m_rexAvatarURLOverride = value; if (OnRexAppearance != null) { OnRexAppearance(this); return; } } } /// /// The URL to avatar appearance which this view currently uses. /// If override is used, return it. Otherwise return normal avatar url. /// Eg: http://avatar.com:10000/uuid/ /// public string RexAvatarURLVisible { get { if (!string.IsNullOrEmpty(RexAvatarURLOverride)) return RexAvatarURLOverride; else return RexAvatarURL; } } /// /// Skype username of the avatar /// eg: Skypeuser /// public string RexSkypeURL { get { return m_rexSkypeURL; } set { m_rexSkypeURL = value; if (OnReceiveRexSkypeStore != null) { OnReceiveRexSkypeStore(this); } } } /// /// The full Rex Username of this account /// Eg: user@hostname.com:10001 /// /// Note: This is not filled immedietely on /// creation. This property is filled in /// via Login and may not be availible /// immedietely upon connect. /// /// The above glitch is scheduled to be /// fixed by a new RexCommsManager which /// will allow this to be set at spawn in /// login. /// public string RexAccount { get { return m_rexAccountID; } set { // Todo: More solid data checking here. m_rexAccountID = value; RexAuthURL = m_rexAccountID.Split('@')[1]; } } /// /// The URL of the Avatar's Authentication Server /// Eg: http://authentication.com:10001/ /// public string RexAuthURL { get { return m_rexAuthURL; } set { m_rexAuthURL = value; } } public float RexCharacterSpeedMod { get { return m_RexCharacterSpeedMod; } set { m_RexCharacterSpeedMod = value; } } public float RexVertMovementSpeedMod { get { return m_RexVertMovementSpeedMod; } set { m_RexVertMovementSpeedMod = value; } } #endregion private void RexClientView_BinaryGenericMessage(Object sender, string method, byte[][] args) { if(method == "RexPrimData".ToLower()) { HandleRexPrimData(args); return; } } private void HandleRexPrimData(byte[][] args) { int rpdLen = 0; int idx = 0; bool first = false; UUID id = UUID.Zero; foreach (byte[] arg in args) { if(!first) { id = new UUID(Util.FieldToString(arg)); first = true; continue; } rpdLen += arg.Length; } first = false; byte[] rpdArray = new byte[rpdLen]; foreach (byte[] arg in args) { if(!first) { first = true; continue; } arg.CopyTo(rpdArray,idx); idx += arg.Length; } if (OnRexObjectProperties != null) OnRexObjectProperties(this, id, new RexObjectProperties(rpdArray)); } /// /// Special - used to convert GenericMessage packets /// to their appropriate Rex equivilents. /// /// Eg: GenericMessage(RexAppearance) -> /// OnRexAppearance(...) /// private void RealXtendClientView_OnGenericMessage(object sender, string method, List args) { RexGenericMessageDelegate handler; if (m_genericMessageHandlers.ContainsKey(method.ToLower())) { handler = m_genericMessageHandlers[method.ToLower()]; if (handler != null) { handler(this, args); } return; } if (method.ToLower() == "rexappearance") { if (OnRexAppearance != null) { OnRexAppearance(this); return; } } if (method == "rexscr") { if (OnRexClientScriptCmd != null) { OnRexClientScriptCmd(this, AgentId, args); return; } } if (method == "rexstartup") { if (OnRexStartUp != null) { OnRexStartUp(this, AgentId, args[0]); return; } } if (method == "rexprimdata") return; m_log.Warn("[REXCLIENT] Unhandled GenericMessage (" + method + ") {"); foreach (string s in args) { m_log.Warn("\t" + s); } m_log.Warn("}"); } private void HandleOnSkypeStore(IClientAPI sender, List args) { string skypeAddr = args[0]; this.RexSkypeURL = skypeAddr; } private void TriggerOnPrimFreeData(IClientAPI sender, List args) { try { //foreach (string s in args) //{ // m_log.Debug("[REXCLIENT] PrimFreeData: " + s); //} if (OnPrimFreeData != null) { OnPrimFreeData(this, args); } } catch (Exception e) { m_log.ErrorFormat("[REXCLIENT] Error parseing incoming prim free data. Exception: ", e); } } private void TriggerOnReceivedRexMediaURL(IClientAPI sender, List args) { try { foreach (string s in args) { m_log.Debug("[REXCLIENT] MediaURL: " + s); } UUID assetID = new UUID(args[0]); string mediaUrl = args[1]; byte refreshRate = Convert.ToByte(args[2]); if (OnReceiveRexMediaURL != null) { OnReceiveRexMediaURL(this, AgentId, assetID, mediaUrl, refreshRate); } } catch (Exception e) { m_log.ErrorFormat("[REXCLIENT] Error parseing incoming media url. Exception: ", e); } } public void SendRexObjectProperties(UUID id, RexObjectProperties x) { GenericMessagePacket gmp = new GenericMessagePacket(); gmp.MethodData.Method = Utils.StringToBytes("RexPrimData"); byte[] temprexprimdata = x.GetRexPrimDataToBytes(); int numlines = 0; int i = 0; if (temprexprimdata != null) { while (i <= temprexprimdata.Length) { numlines++; i += 200; } } gmp.ParamList = new GenericMessagePacket.ParamListBlock[1 + numlines]; gmp.ParamList[0] = new GenericMessagePacket.ParamListBlock(); gmp.ParamList[0].Parameter = Utils.StringToBytes(id.ToString()); for (i = 0; i < numlines; i++) { gmp.ParamList[i + 1] = new GenericMessagePacket.ParamListBlock(); if ((temprexprimdata.Length - i * 200) < 200) { gmp.ParamList[i + 1].Parameter = new byte[temprexprimdata.Length - i * 200]; Buffer.BlockCopy(temprexprimdata, i * 200, gmp.ParamList[i + 1].Parameter, 0, temprexprimdata.Length - i * 200); } else { gmp.ParamList[i + 1].Parameter = new byte[200]; Buffer.BlockCopy(temprexprimdata, i * 200, gmp.ParamList[i + 1].Parameter, 0, 200); } } // m_log.Warn("[REXDEBUG]: SendRexPrimData " + vPrimId.ToString()); OutPacket(gmp, ThrottleOutPacketType.Task); } /// /// Sends a Rex Script Command to the viewer /// attached to this ClientView. /// /// If you are coding something, try use /// SendRex*** instead, as many of them /// will trigger this instead with type /// and parameter checking. /// public void SendRexScriptCommand(string unit, string command, string parameters) { List pack = new List(); pack.Add(unit); pack.Add(command); if (!string.IsNullOrEmpty(parameters)) pack.Add(parameters); SendGenericMessage("RexScr", pack); } public void SendRexInventoryMessage(string message) { SendRexScriptCommand("hud", "ShowInventoryMessage(\"" + message + "\")", ""); } public void SendRexScrollMessage(string message, double time) { SendRexScriptCommand("hud", "ShowScrollMessage(\"" + message + "\", \"" + time + "\")", ""); } public void SendRexTutorialMessage(string message, double time) { SendRexScriptCommand("hud", "ShowScrollMessage(\"" + message + "\", \"" + time + "\")", ""); } public void SendRexFadeInAndOut(string message, double between, double time) { SendRexScriptCommand("hud", "ShowInventoryMessage(\"" + message + "\"," + " \"" + between + "\", \"" + time + "\")", ""); } public void SendRexFaceExpression(List expressionData) { expressionData.Insert(0, AgentId.ToString()); SendGenericMessage("RexFaceExpression", expressionData); } public void SendRexAppearance(UUID agentID, string avatarURL) { List pack = new List(); pack.Add(avatarURL); pack.Add(agentID.ToString()); SendGenericMessage("RexAppearance", pack); } /// /// Sends Fog parameters to client. Only works underwater. /// /// meters from camera where the fog starts /// meters from camera where the fog ends /// redness in fog /// greeness in fog /// blueness in fog public void SendRexFog(float start, float end, float red, float green, float blue) { List pack = new List(); pack.Add(start.ToString()); pack.Add(end.ToString()); pack.Add(red.ToString()); pack.Add(green.ToString()); pack.Add(blue.ToString()); SendGenericMessage("RexFog", pack); } /// /// Sends water height to client. Usually used when changing water height on the fly with scripting. /// /// Water height in meters public void SendRexWaterHeight(float height) { List pack = new List(); pack.Add(height.ToString()); SendGenericMessage("RexWaterHeight", pack); } /// /// Sends post postprosessing effect toggle to client. /// /// /// Id of the effect. See documentation for the effect ids /// True to set effect on. False to set effect off. public void SendRexPostProcess(int effectId, bool toggle) { List pack = new List(); pack.Add(effectId.ToString()); pack.Add(toggle.ToString()); SendGenericMessage("RexPostP", pack); } /// /// Creates client side rtt camera /// /// 0 to remove existing rtt camera (by name), 1 to add new rtt camera /// Unique identifier for the camera /// UUID of the texture that gets rendered to /// Position of the camera in the world /// Point in the world the camera will look at /// Width of the texture /// Height of the texture public void SendRexRttCamera(int command, string name, UUID assetId, Vector3 pos, Vector3 lookat, int width, int height) { string sPos = pos.X.ToString() + " " + pos.Y.ToString() + " " + pos.Z.ToString(); sPos = sPos.Replace(",", "."); string sLookAt = lookat.X.ToString() + " " + lookat.Y.ToString() + " " + lookat.Z.ToString(); sLookAt = sLookAt.Replace(",", "."); List pack = new List(); pack.Add(command.ToString()); pack.Add(name); pack.Add(assetId.ToString()); pack.Add(sPos); pack.Add(sLookAt); pack.Add(width.ToString()); pack.Add(height.ToString()); SendGenericMessage("RexRttCam", pack); } /// /// Sends a viewport to client /// /// 0 to remove existing viewport (by name), 1 to add new viewport. /// Unique identifier for the viewport /// screen relative position of the left edge of the viewport /// screen relative position of the top edge of the viewport /// screen relative width of the viewport /// screen relative height of the viewport public void SendRexViewport(int command, string name, float posX, float posY, float width, float height) { List pack = new List(); pack.Add(command.ToString()); pack.Add(name); pack.Add(posX.ToString()); pack.Add(posY.ToString()); pack.Add(width.ToString()); pack.Add(height.ToString()); SendGenericMessage("RexSetViewport", pack); } /// /// Toggles the wind sound on client /// /// public void SendRexToggleWindSound(bool toggle) { List pack = new List(); pack.Add(toggle.ToString()); SendGenericMessage("RexToggleWindSound", pack); } /// /// Sends Rex clientside camera effects, particle script attached to camera etc. /// /// True to enable the effect, False to disable /// Id of the effect /// Offset position from the camera /// Offset rotation from the camera public void SendRexCameraClientSideEffect(bool enable, UUID assetId, Vector3 pos, Quaternion rot) { List pack = new List(); pack.Add(assetId.ToString()); pack.Add(pos.ToString()); pack.Add(rot.ToString()); pack.Add(enable.ToString()); SendGenericMessage("RexSCSEffect", pack); } /// /// Overrides default lighting conditions and ambient light in the world. /// /// Note that this override is a hard one. The user will be unable to change the lighting /// conditions in any way after they are overridden. /// /// Direction of the global light (sun) /// Colour of the global light /// Colour of the ambient light (the light that is always present) public void SendRexSetAmbientLight(Vector3 direction, Vector3 colour, Vector3 ambientColour) { List pack = new List(); string slightDirection = direction.X.ToString() + " " + direction.Y.ToString() + " " + direction.Z.ToString(); slightDirection = slightDirection.Replace(",", "."); string slightColour = colour.X.ToString() + " " + colour.Y.ToString() + " " + colour.Z.ToString(); slightColour = slightColour.Replace(",", "."); string sambientColour = ambientColour.X.ToString() + " " + ambientColour.Y.ToString() + " " + ambientColour.Z.ToString(); sambientColour = sambientColour.Replace(",", "."); pack.Add(slightDirection); pack.Add(slightColour); pack.Add(sambientColour); SendGenericMessage("RexAmbientL", pack); } /// /// Lauch flash animation to play on client /// /// Id of the flash animation (swf) to play /// left border of the rectangle /// top border of the rectangle /// right border of the rectangle /// bottom border of the rectangle /// time in seconds from start of animation playback until the flash control is destroyed public void SendRexPlayFlashAnimation(UUID assetId, float left, float top, float right, float bottom, float timeToDeath) { List pack = new List(); pack.Add(assetId.ToString()); pack.Add(left.ToString()); pack.Add(top.ToString()); pack.Add(right.ToString()); pack.Add(bottom.ToString()); pack.Add(timeToDeath.ToString()); SendGenericMessage("RexFlashAnim", pack); } /// /// Sends preload avatar assets /// /// List of avatar assets public void SendRexPreloadAvatarAssets(List assetList) { try { List pack = new List(); foreach (string avatarUrl in assetList) { pack.Add(avatarUrl); } SendGenericMessage("RexPreloadAppearance", pack); } catch (Exception exep) { m_log.Error("[REXCLIENT]: SendRexPreloadAvatarAssets fail:" + exep.ToString()); } } /// /// Force Field Of View /// /// Field of View in degrees. This parameter is irrelevant when disabling FOV /// True to enable, False to disable public void SendRexForceFOV(float fov, bool enable) { List pack = new List(); pack.Add(fov.ToString()); pack.Add(enable.ToString()); SendGenericMessage("RexForceFOV", pack); } /// /// Sends Forced Camera mode to client /// /// 1 = 1st person mode, 3 = 3rd person mode, 0 = no limits /// Minimum zoom (0.0-1.0) /// Maximum zoom (0.0-1.0) public void SendRexForceCamera(int forceMode, float minZoom, float maxZoom) { List pack = new List(); pack.Add(forceMode.ToString()); pack.Add(minZoom.ToString()); pack.Add(maxZoom.ToString()); SendGenericMessage("RexForceCamera", pack); } /// /// Sends the sky to user /// /// Type of the sky: 0 = none, 1 = skybox, 2 = skydome /// List of image uuids - separated by space - to use for the sky. /// Skyboxes need 6 images, skydomes take one image. /// You can add suffix to the uuids to specify which side the texture should go to: /// _fr front, _lf left, _rt right, _bk back, _up up, _dn down /// Curvature of the skydome. Values around 10.0 are good for /// open spaces and landscapes. Not used with skyboxes /// Skydome tiling. Not used with skyboxes. public void SendRexSky(int type, string images, float curvature, float tiling) { List pack = new List(); pack.Add(type.ToString()); pack.Add(images); pack.Add(curvature.ToString()); pack.Add(tiling.ToString()); SendGenericMessage("RexSky", pack); } /// /// Sends list of preloaded assets to user /// /// public void SendRexPreloadAssets(Dictionary assetList) { try { List pack = new List(); string assetline = String.Empty; foreach (UUID materialUUID in assetList.Keys) { assetline = assetList[materialUUID] + " " + materialUUID.ToString(); pack.Add(assetline); } SendGenericMessage("RexPreloadAssets", pack); } catch (Exception exep) { m_log.Error("[REXCLIENT]: SendRexPreloadAssets fail:" + exep.ToString()); } } /// /// Sends MediaURL to client /// /// UUID of the asset which to replace with MediaURL content /// URL pointing to web-page or vnc server /// How many times per second to refresh the texture public void SendMediaURL(UUID assetId, string mediaURL, byte refreshRate) { if (mediaURL == null) { m_log.Warn("[REXCLIENT]: Did not send media url to user, because it was null"); return; } List pack = new List(); pack.Add(assetId.ToString()); pack.Add(mediaURL); pack.Add(refreshRate.ToString()); SendGenericMessage("RexMediaUrl", pack); } public void RexIKSendLimbTarget(UUID agentID, int limbId, Vector3 destination, float timeToTarget, float stayTime, float constraintAngle, string startAnim, string targetAnim, string endAnim) { List pack = new List(); pack.Add("0"); pack.Add(agentID.ToString()); pack.Add(limbId.ToString()); string sDest = destination.X.ToString() + " " + destination.Y.ToString() + " " + destination.Z.ToString(); sDest = sDest.Replace(",", "."); pack.Add(sDest); pack.Add(timeToTarget.ToString()); pack.Add(stayTime.ToString()); pack.Add(constraintAngle.ToString()); pack.Add(startAnim); pack.Add(targetAnim); pack.Add(endAnim); SendGenericMessage("RexIK", pack); } public void SendRexAvatarAnimation(UUID agentID, string animName, float rate, float fadeIn, float fadeOut, int repeats, bool stopAnim) { List pack = new List(); pack.Add(agentID.ToString()); pack.Add(animName); pack.Add(rate.ToString()); pack.Add(fadeIn.ToString().Replace(",",".")); pack.Add(fadeOut.ToString().Replace(",", ".")); pack.Add(repeats.ToString()); pack.Add(stopAnim.ToString()); SendGenericMessage("RexAnim", pack); } public void SendRexAvatarMorph(UUID agentID, string morphName, float weight, float time) { List pack = new List(); pack.Add(agentID.ToString()); pack.Add(morphName); pack.Add(weight.ToString()); pack.Add(time.ToString()); SendGenericMessage("RexMorph", pack); } /// /// Send Mesh Animation command to client /// /// id of the primitive /// Name of the animation to launch /// Speed of the animation, where 1.0 is default speed /// True to make the animation looped, false to play it only once /// True to stop the animation, false to launch the animation public void SendRexMeshAnimation(UUID primId, string animationName, float rate, bool loop, bool stopAnimation) { List pack = new List(); pack.Add(primId.ToString()); pack.Add(animationName); pack.Add(rate.ToString()); pack.Add(loop.ToString()); pack.Add(stopAnimation.ToString()); SendGenericMessage("RexPrimAnim", pack); } /// /// Send Client side effect to client /// /// Id of the asset /// Time in seconds until the effect is launched on the client. /// Set to zero to launch immediatelly /// The duration of the effect. The particle system gets completely /// destroyed after this duration. The duration is counted after the effect is launched. /// Position of the effect in the world /// Rotation of the particle system. Affects it's movement if speed > 0 /// Speed at which the particle system moves. The system moves at the direction /// specified by rot. Set to zero to make it stationary. public void SendRexClientSideEffect(string assetId, float timeUntilLaunch, float timeUntilDeath, Vector3 pos, Quaternion rot, float speed) { List pack = new List(); pack.Add(assetId.ToString()); pack.Add(timeUntilLaunch.ToString().Replace(",", ".")); pack.Add(timeUntilDeath.ToString().Replace(",", ".")); string sPos = pos.X.ToString() + " " + pos.Y.ToString() + " " + pos.Z.ToString(); sPos = sPos.Replace(",", "."); pack.Add(sPos); string sRot = rot.X.ToString() + " " + rot.Y.ToString() + " " + rot.Z.ToString() + " " + rot.W.ToString(); sRot = sRot.Replace(",", "."); pack.Add(sRot); pack.Add(speed.ToString().Replace(",", ".")); SendGenericMessage("RexCSEffect", pack); } public void SendSkypeAddress(UUID agentID, string skypeAddress) { List pack = new List(); pack.Add(skypeAddress); pack.Add(agentID.ToString()); SendGenericMessage("SkypeAdderss", pack); } public override void InformClientOfNeighbour(ulong neighbourHandle, IPEndPoint neighbourExternalEndPoint) { IRexUDPPort module = m_scene.RequestModuleInterface(); int udpport = module.GetPort(neighbourHandle); m_log.DebugFormat("[REXCLIENT]: Informing Client About Neighbour {0}", neighbourExternalEndPoint); base.InformClientOfNeighbour(neighbourHandle, new IPEndPoint(neighbourExternalEndPoint.Address, udpport)); } public override void CrossRegion(ulong newRegionHandle, Vector3 pos, Vector3 lookAt, IPEndPoint externalIPEndPoint, string capsURL) { IRexUDPPort module = m_scene.RequestModuleInterface(); int udpport = module.GetPort(newRegionHandle); m_log.DebugFormat("[REXCLIENT]: Crossing client to region {0}", externalIPEndPoint); base.CrossRegion(newRegionHandle, pos, lookAt, new IPEndPoint(externalIPEndPoint.Address, udpport), capsURL); } public override void SendRegionTeleport(ulong regionHandle, byte simAccess, IPEndPoint newRegionEndPoint, uint locationID, uint flags, string capsURL) { IRexUDPPort module = m_scene.RequestModuleInterface(); int udpport = module.GetPort(regionHandle); m_log.DebugFormat("[REXCLIENT]: Sending region teleport to client {0}", newRegionEndPoint); base.SendRegionTeleport(regionHandle, simAccess, new IPEndPoint(newRegionEndPoint.Address, udpport), locationID, flags, capsURL); } #region Avatar terse update protected override void InitNewClient() { m_avatarTerseUpdateTimer = new System.Timers.Timer(m_avatarTerseUpdateRate); m_avatarTerseUpdateTimer.Elapsed += new ElapsedEventHandler(ProcessAvatarTerseUpdates); m_avatarTerseUpdateTimer.AutoReset = false; base.InitNewClient(); } private System.Timers.Timer m_avatarTerseUpdateTimer; private List m_avatarTerseUpdates = new List(); /// /// Send a terse positional/rotation/velocity update about an avatar /// to the client. This avatar can be that of the client itself. /// public override void SendAvatarTerseUpdate(ulong regionHandle, ushort timeDilation, uint localID, Vector3 position, Vector3 velocity, Quaternion rotation, UUID agentid) { if (rotation.X == rotation.Y && rotation.Y == rotation.Z && rotation.Z == rotation.W && rotation.W == 0) rotation = Quaternion.Identity; position.Z = (float)(position.Z - 0.15); ImprovedTerseObjectUpdatePacket.ObjectDataBlock terseBlock = RexCreateAvatarImprovedBlock(localID, position, velocity, rotation); //CreateAvatarImprovedBlock(localID, position, velocity, rotation); lock (m_avatarTerseUpdates) { m_avatarTerseUpdates.Add(terseBlock); // If packet is full or own movement packet, send it. if (m_avatarTerseUpdates.Count >= m_avatarTerseUpdatesPerPacket) { ProcessAvatarTerseUpdates(this, null); } else if (m_avatarTerseUpdates.Count == 1) { m_avatarTerseUpdateTimer.Start(); } } } private void ProcessAvatarTerseUpdates(object sender, ElapsedEventArgs e) { lock (m_avatarTerseUpdates) { ImprovedTerseObjectUpdatePacket terse = (ImprovedTerseObjectUpdatePacket)PacketPool.Instance.GetPacket(PacketType.ImprovedTerseObjectUpdate); terse.RegionData = new ImprovedTerseObjectUpdatePacket.RegionDataBlock(); terse.RegionData.RegionHandle = Scene.RegionInfo.RegionHandle; terse.RegionData.TimeDilation = (ushort)(Scene.TimeDilation * ushort.MaxValue); int max = m_avatarTerseUpdatesPerPacket; if (max > m_avatarTerseUpdates.Count) max = m_avatarTerseUpdates.Count; int count = 0; int size = 0; byte[] zerobuffer = new byte[1024]; byte[] blockbuffer = new byte[1024]; for (count = 0; count < max; count++) { int length = 0; m_avatarTerseUpdates[count].ToBytes(blockbuffer, ref length); length = Helpers.ZeroEncode(blockbuffer, length, zerobuffer); if (size + length > m_packetMTU) break; size += length; } terse.ObjectData = new ImprovedTerseObjectUpdatePacket.ObjectDataBlock[count]; for (int i = 0; i < count; i++) { terse.ObjectData[i] = m_avatarTerseUpdates[0]; m_avatarTerseUpdates.RemoveAt(0); } terse.Header.Reliable = false; terse.Header.Zerocoded = true; OutPacket(terse, ThrottleOutPacketType.Task); if (m_avatarTerseUpdates.Count == 0) m_avatarTerseUpdateTimer.Stop(); } } /// /// Creates compressed avatar terse update block. This is about half smaller than the orginal SL /// /// /// /// /// /// protected ImprovedTerseObjectUpdatePacket.ObjectDataBlock RexCreateAvatarImprovedBlock(uint localID, Vector3 pos, Vector3 velocity, Quaternion rotation) { byte[] bytes = new byte[30]; int i = 0; ImprovedTerseObjectUpdatePacket.ObjectDataBlock dat = PacketPool.GetDataBlock(); dat.TextureEntry = new byte[0]; uint ID = localID; bytes[i++] = (byte)(ID % 256); bytes[i++] = (byte)((ID >> 8) % 256); bytes[i++] = (byte)((ID >> 16) % 256); bytes[i++] = (byte)((ID >> 24) % 256); // pos byte[] pb = pos.GetBytes(); Array.Copy(pb, 0, bytes, i, pb.Length); i += 12; // velocity velocity = velocity / 128.0f; velocity.X += 1; velocity.Y += 1; velocity.Z += 1; ushort InternVelocityX = (ushort)(32768 * velocity.X); ushort InternVelocityY = (ushort)(32768 * velocity.Y); ushort InternVelocityZ = (ushort)(32768 * velocity.Z); bytes[i++] = (byte)(InternVelocityX % 256); bytes[i++] = (byte)((InternVelocityX >> 8) % 256); bytes[i++] = (byte)(InternVelocityY % 256); bytes[i++] = (byte)((InternVelocityY >> 8) % 256); bytes[i++] = (byte)(InternVelocityZ % 256); bytes[i++] = (byte)((InternVelocityZ >> 8) % 256); //rotation ushort rw = (ushort)(32768 * (rotation.W + 1)); ushort rx = (ushort)(32768 * (rotation.X + 1)); ushort ry = (ushort)(32768 * (rotation.Y + 1)); ushort rz = (ushort)(32768 * (rotation.Z + 1)); //rot bytes[i++] = (byte)(rx % 256); bytes[i++] = (byte)((rx >> 8) % 256); bytes[i++] = (byte)(ry % 256); bytes[i++] = (byte)((ry >> 8) % 256); bytes[i++] = (byte)(rz % 256); bytes[i++] = (byte)((rz >> 8) % 256); bytes[i++] = (byte)(rw % 256); bytes[i++] = (byte)((rw >> 8) % 256); dat.Data = bytes; return (dat); } #endregion } }