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; namespace ModularRex.RexNetwork { public delegate void RexGenericMessageDelegate(RexClientView sender, List parameters); public delegate void RexAppearanceDelegate(RexClientView sender); public delegate void RexObjectPropertiesDelegate(RexClientView sender, UUID id, RexObjectProperties props); public delegate void RexStartUpDelegate(RexClientView remoteClient, UUID agentID, string status); public delegate void RexClientScriptCmdDelegate(RexClientView remoteClient, UUID agentID, List parameters); public delegate void ReceiveRexMediaURL(IClientAPI remoteClient, UUID agentID, UUID itemID, string mediaURL, byte refreshRate); /// /// 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 { 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_rexAuthURL; private string m_rexSkypeURL; public string AvatarStorageOverride; public float RexCharacterSpeedMod = 1.0f; public float RexMovementSpeedMod = 1.0f; public float RexVertMovementSpeedMod = 1.0f; public bool RexWalkDisabled = false; public bool RexFlyDisabled = false; public bool RexSitDisabled = false; 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 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); AddGenericPacketHandler("RexPrimData", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexData", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("RexMediaUrl", RealXtendClientView_OnGenericMessage); AddGenericPacketHandler("rexstartup", RealXtendClientView_OnGenericMessage); m_genericMessageHandlers.Add("rexfaceexpression", OnRexFaceExpression); m_genericMessageHandlers.Add("rexavatarprop", OnRexAvatarProperties); m_genericMessageHandlers.Add("rexmediaurl", TriggerOnReceivedRexMediaURL); } /// /// 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); // Register our own class 'as-is' so it can be // used via IClientCore.Get()... RegisterInterface(this); base.RegisterInterfaces(); } /// /// 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; } } } /// /// Skype username of the avatar /// eg: Skypeuser /// public string RexSkypeURL { get { return m_rexSkypeURL; } set { m_rexSkypeURL = value; } } /// /// 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; // Request Agent Properties Asynchronously ThreadPool.QueueUserWorkItem(RequestProperties); } } 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 == "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; } } m_log.Warn("[REXCLIENT] Unhandled GenericMessage (" + method + ") {"); foreach (string s in args) { m_log.Warn("\t" + s); } m_log.Warn("}"); } 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); } /// /// Requests properties about this agent from their /// authentication server. This should be run in /// an async thread. /// /// Note that this particular function may set the /// avatars appearance which may in turn call /// additional modules and functions elsewhere. /// /// private void RequestProperties(object o) { m_log.Info("[REXCLIENT] Resolving avatar..."); Hashtable ReqVals = new Hashtable(); ReqVals["avatar_account"] = RexAccount; ReqVals["AuthenticationAddress"] = RexAuthURL; ArrayList SendParams = new ArrayList(); SendParams.Add(ReqVals); XmlRpcRequest req = new XmlRpcRequest("get_user_by_account", SendParams); m_log.Info("[REXCLIENT] Sending XMLRPC Request to http://" + RexAuthURL); XmlRpcResponse authreply = req.Send("http://" + RexAuthURL, 9000); //m_log.Info(authreply.ToString()); if (!((Hashtable)authreply.Value).ContainsKey("error_type")) { string rexAsAddress = ((Hashtable)authreply.Value)["as_address"].ToString(); //string rexSkypeURL = ((Hashtable)authreply.Value)["skype_url"].ToString(); UUID userID = new UUID(((Hashtable) authreply.Value)["uuid"].ToString()); // Sanity check if (userID == AgentId) { RexAvatarURL = rexAsAddress; //RexSkypeURL = rexSkypeURL; } } else { m_log.Warn("[REXCLIENT]: User not found"); } } /// /// 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(); pack.Add(direction.ToString()); pack.Add(colour.ToString()); pack.Add(ambientColour.ToString()); 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 override void InformClientOfNeighbour(ulong neighbourHandle, IPEndPoint neighbourExternalEndPoint) //{ // m_log.Debug("[REXCLIENT]: Informing Client About Neighbour"); // neighbourExternalEndPoint.Port = neighbourExternalEndPoint.Port - 2000; // base.InformClientOfNeighbour(neighbourHandle, neighbourExternalEndPoint); //} } }