diff --git a/OpenSim/Client/MXP/ClientStack/MXPClientView.cs b/OpenSim/Client/MXP/ClientStack/MXPClientView.cs index 7b9faa98d1..f8cfe448f7 100644 --- a/OpenSim/Client/MXP/ClientStack/MXPClientView.cs +++ b/OpenSim/Client/MXP/ClientStack/MXPClientView.cs @@ -598,6 +598,7 @@ namespace OpenSim.Client.MXP.ClientStack public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; diff --git a/OpenSim/Client/Sirikata/ClientStack/SirikataClientView.cs b/OpenSim/Client/Sirikata/ClientStack/SirikataClientView.cs index b7ead6eeab..3bf73d651d 100644 --- a/OpenSim/Client/Sirikata/ClientStack/SirikataClientView.cs +++ b/OpenSim/Client/Sirikata/ClientStack/SirikataClientView.cs @@ -244,6 +244,7 @@ namespace OpenSim.Client.Sirikata.ClientStack public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; diff --git a/OpenSim/Client/VWoHTTP/ClientStack/VWHClientView.cs b/OpenSim/Client/VWoHTTP/ClientStack/VWHClientView.cs index d51277b9ea..894fdac832 100644 --- a/OpenSim/Client/VWoHTTP/ClientStack/VWHClientView.cs +++ b/OpenSim/Client/VWoHTTP/ClientStack/VWHClientView.cs @@ -247,6 +247,7 @@ namespace OpenSim.Client.VWoHTTP.ClientStack public event Action OnRegionHandShakeReply = delegate { }; public event GenericCall2 OnRequestWearables = delegate { }; public event GenericCall1 OnCompleteMovementToRegion = delegate { }; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate = delegate { }; public event AgentRequestSit OnAgentRequestSit = delegate { }; public event AgentSit OnAgentSit = delegate { }; diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 7219bf8c68..3126e57330 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -881,6 +881,7 @@ namespace OpenSim.Framework event Action OnRegionHandShakeReply; event GenericCall2 OnRequestWearables; event GenericCall1 OnCompleteMovementToRegion; + event UpdateAgent OnPreAgentUpdate; event UpdateAgent OnAgentUpdate; event AgentRequestSit OnAgentRequestSit; event AgentSit OnAgentSit; diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs index 2c8d88b5df..ea73abb587 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs @@ -127,6 +127,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP public event ObjectDeselect OnObjectDetach; public event ObjectDrop OnObjectDrop; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; @@ -4880,7 +4881,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP UpdateAgent handlerAgentUpdate = OnAgentUpdate; lastarg = arg; // save this set of arguments for nexttime if (handlerAgentUpdate != null) + { + OnPreAgentUpdate(this, arg); OnAgentUpdate(this, arg); + } handlerAgentUpdate = null; } diff --git a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs index 8c3d17d82c..e0cdb36efe 100644 --- a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using log4net; using Nini.Config; @@ -86,6 +87,7 @@ namespace OpenSim.Region.CoreModules.World.Land // caches ExtendedLandData private Cache parcelInfoCache; + private Vector3? forcedPosition = null; #region INonSharedRegionModule Members @@ -144,6 +146,13 @@ namespace OpenSim.Region.CoreModules.World.Land { } + private bool OnVerifyUserConnection(ScenePresence scenePresence, out string reason) + { + ILandObject nearestParcel = m_scene.GetNearestAllowedParcel(scenePresence.UUID, scenePresence.AbsolutePosition.X, scenePresence.AbsolutePosition.Y); + reason = "You are not allowed to enter this sim."; + return nearestParcel != null; + } + void EventManagerOnNewClient(IClientAPI client) { //Register some client events @@ -161,6 +170,7 @@ namespace OpenSim.Region.CoreModules.World.Land client.OnParcelInfoRequest += ClientOnParcelInfoRequest; client.OnParcelDwellRequest += ClientOnParcelDwellRequest; client.OnParcelDeedToGroup += ClientOnParcelDeedToGroup; + client.OnPreAgentUpdate += ClientOnPreAgentUpdate; EntityBase presenceEntity; if (m_scene.Entities.TryGetValue(client.AgentId, out presenceEntity) && presenceEntity is ScenePresence) @@ -170,6 +180,40 @@ namespace OpenSim.Region.CoreModules.World.Land } } + void ClientOnPreAgentUpdate(IClientAPI remoteClient, AgentUpdateArgs agentData) + { + //If we are forcing a position for them to go + if( forcedPosition != null ) + { + ScenePresence clientAvatar = m_scene.GetScenePresence(remoteClient.AgentId); + + //Putting the user into flying, both keeps the avatar in fligth when it bumps into something and stopped from going another direction AND + //When the avatar walks into a ban line on the ground, it prevents getting stuck + agentData.ControlFlags = (uint)AgentManager.ControlFlags.AGENT_CONTROL_FLY; + + + //Make sure we stop if they get about to the right place to prevent yoyo and prevents getting stuck on banlines + if (Vector3.Distance(clientAvatar.AbsolutePosition, forcedPosition.Value) < .2) + { + Debug.WriteLine(string.Format("Stopping force position because {0} is close enough to position {1}", forcedPosition.Value, clientAvatar.AbsolutePosition)); + forcedPosition = null; + } + //if we are far away, teleport + else if(Vector3.Distance(clientAvatar.AbsolutePosition,forcedPosition.Value) > 3 ) + { + Debug.WriteLine(string.Format("Teleporting out because {0} is too far from avatar position {1}",forcedPosition.Value,clientAvatar.AbsolutePosition)); + clientAvatar.Teleport(forcedPosition.Value); + forcedPosition = null; + } + else + { + //Forces them toward the forced position we want if they aren't there yet + agentData.UseClientAgentPosition = true; + agentData.ClientAgentPosition = forcedPosition.Value; + } + } + } + public void PostInitialise() { @@ -275,9 +319,6 @@ namespace OpenSim.Region.CoreModules.World.Land { avatar.ControllingClient.SendAlertMessage( "You are not allowed on this parcel because you are banned. Please go away."); - - avatar.PhysicsActor.Position = avatar.lastKnownAllowedPosition; - avatar.PhysicsActor.Velocity = Vector3.Zero; } else { @@ -286,6 +327,24 @@ namespace OpenSim.Region.CoreModules.World.Land } } + + + private void ForceAvatarToPosition(ScenePresence avatar, Vector3? position) + { + if (m_scene.Permissions.IsGod(avatar.UUID)) return; + if (position.HasValue) + { + forcedPosition = position; + } + } + + public void SendYouAreRestrictedNotice(ScenePresence avatar) + { + avatar.ControllingClient.SendAlertMessage( + "You are not allowed on this parcel because the land owner has restricted access."); + + } + public void EventManagerOnAvatarEnteringNewParcel(ScenePresence avatar, int localLandID, UUID regionID) { if (m_scene.RegionInfo.RegionID == regionID) @@ -303,11 +362,12 @@ namespace OpenSim.Region.CoreModules.World.Land if (parcelAvatarIsEntering.IsBannedFromLand(avatar.UUID)) { SendYouAreBannedNotice(avatar); + ForceAvatarToPosition(avatar, m_scene.GetNearestAllowedPosition(avatar)); } else if (parcelAvatarIsEntering.IsRestrictedFromLand(avatar.UUID)) { - avatar.ControllingClient.SendAlertMessage( - "You are not allowed on this parcel because the land owner has restricted access. For now, you can enter, but please respect the land owner's decisions (or he can ban you!)."); + SendYouAreRestrictedNotice(avatar); + ForceAvatarToPosition(avatar, m_scene.GetNearestAllowedPosition(avatar)); } else { @@ -408,7 +468,26 @@ namespace OpenSim.Region.CoreModules.World.Land else if (clientAvatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT && parcel.IsBannedFromLand(clientAvatar.UUID)) { - SendYouAreBannedNotice(clientAvatar); + //once we've sent the message once, keep going toward the target until we are done + if (forcedPosition == null) + { + SendYouAreBannedNotice(clientAvatar); + ForceAvatarToPosition(clientAvatar, m_scene.GetNearestAllowedPosition(clientAvatar)); + } + } + else if ( parcel.IsRestrictedFromLand(clientAvatar.UUID)) + { + //once we've sent the message once, keep going toward the target until we are done + if (forcedPosition == null) + { + SendYouAreRestrictedNotice(clientAvatar); + ForceAvatarToPosition(clientAvatar, m_scene.GetNearestAllowedPosition(clientAvatar)); + } + } + else + { + //when we are finally in a safe place, lets release the forced position lock + forcedPosition = null; } } } @@ -420,7 +499,7 @@ namespace OpenSim.Region.CoreModules.World.Land ILandObject over = GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); if (over != null) { - if (!over.IsBannedFromLand(avatar.UUID) || avatar.AbsolutePosition.Z >= LandChannel.BAN_LINE_SAFETY_HIEGHT) + if (!over.IsRestrictedFromLand(avatar.UUID) && (!over.IsBannedFromLand(avatar.UUID) || avatar.AbsolutePosition.Z >= LandChannel.BAN_LINE_SAFETY_HIEGHT)) { avatar.lastKnownAllowedPosition = new Vector3(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z); diff --git a/OpenSim/Region/CoreModules/World/Land/LandObject.cs b/OpenSim/Region/CoreModules/World/Land/LandObject.cs index 1fa8630cbc..27d9fdb5aa 100644 --- a/OpenSim/Region/CoreModules/World/Land/LandObject.cs +++ b/OpenSim/Region/CoreModules/World/Land/LandObject.cs @@ -104,7 +104,7 @@ namespace OpenSim.Region.CoreModules.World.Land /// Returns true if the piece of land contains the specified point public bool ContainsPoint(int x, int y) { - if (x >= 0 && y >= 0 && x <= Constants.RegionSize && x <= Constants.RegionSize) + if (x >= 0 && y >= 0 && x <= Constants.RegionSize && y <= Constants.RegionSize) { return (LandBitmap[x / 4, y / 4] == true); } @@ -286,7 +286,8 @@ namespace OpenSim.Region.CoreModules.World.Land entry.AgentID = avatar; entry.Flags = AccessList.Ban; entry.Time = new DateTime(); - if (LandData.ParcelAccessList.Contains(entry)) + //See if they are on the list, but make sure the owner isn't banned + if (LandData.ParcelAccessList.Contains(entry) && LandData.OwnerID != avatar ) { //They are banned, so lets send them a notice about this parcel return true; @@ -303,7 +304,9 @@ namespace OpenSim.Region.CoreModules.World.Land entry.AgentID = avatar; entry.Flags = AccessList.Access; entry.Time = new DateTime(); - if (!LandData.ParcelAccessList.Contains(entry)) + + //If they are not on the access list and are not the owner + if (!LandData.ParcelAccessList.Contains(entry) && LandData.OwnerID != avatar) { //They are not allowed in this parcel, but not banned, so lets send them a notice about this parcel return true; diff --git a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs index 58e3de96c5..c2753d9c0d 100644 --- a/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs +++ b/OpenSim/Region/Examples/SimpleModule/MyNpcCharacter.cs @@ -84,6 +84,7 @@ namespace OpenSim.Region.Examples.SimpleModule public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 388bc99176..884f13ad32 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; @@ -3251,6 +3252,7 @@ namespace OpenSim.Region.Framework.Scenes /// also return a reason. public bool NewUserConnection(AgentCircuitData agent, uint teleportFlags, out string reason) { + TeleportFlags tp = (TeleportFlags)teleportFlags; //Teleport flags: // // TeleportFlags.ViaGodlikeLure - Border Crossing @@ -3284,6 +3286,17 @@ namespace OpenSim.Region.Framework.Scenes CapsModule.NewUserConnection(agent); + ILandObject land = LandChannel.GetLandObject(agent.startpos.X, agent.startpos.Y); + + //On login or border crossing test land permisions + if (tp != TeleportFlags.Default) + { + if (land != null && !TestLandRestrictions(agent, land, out reason)) + { + return false; + } + } + ScenePresence sp = m_sceneGraph.GetScenePresence(agent.AgentID); if (sp != null) { @@ -3377,6 +3390,40 @@ namespace OpenSim.Region.Framework.Scenes return true; } + private bool TestLandRestrictions(AgentCircuitData agent, ILandObject land, out string reason) + { + + bool banned = land.IsBannedFromLand(agent.AgentID); + bool restricted = land.IsRestrictedFromLand(agent.AgentID); + + if (banned || restricted) + { + ILandObject nearestParcel = GetNearestAllowedParcel(agent.AgentID, agent.startpos.X, agent.startpos.Y); + if (nearestParcel != null) + { + //Move agent to nearest allowed + Vector3 newPosition = GetParcelCenterAtGround(nearestParcel); + agent.startpos.X = newPosition.X; + agent.startpos.Y = newPosition.Y; + } + else + { + if (banned) + { + reason = "Cannot regioncross into banned parcel."; + } + else + { + reason = String.Format("Denied access to private region {0}: You are not on the access list for that region.", + RegionInfo.RegionName); + } + return false; + } + } + reason = ""; + return true; + } + /// /// Verifies that the user has a presence on the Grid /// @@ -3508,6 +3555,18 @@ namespace OpenSim.Region.Framework.Scenes return true; } + private ILandObject GetParcelAtPoint(float x, float y) + { + foreach (var parcel in AllParcels()) + { + if( parcel.ContainsPoint((int)x,(int)y)) + { + return parcel; + } + } + return null; + } + /// /// Update an AgentCircuitData object with new information /// @@ -4780,5 +4839,175 @@ namespace OpenSim.Region.Framework.Scenes { get { return m_allowScriptCrossings; } } + + public Vector3? GetNearestAllowedPosition(ScenePresence avatar) + { + //simulate to make sure we have pretty up to date positions + PhysicsScene.Simulate(0); + + ILandObject nearestParcel = GetNearestAllowedParcel(avatar.UUID, avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + + if (nearestParcel != null) + { + Vector3 dir = Vector3.Normalize(Vector3.Multiply(avatar.Velocity, -1)); + //Try to get a location that feels like where they came from + Vector3? nearestPoint = GetNearestPointInParcelAlongDirectionFromPoint(avatar.AbsolutePosition, dir, nearestParcel); + if (nearestPoint != null) + { + Debug.WriteLine("Found a sane previous position based on velocity, sending them to: " + nearestPoint.ToString()); + return nearestPoint.Value; + } + + //Sometimes velocity might be zero (local teleport), so try finding point along path from avatar to center of nearest parcel + Vector3 directionToParcelCenter = Vector3.Subtract(GetParcelCenterAtGround(nearestParcel), avatar.AbsolutePosition); + dir = Vector3.Normalize(directionToParcelCenter); + nearestPoint = GetNearestPointInParcelAlongDirectionFromPoint(avatar.AbsolutePosition, dir, nearestParcel); + if (nearestPoint != null) + { + Debug.WriteLine("They had a zero velocity, sending them to: " + nearestPoint.ToString()); + return nearestPoint.Value; + } + + //Ultimate backup if we have no idea where they are + Debug.WriteLine("Have no idea where they are, sending them to: " + avatar.lastKnownAllowedPosition.ToString()); + return avatar.lastKnownAllowedPosition; + + } + + //Go to the edge, this happens in teleporting to a region with no available parcels + Vector3 nearestRegionEdgePoint = GetNearestRegionEdgePosition(avatar); + //Debug.WriteLine("They are really in a place they don't belong, sending them to: " + nearestRegionEdgePoint.ToString()); + return nearestRegionEdgePoint; + return null; + } + + private Vector3 GetParcelCenterAtGround(ILandObject parcel) + { + Vector2 center = GetParcelCenter(parcel); + return GetPositionAtGround(center.X, center.Y); + } + + private Vector3? GetNearestPointInParcelAlongDirectionFromPoint(Vector3 pos, Vector3 direction, ILandObject parcel) + { + Vector3 unitDirection = Vector3.Normalize(direction); + //Making distance to search go through some sane limit of distance + for (float distance = 0; distance < Constants.RegionSize * 2; distance += .5f) + { + Vector3 testPos = Vector3.Add(pos, Vector3.Multiply(unitDirection, distance)); + if (parcel.ContainsPoint((int)testPos.X, (int)testPos.Y)) + { + return testPos; + } + } + return null; + } + + public ILandObject GetNearestAllowedParcel(UUID avatarId, float x, float y) + { + List all = AllParcels(); + float minParcelDistance = float.MaxValue; + ILandObject nearestParcel = null; + + foreach (var parcel in all) + { + if (!parcel.IsEitherBannedOrRestricted(avatarId)) + { + float parcelDistance = GetParcelDistancefromPoint(parcel, x, y); + if (parcelDistance < minParcelDistance) + { + minParcelDistance = parcelDistance; + nearestParcel = parcel; + } + } + } + + return nearestParcel; + } + + private List AllParcels() + { + return LandChannel.AllParcels(); + } + + private float GetParcelDistancefromPoint(ILandObject parcel, float x, float y) + { + return Vector2.Distance(new Vector2(x, y), GetParcelCenter(parcel)); + } + + //calculate the average center point of a parcel + private Vector2 GetParcelCenter(ILandObject parcel) + { + int count = 0; + int avgx = 0; + int avgy = 0; + for (int x = 0; x < Constants.RegionSize; x++) + { + for (int y = 0; y < Constants.RegionSize; y++) + { + //Just keep a running average as we check if all the points are inside or not + if (parcel.ContainsPoint(x, y)) + { + if (count == 0) + { + avgx = x; + avgy = y; + } + else + { + avgx = (avgx * count + x) / (count + 1); + avgy = (avgy * count + y) / (count + 1); + } + count += 1; + } + } + } + return new Vector2(avgx, avgy); + } + + private Vector3 GetNearestRegionEdgePosition(ScenePresence avatar) + { + float xdistance = avatar.AbsolutePosition.X < Constants.RegionSize / 2 ? avatar.AbsolutePosition.X : Constants.RegionSize - avatar.AbsolutePosition.X; + float ydistance = avatar.AbsolutePosition.Y < Constants.RegionSize / 2 ? avatar.AbsolutePosition.Y : Constants.RegionSize - avatar.AbsolutePosition.Y; + + //find out what vertical edge to go to + if (xdistance < ydistance) + { + if (avatar.AbsolutePosition.X < Constants.RegionSize / 2) + { + return GetPositionAtAvatarHeightOrGroundHeight(avatar, 0.0f, avatar.AbsolutePosition.Y); + } + else + { + return GetPositionAtAvatarHeightOrGroundHeight(avatar, Constants.RegionSize, avatar.AbsolutePosition.Y); + } + } + //find out what horizontal edge to go to + else + { + if (avatar.AbsolutePosition.Y < Constants.RegionSize / 2) + { + return GetPositionAtAvatarHeightOrGroundHeight(avatar, avatar.AbsolutePosition.X, 0.0f); + } + else + { + return GetPositionAtAvatarHeightOrGroundHeight(avatar, avatar.AbsolutePosition.X, Constants.RegionSize); + } + } + } + + private Vector3 GetPositionAtAvatarHeightOrGroundHeight(ScenePresence avatar, float x, float y) + { + Vector3 ground = GetPositionAtGround(x, y); + if( avatar.AbsolutePosition.Z > ground.Z) + { + ground.Z = avatar.AbsolutePosition.Z; + } + return ground; + } + + private Vector3 GetPositionAtGround(float x, float y) + { + return new Vector3(x, y, GetGroundHeight(x, y)); + } } } diff --git a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs index 99e5b84872..711052c5ad 100644 --- a/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs +++ b/OpenSim/Region/OptionalModules/Agent/InternetRelayClientView/Server/IRCClientView.cs @@ -680,6 +680,7 @@ namespace OpenSim.Region.OptionalModules.Agent.InternetRelayClientView.Server public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; diff --git a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs index 15473d89c9..ebf9333aa5 100644 --- a/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs +++ b/OpenSim/Region/OptionalModules/World/NPC/NPCAvatar.cs @@ -190,6 +190,7 @@ namespace OpenSim.Region.OptionalModules.World.NPC public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit; diff --git a/OpenSim/Tests/Common/Mock/TestClient.cs b/OpenSim/Tests/Common/Mock/TestClient.cs index 0d9dcc6034..715e1a43a6 100644 --- a/OpenSim/Tests/Common/Mock/TestClient.cs +++ b/OpenSim/Tests/Common/Mock/TestClient.cs @@ -96,6 +96,7 @@ namespace OpenSim.Tests.Common.Mock public event Action OnRegionHandShakeReply; public event GenericCall2 OnRequestWearables; public event GenericCall1 OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; public event UpdateAgent OnAgentUpdate; public event AgentRequestSit OnAgentRequestSit; public event AgentSit OnAgentSit;