/* * Copyright (c) 2006-2008, openmetaverse.org * All rights reserved. * * - 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. * - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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 System; using OpenMetaverse.Packets; using System.Collections.Generic; namespace OpenMetaverse { /// Describes tasks returned in LandStatReply public class EstateTask { public Vector3 Position; public float Score; public UUID TaskID; public uint TaskLocalID; public string TaskName; public string OwnerName; } /// /// Estate level administration and utilities /// public class EstateTools { private GridClient Client; /// Textures for each of the four terrain height levels public GroundTextureSettings GroundTextures; /// Upper/lower texture boundaries for each corner of the sim public GroundTextureHeightSettings GroundTextureLimits; #region Delegates /// /// Triggered on LandStatReply when the report type is for "top colliders" /// /// /// public delegate void TopCollidersReplyCallback(int objectCount, Dictionary Tasks); /// /// Triggered on LandStatReply when the report type is for "top scripts" /// /// /// public delegate void TopScriptsReplyCallback(int objectCount, Dictionary Tasks); /// /// Triggered when the list of estate managers is received for the current estate /// /// /// /// public delegate void EstateManagersReply(uint estateID, int count, List managers); /// /// FIXME - Enumerate all params from EstateOwnerMessage packet /// /// /// /// /// public delegate void EstateUpdateInfoReply(string estateName, UUID estateOwner, uint estateID, bool denyNoPaymentInfo); public delegate void EstateManagersListReply(uint estateID, List managers); public delegate void EstateBansReply(uint estateID, int count, List banned); public delegate void EstateUsersReply(uint estateID, int count, List allowedUsers); public delegate void EstateGroupsReply(uint estateID, int count, List allowedGroups); public delegate void EstateCovenantReply(UUID covenantID, long timestamp, string estateName, UUID estateOwnerID); #endregion #region Events // Callback for LandStatReply packets //public event LandStatReply OnLandStatReply; /// Triggered upon a successful .GetTopColliders() public event TopCollidersReplyCallback OnGetTopColliders; /// Triggered upon a successful .GetTopScripts() public event TopScriptsReplyCallback OnGetTopScripts; /// Returned, along with other info, upon a successful .GetInfo() public event EstateUpdateInfoReply OnGetEstateUpdateInfo; /// Returned, along with other info, upon a successful .GetInfo() public event EstateManagersReply OnGetEstateManagers; /// Returned, along with other info, upon a successful .GetInfo() public event EstateBansReply OnGetEstateBans; /// Returned, along with other info, upon a successful .GetInfo() public event EstateGroupsReply OnGetAllowedGroups; /// Returned, along with other info, upon a successful .GetInfo() public event EstateUsersReply OnGetAllowedUsers; /// Triggered upon a successful .RequestCovenant() public event EstateCovenantReply OnGetCovenant; #endregion /// /// Constructor for EstateTools class /// /// public EstateTools(GridClient client) { GroundTextures = new GroundTextureSettings(); GroundTextureLimits = new GroundTextureHeightSettings(); Client = client; Client.Network.RegisterCallback(PacketType.LandStatReply, new NetworkManager.PacketCallback(LandStatReplyHandler)); Client.Network.RegisterCallback(PacketType.EstateOwnerMessage, new NetworkManager.PacketCallback(EstateOwnerMessageHandler)); Client.Network.RegisterCallback(PacketType.EstateCovenantReply, new NetworkManager.PacketCallback(EstateCovenantReplyHandler)); } #region Enums /// Used in the ReportType field of a LandStatRequest public enum LandStatReportType { TopScripts = 0, TopColliders = 1 } /// Used by EstateOwnerMessage packets public enum EstateAccessDelta : uint { BanUser = 64, BanUserAllEstates = 66, UnbanUser = 128, UnbanUserAllEstates = 130, AddManager = 256, AddManagerAllEstates = 257, RemoveManager = 512, RemoveManagerAllEstates = 513 } /// Used by EstateOwnerMessage packets public enum EstateAccessReplyDelta : uint { AllowedUsers = 17, AllowedGroups = 18, EstateBans = 20, EstateManagers = 24 } /// /// /// [Flags] public enum EstateReturnFlags : uint { /// No flags set None = 2, /// Only return targets scripted objects ReturnScripted = 6, /// Only return targets objects if on others land ReturnOnOthersLand = 3, /// Returns target's scripted objects and objects on other parcels ReturnScriptedAndOnOthers = 7 } #endregion #region Structs /// Ground texture settings for each corner of the region // TODO: maybe move this class to the Simulator object and implement it there too public struct GroundTextureSettings { public UUID Low; public UUID MidLow; public UUID MidHigh; public UUID High; } /// Used by GroundTextureHeightSettings public struct GroundTextureHeight { public float Low; public float High; } /// The high and low texture thresholds for each corner of the sim public struct GroundTextureHeightSettings { public GroundTextureHeight SW; public GroundTextureHeight NW; public GroundTextureHeight SE; public GroundTextureHeight NE; } #endregion #region Public Methods /// /// Requests estate information such as top scripts and colliders /// /// /// /// /// public void LandStatRequest(int parcelLocalID, LandStatReportType reportType, uint requestFlags, string filter) { LandStatRequestPacket p = new LandStatRequestPacket(); p.AgentData.AgentID = Client.Self.AgentID; p.AgentData.SessionID = Client.Self.SessionID; p.RequestData.Filter = Utils.StringToBytes(filter); p.RequestData.ParcelLocalID = parcelLocalID; p.RequestData.ReportType = (uint)reportType; p.RequestData.RequestFlags = requestFlags; Client.Network.SendPacket(p); } /// Requests estate settings, including estate manager and access/ban lists public void RequestInfo() { EstateOwnerMessage("getinfo", ""); } /// Requests the "Top Scripts" list for the current region public void RequestTopScripts() { //EstateOwnerMessage("scripts", ""); LandStatRequest(0, LandStatReportType.TopScripts, 0, ""); } /// Requests the "Top Colliders" list for the current region public void RequestTopColliders() { //EstateOwnerMessage("colliders", ""); LandStatRequest(0, LandStatReportType.TopColliders, 0, ""); } /// /// Set several estate specific configuration variables /// /// The Height of the waterlevel over the entire estate. Defaults to 20 /// The maximum height change allowed above the baked terrain. Defaults to 4 /// The minimum height change allowed below the baked terrain. Defaults to -4 /// true to use /// if True forces the sun position to the position in SunPosition /// The current position of the sun on the estate, or when FixedSun is true the static position /// the sun will remain. 6.0 = Sunrise, 30.0 = Sunset public void SetTerrainVariables(float WaterHeight, float TerrainRaiseLimit, float TerrainLowerLimit, bool UseEstateSun, bool FixedSun, float SunPosition) { List simVariables = new List(); simVariables.Add(WaterHeight.ToString(Utils.EnUsCulture)); simVariables.Add(TerrainRaiseLimit.ToString(Utils.EnUsCulture)); simVariables.Add(TerrainLowerLimit.ToString(Utils.EnUsCulture)); simVariables.Add(UseEstateSun ? "Y" : "N"); simVariables.Add(FixedSun ? "Y" : "N"); simVariables.Add(SunPosition.ToString(Utils.EnUsCulture)); simVariables.Add("Y"); //Not used? simVariables.Add("N"); //Not used? simVariables.Add("0.00"); //Also not used? EstateOwnerMessage("setregionterrain", simVariables); } /// /// Request return of objects owned by specified avatar /// /// The Agents owning the primitives to return /// specify the coverage and type of objects to be included in the return /// true to perform return on entire estate public void SimWideReturn(UUID Target, EstateReturnFlags flag, bool EstateWide) { if (EstateWide) { List param = new List(); param.Add(flag.ToString()); param.Add(Target.ToString()); EstateOwnerMessage("estateobjectreturn", param); } else { SimWideDeletesPacket simDelete = new SimWideDeletesPacket(); simDelete.AgentData.AgentID = Client.Self.AgentID; simDelete.AgentData.SessionID = Client.Self.SessionID; simDelete.DataBlock.TargetID = Target; simDelete.DataBlock.Flags = (uint)flag; Client.Network.SendPacket(simDelete); } } /// /// /// public void EstateOwnerMessage(string method, string param) { List listParams = new List(); listParams.Add(param); EstateOwnerMessage(method, listParams); } /// /// Used for setting and retrieving various estate panel settings /// /// EstateOwnerMessage Method field /// List of parameters to include public void EstateOwnerMessage(string method, List listParams) { EstateOwnerMessagePacket estate = new EstateOwnerMessagePacket(); estate.AgentData.AgentID = Client.Self.AgentID; estate.AgentData.SessionID = Client.Self.SessionID; estate.AgentData.TransactionID = UUID.Zero; estate.MethodData.Invoice = UUID.Random(); estate.MethodData.Method = Utils.StringToBytes(method); estate.ParamList = new EstateOwnerMessagePacket.ParamListBlock[listParams.Count]; for (int i = 0; i < listParams.Count; i++) { estate.ParamList[i] = new EstateOwnerMessagePacket.ParamListBlock(); estate.ParamList[i].Parameter = Utils.StringToBytes(listParams[i]); } Client.Network.SendPacket((Packet)estate); } /// /// Kick an avatar from an estate /// /// Key of Agent to remove public void KickUser(UUID userID) { EstateOwnerMessage("kickestate", userID.ToString()); } /// /// Ban an avatar from an estate /// Key of Agent to remove /// Ban user from this estate and all others owned by the estate owner public void BanUser(UUID userID, bool allEstates) { List listParams = new List(); uint flag = allEstates ? (uint)EstateAccessDelta.BanUserAllEstates : (uint)EstateAccessDelta.BanUser; listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(flag.ToString()); listParams.Add(userID.ToString()); EstateOwnerMessage("estateaccessdelta", listParams); } /// Unban an avatar from an estate /// Key of Agent to remove /// /// Unban user from this estate and all others owned by the estate owner public void UnbanUser(UUID userID, bool allEstates) { List listParams = new List(); uint flag = allEstates ? (uint)EstateAccessDelta.UnbanUserAllEstates : (uint)EstateAccessDelta.UnbanUser; listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(flag.ToString()); listParams.Add(userID.ToString()); EstateOwnerMessage("estateaccessdelta", listParams); } /// /// Send a message dialog to everyone in an entire estate /// /// Message to send all users in the estate public void EstateMessage(string message) { List listParams = new List(); listParams.Add(Client.Self.FirstName + " " + Client.Self.LastName); listParams.Add(message); EstateOwnerMessage("instantmessage", listParams); } /// /// Send a message dialog to everyone in a simulator /// /// Message to send all users in the simulator public void SimulatorMessage(string message) { List listParams = new List(); listParams.Add("-1"); listParams.Add("-1"); listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(Client.Self.FirstName + " " + Client.Self.LastName); listParams.Add(message); EstateOwnerMessage("simulatormessage", listParams); } /// /// Send an avatar back to their home location /// /// Key of avatar to send home public void TeleportHomeUser(UUID pest) { List listParams = new List(); listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(pest.ToString()); EstateOwnerMessage("teleporthomeuser", listParams); } /// /// Begin the region restart process /// public void RestartRegion() { EstateOwnerMessage("restart", "120"); } /// /// Cancels a region restart /// public void CancelRestart() { EstateOwnerMessage("restart", "-1"); } /// Estate panel "Region" tab settings public void SetRegionInfo(bool blockTerraform, bool blockFly, bool allowDamage, bool allowLandResell, bool restrictPushing, bool allowParcelJoinDivide, float agentLimit, float objectBonus, bool mature) { List listParams = new List(); if (blockTerraform) listParams.Add("Y"); else listParams.Add("N"); if (blockFly) listParams.Add("Y"); else listParams.Add("N"); if (allowDamage) listParams.Add("Y"); else listParams.Add("N"); if (allowLandResell) listParams.Add("Y"); else listParams.Add("N"); listParams.Add(agentLimit.ToString()); listParams.Add(objectBonus.ToString()); if (mature) listParams.Add("21"); else listParams.Add("13"); //FIXME - enumerate these settings if (restrictPushing) listParams.Add("Y"); else listParams.Add("N"); if (allowParcelJoinDivide) listParams.Add("Y"); else listParams.Add("N"); EstateOwnerMessage("setregioninfo", listParams); } /// Estate panel "Debug" tab settings public void SetRegionDebug(bool disableScripts, bool disableCollisions, bool disablePhysics) { List listParams = new List(); if (disableScripts) listParams.Add("Y"); else listParams.Add("N"); if (disableCollisions) listParams.Add("Y"); else listParams.Add("N"); if (disablePhysics) listParams.Add("Y"); else listParams.Add("N"); EstateOwnerMessage("setregiondebug", listParams); } /// Used for setting the region's terrain textures for its four height levels /// /// /// /// public void SetRegionTerrain(UUID low, UUID midLow, UUID midHigh, UUID high) { List listParams = new List(); listParams.Add("0 " + low.ToString()); listParams.Add("1 " + midLow.ToString()); listParams.Add("2 " + midHigh.ToString()); listParams.Add("3 " + high.ToString()); EstateOwnerMessage("texturedetail", listParams); EstateOwnerMessage("texturecommit", ""); } /// Used for setting sim terrain texture heights public void SetRegionTerrainHeights(float lowSW, float highSW, float lowNW, float highNW, float lowSE, float highSE, float lowNE, float highNE) { List listParams = new List(); listParams.Add("0 " + lowSW.ToString() + " " + highSW.ToString(Utils.EnUsCulture)); //SW low-high listParams.Add("1 " + lowNW.ToString() + " " + highNW.ToString(Utils.EnUsCulture)); //NW low-high listParams.Add("2 " + lowSE.ToString() + " " + highSE.ToString(Utils.EnUsCulture)); //SE low-high listParams.Add("3 " + lowNE.ToString() + " " + highNE.ToString(Utils.EnUsCulture)); //NE low-high EstateOwnerMessage("texturedetail", listParams); EstateOwnerMessage("texturecommit", ""); } /// Requests the estate covenant public void RequestCovenant() { EstateCovenantRequestPacket req = new EstateCovenantRequestPacket(); req.AgentData.AgentID = Client.Self.AgentID; req.AgentData.SessionID = Client.Self.SessionID; Client.Network.SendPacket(req); } /// /// Upload a terrain RAW file /// /// A byte array containing the encoded terrain data /// The name of the file being uploaded /// The Id of the transfer request public UUID UploadTerrain(byte[] fileData, string fileName) { AssetUpload upload = new AssetUpload(); upload.AssetData = fileData; upload.AssetType = AssetType.Unknown; upload.Size = fileData.Length; upload.ID = UUID.Random(); // Tell the library we have a pending file to upload Client.Assets.SetPendingAssetUploadData(upload); // Create and populate a list with commands specific to uploading a raw terrain file List paramList = new List(); paramList.Add("upload filename"); paramList.Add(fileName); // Tell the simulator we have a new raw file to upload Client.Estate.EstateOwnerMessage("terrain", paramList); return upload.ID; } /// /// Teleports all users home in current Estate /// public void TeleportHomeAllUsers() { List Params = new List(); Params.Add(Client.Self.AgentID.ToString()); EstateOwnerMessage("teleporthomeallusers", Params); } /// /// Remove estate manager /// Key of Agent to Remove /// removes manager to this estate and all others owned by the estate owner public void RemoveEstateManager(UUID userID, bool allEstates) { List listParams = new List(); uint flag = allEstates ? (uint)EstateAccessDelta.RemoveManagerAllEstates : (uint)EstateAccessDelta.RemoveManager; listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(flag.ToString()); listParams.Add(userID.ToString()); EstateOwnerMessage("estateaccessdelta", listParams); } /// /// Add estate manager /// Key of Agent to Add /// Add agent as manager to this estate and all others owned by the estate owner public void AddEstateManager(UUID userID, bool allEstates) { List listParams = new List(); uint flag = allEstates ? (uint)EstateAccessDelta.AddManagerAllEstates : (uint)EstateAccessDelta.AddManager; listParams.Add(Client.Self.AgentID.ToString()); listParams.Add(flag.ToString()); listParams.Add(userID.ToString()); EstateOwnerMessage("estateaccessdelta", listParams); } #endregion #region Packet Handlers /// /// /// private void EstateCovenantReplyHandler(Packet packet, Simulator simulator) { EstateCovenantReplyPacket reply = (EstateCovenantReplyPacket)packet; if (OnGetCovenant != null) { try { OnGetCovenant( reply.Data.CovenantID, reply.Data.CovenantTimestamp, Utils.BytesToString(reply.Data.EstateName), reply.Data.EstateOwnerID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } /// /// /// private void EstateOwnerMessageHandler(Packet packet, Simulator simulator) { EstateOwnerMessagePacket message = (EstateOwnerMessagePacket)packet; uint estateID; string method = Utils.BytesToString(message.MethodData.Method); //List parameters = new List(); if (method == "estateupdateinfo") { string estateName = Utils.BytesToString(message.ParamList[0].Parameter); UUID estateOwner = new UUID(Utils.BytesToString(message.ParamList[1].Parameter)); estateID = Utils.BytesToUInt(message.ParamList[2].Parameter); /* foreach (EstateOwnerMessagePacket.ParamListBlock param in message.ParamList) { parameters.Add(Utils.BytesToString(param.Parameter)); } */ bool denyNoPaymentInfo; if (Utils.BytesToUInt(message.ParamList[8].Parameter) == 0) denyNoPaymentInfo = true; else denyNoPaymentInfo = false; if (OnGetEstateUpdateInfo != null) { try { OnGetEstateUpdateInfo(estateName, estateOwner, estateID, denyNoPaymentInfo); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } else if (method == "setaccess") { int count; estateID = Utils.BytesToUInt(message.ParamList[0].Parameter); if (message.ParamList.Length > 1) { //param comes in as a string for some reason uint param; if (!uint.TryParse(Utils.BytesToString(message.ParamList[1].Parameter), out param)) return; EstateAccessReplyDelta accessType = (EstateAccessReplyDelta)param; switch (accessType) { case EstateAccessReplyDelta.EstateManagers: if (OnGetEstateManagers != null) { if (message.ParamList.Length > 5) { if (!int.TryParse(Utils.BytesToString(message.ParamList[5].Parameter), out count)) return; List managers = new List(); for (int i = 6; i < message.ParamList.Length; i++) { try { UUID managerID = new UUID(message.ParamList[i].Parameter, 0); managers.Add(managerID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } try { OnGetEstateManagers(estateID, count, managers); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } break; case EstateAccessReplyDelta.EstateBans: if (OnGetEstateBans != null) { if (message.ParamList.Length > 6) { if (!int.TryParse(Utils.BytesToString(message.ParamList[4].Parameter), out count)) return; List bannedUsers = new List(); for (int i = 7; i < message.ParamList.Length; i++) { try { UUID bannedID = new UUID(message.ParamList[i].Parameter, 0); bannedUsers.Add(bannedID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } try { OnGetEstateBans(estateID, count, bannedUsers); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } break; case EstateAccessReplyDelta.AllowedUsers: if (OnGetAllowedUsers != null) { if (message.ParamList.Length > 5) { if (!int.TryParse(Utils.BytesToString(message.ParamList[2].Parameter), out count)) return; List allowedUsers = new List(); for (int i = 6; i < message.ParamList.Length; i++) { try { UUID allowedID = new UUID(message.ParamList[i].Parameter, 0); allowedUsers.Add(allowedID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } try { OnGetAllowedUsers(estateID, count, allowedUsers); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } break; case EstateAccessReplyDelta.AllowedGroups: if (OnGetAllowedGroups != null) { if (message.ParamList.Length > 5) { if (!int.TryParse(Utils.BytesToString(message.ParamList[3].Parameter), out count)) return; List allowedGroups = new List(); for (int i = 5; i < message.ParamList.Length; i++) { try { UUID groupID = new UUID(message.ParamList[i].Parameter, 0); allowedGroups.Add(groupID); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } try { OnGetAllowedGroups(estateID, count, allowedGroups); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } } break; } } } } /// /// /// private void LandStatReplyHandler(Packet packet, Simulator simulator) { //if (OnLandStatReply != null || OnGetTopScripts != null || OnGetTopColliders != null) if (OnGetTopScripts != null || OnGetTopColliders != null) { LandStatReplyPacket p = (LandStatReplyPacket)packet; Dictionary Tasks = new Dictionary(); foreach (LandStatReplyPacket.ReportDataBlock rep in p.ReportData) { EstateTask task = new EstateTask(); task.Position = new Vector3(rep.LocationX, rep.LocationY, rep.LocationZ); task.Score = rep.Score; task.TaskID = rep.TaskID; task.TaskLocalID = rep.TaskLocalID; task.TaskName = Utils.BytesToString(rep.TaskName); task.OwnerName = Utils.BytesToString(rep.OwnerName); Tasks.Add(task.TaskID, task); } LandStatReportType type = (LandStatReportType)p.RequestData.ReportType; if (OnGetTopScripts != null && type == LandStatReportType.TopScripts) { try { OnGetTopScripts((int)p.RequestData.TotalObjectCount, Tasks); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } else if (OnGetTopColliders != null && type == LandStatReportType.TopColliders) { try { OnGetTopColliders((int)p.RequestData.TotalObjectCount, Tasks); } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } /* if (OnGetTopColliders != null) { //FIXME - System.UnhandledExceptionEventArgs OnLandStatReply( type, p.RequestData.RequestFlags, (int)p.RequestData.TotalObjectCount, Tasks ); } */ } } #endregion } }