From 180be7de07014aa33bc6066f12a0819b731c1c9d Mon Sep 17 00:00:00 2001 From: Dr Scofield Date: Tue, 10 Feb 2009 13:10:57 +0000 Subject: this is step 2 of 2 of the OpenSim.Region.Environment refactor. NOTHING has been deleted or moved off to forge at this point. what has happened is that OpenSim.Region.Environment.Modules has been split in two: - OpenSim.Region.CoreModules: all those modules that are either directly or indirectly referenced from other OpenSim packages, or that provide functionality that the OpenSim developer community considers core functionality: CoreModules/Agent/AssetTransaction CoreModules/Agent/Capabilities CoreModules/Agent/TextureDownload CoreModules/Agent/TextureSender CoreModules/Agent/TextureSender/Tests CoreModules/Agent/Xfer CoreModules/Avatar/AvatarFactory CoreModules/Avatar/Chat/ChatModule CoreModules/Avatar/Combat CoreModules/Avatar/Currency/SampleMoney CoreModules/Avatar/Dialog CoreModules/Avatar/Friends CoreModules/Avatar/Gestures CoreModules/Avatar/Groups CoreModules/Avatar/InstantMessage CoreModules/Avatar/Inventory CoreModules/Avatar/Inventory/Archiver CoreModules/Avatar/Inventory/Transfer CoreModules/Avatar/Lure CoreModules/Avatar/ObjectCaps CoreModules/Avatar/Profiles CoreModules/Communications/Local CoreModules/Communications/REST CoreModules/Framework/EventQueue CoreModules/Framework/InterfaceCommander CoreModules/Hypergrid CoreModules/InterGrid CoreModules/Scripting/DynamicTexture CoreModules/Scripting/EMailModules CoreModules/Scripting/HttpRequest CoreModules/Scripting/LoadImageURL CoreModules/Scripting/VectorRender CoreModules/Scripting/WorldComm CoreModules/Scripting/XMLRPC CoreModules/World/Archiver CoreModules/World/Archiver/Tests CoreModules/World/Estate CoreModules/World/Land CoreModules/World/Permissions CoreModules/World/Serialiser CoreModules/World/Sound CoreModules/World/Sun CoreModules/World/Terrain CoreModules/World/Terrain/DefaultEffects CoreModules/World/Terrain/DefaultEffects/bin CoreModules/World/Terrain/DefaultEffects/bin/Debug CoreModules/World/Terrain/Effects CoreModules/World/Terrain/FileLoaders CoreModules/World/Terrain/FloodBrushes CoreModules/World/Terrain/PaintBrushes CoreModules/World/Terrain/Tests CoreModules/World/Vegetation CoreModules/World/Wind CoreModules/World/WorldMap - OpenSim.Region.OptionalModules: all those modules that are not core modules: OptionalModules/Avatar/Chat/IRC-stuff OptionalModules/Avatar/Concierge OptionalModules/Avatar/Voice/AsterixVoice OptionalModules/Avatar/Voice/SIPVoice OptionalModules/ContentManagementSystem OptionalModules/Grid/Interregion OptionalModules/Python OptionalModules/SvnSerialiser OptionalModules/World/NPC OptionalModules/World/TreePopulator --- .../Avatar/AvatarFactory/AvatarFactoryModule.cs | 232 +++ .../Region/CoreModules/Avatar/Chat/ChatModule.cs | 287 ++++ .../CoreModules/Avatar/Combat/CombatModule.cs | 154 ++ .../Currency/SampleMoney/SampleMoneyModule.cs | 1605 ++++++++++++++++++++ .../CoreModules/Avatar/Dialog/DialogModule.cs | 143 ++ .../CoreModules/Avatar/Friends/FriendsModule.cs | 1003 ++++++++++++ .../CoreModules/Avatar/Gestures/GesturesModule.cs | 104 ++ .../CoreModules/Avatar/Groups/GroupsModule.cs | 223 +++ .../Avatar/InstantMessage/InstantMessageModule.cs | 170 +++ .../Avatar/InstantMessage/MessageTransferModule.cs | 655 ++++++++ .../Avatar/InstantMessage/PresenceModule.cs | 426 ++++++ .../Archiver/InventoryArchiveReadRequest.cs | 279 ++++ .../Archiver/InventoryArchiveWriteRequest.cs | 247 +++ .../Inventory/Transfer/InventoryTransferModule.cs | 389 +++++ .../Region/CoreModules/Avatar/Lure/LureModule.cs | 176 +++ .../CoreModules/Avatar/ObjectCaps/ObjectAdd.cs | 368 +++++ .../Avatar/Profiles/AvatarProfilesModule.cs | 145 ++ .../Communications/Local/LocalInterregionComms.cs | 244 +++ .../Communications/REST/RESTInterregionComms.cs | 960 ++++++++++++ .../Framework/EventQueue/EventQueueGetModule.cs | 630 ++++++++ .../Framework/EventQueue/EventQueueHelper.cs | 459 ++++++ .../Framework/InterfaceCommander/Command.cs | 216 +++ .../Framework/InterfaceCommander/Commander.cs | 182 +++ .../Hypergrid/HGStandaloneAssetService.cs | 201 +++ .../Hypergrid/HGStandaloneInventoryService.cs | 314 ++++ .../CoreModules/Hypergrid/HGWorldMapModule.cs | 178 +++ .../InterGrid/OpenGridProtocolModule.cs | 1273 ++++++++++++++++ .../DynamicTexture/DynamicTextureModule.cs | 332 ++++ .../Scripting/EMailModules/EmailModule.cs | 288 ++++ .../Scripting/HttpRequest/ScriptsHttpRequests.cs | 437 ++++++ .../Scripting/LoadImageURL/LoadImageURLModule.cs | 229 +++ .../Scripting/VectorRender/VectorRenderModule.cs | 515 +++++++ .../Scripting/WorldComm/WorldCommModule.cs | 726 +++++++++ .../CoreModules/Scripting/XMLRPC/XMLRPCModule.cs | 726 +++++++++ .../CoreModules/World/Archiver/ArchiveConstants.cs | 128 ++ .../World/Archiver/ArchiveReadRequest.cs | 460 ++++++ .../World/Archiver/ArchiveWriteRequestExecution.cs | 161 ++ .../Archiver/ArchiveWriteRequestPreparation.cs | 333 ++++ .../CoreModules/World/Archiver/ArchiverModule.cs | 95 ++ .../CoreModules/World/Archiver/AssetsArchiver.cs | 143 ++ .../CoreModules/World/Archiver/AssetsDearchiver.cs | 184 +++ .../CoreModules/World/Archiver/AssetsRequest.cs | 138 ++ .../World/Archiver/RegionSettingsSerializer.cs | 258 ++++ .../CoreModules/World/Archiver/TarArchiveReader.cs | 195 +++ .../CoreModules/World/Archiver/TarArchiveWriter.cs | 202 +++ .../World/Archiver/Tests/ArchiverTests.cs | 188 +++ .../World/Estate/EstateManagementModule.cs | 1012 ++++++++++++ .../World/Estate/EstateTerrainXferHandler.cs | 127 ++ .../Region/CoreModules/World/Land/LandChannel.cs | 188 +++ .../CoreModules/World/Land/LandManagementModule.cs | 1347 ++++++++++++++++ .../Region/CoreModules/World/Land/LandObject.cs | 930 ++++++++++++ .../World/Permissions/PermissionsModule.cs | 1498 ++++++++++++++++++ .../World/Serialiser/IFileSerialiser.cs | 36 + .../World/Serialiser/SerialiseObjects.cs | 125 ++ .../World/Serialiser/SerialiseTerrain.cs | 53 + .../World/Serialiser/SerialiserModule.cs | 226 +++ .../Region/CoreModules/World/Sound/SoundModule.cs | 97 ++ OpenSim/Region/CoreModules/World/Sun/SunModule.cs | 434 ++++++ .../World/Terrain/DefaultEffects/ChannelDigger.cs | 107 ++ .../World/Terrain/Effects/CookieCutter.cs | 125 ++ .../Terrain/Effects/DefaultTerrainGenerator.cs | 56 + .../CoreModules/World/Terrain/FileLoaders/BMP.cs | 76 + .../CoreModules/World/Terrain/FileLoaders/GIF.cs | 61 + .../Terrain/FileLoaders/GenericSystemDrawing.cs | 195 +++ .../CoreModules/World/Terrain/FileLoaders/JPEG.cs | 112 ++ .../CoreModules/World/Terrain/FileLoaders/LLRAW.cs | 250 +++ .../CoreModules/World/Terrain/FileLoaders/PNG.cs | 61 + .../CoreModules/World/Terrain/FileLoaders/RAW32.cs | 170 +++ .../CoreModules/World/Terrain/FileLoaders/TIFF.cs | 61 + .../World/Terrain/FileLoaders/Terragen.cs | 142 ++ .../World/Terrain/FloodBrushes/FlattenArea.cs | 70 + .../World/Terrain/FloodBrushes/LowerArea.cs | 54 + .../World/Terrain/FloodBrushes/NoiseArea.cs | 58 + .../World/Terrain/FloodBrushes/RaiseArea.cs | 54 + .../World/Terrain/FloodBrushes/RevertArea.cs | 67 + .../World/Terrain/FloodBrushes/SmoothArea.cs | 114 ++ .../CoreModules/World/Terrain/ITerrainEffect.cs | 36 + .../World/Terrain/ITerrainFloodEffect.cs | 37 + .../CoreModules/World/Terrain/ITerrainLoader.cs | 42 + .../CoreModules/World/Terrain/ITerrainModule.cs | 61 + .../World/Terrain/ITerrainPaintableEffect.cs | 36 + .../World/Terrain/PaintBrushes/ErodeSphere.cs | 318 ++++ .../World/Terrain/PaintBrushes/FlattenSphere.cs | 101 ++ .../World/Terrain/PaintBrushes/LowerSphere.cs | 84 + .../World/Terrain/PaintBrushes/NoiseSphere.cs | 67 + .../World/Terrain/PaintBrushes/OlsenSphere.cs | 223 +++ .../World/Terrain/PaintBrushes/RaiseSphere.cs | 80 + .../World/Terrain/PaintBrushes/RevertSphere.cs | 80 + .../World/Terrain/PaintBrushes/SmoothSphere.cs | 100 ++ .../World/Terrain/PaintBrushes/WeatherSphere.cs | 211 +++ .../CoreModules/World/Terrain/TerrainException.cs | 46 + .../CoreModules/World/Terrain/TerrainModule.cs | 1001 ++++++++++++ .../CoreModules/World/Terrain/Tests/TerrainTest.cs | 118 ++ .../World/Vegetation/VegetationModule.cs | 118 ++ .../Region/CoreModules/World/Wind/WindModule.cs | 207 +++ .../World/WorldMap/IMapTileTerrainRenderer.cs | 39 + .../CoreModules/World/WorldMap/MapImageModule.cs | 586 +++++++ .../CoreModules/World/WorldMap/MapSearchModule.cs | 172 +++ .../World/WorldMap/ShadedMapTileRenderer.cs | 249 +++ .../World/WorldMap/TexturedMapTileRenderer.cs | 411 +++++ .../CoreModules/World/WorldMap/WorldMapModule.cs | 905 +++++++++++ 101 files changed, 30235 insertions(+) create mode 100644 OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Gestures/GesturesModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Groups/GroupsModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/ObjectCaps/ObjectAdd.cs create mode 100644 OpenSim/Region/CoreModules/Avatar/Profiles/AvatarProfilesModule.cs create mode 100644 OpenSim/Region/CoreModules/Communications/Local/LocalInterregionComms.cs create mode 100644 OpenSim/Region/CoreModules/Communications/REST/RESTInterregionComms.cs create mode 100644 OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueGetModule.cs create mode 100644 OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueHelper.cs create mode 100644 OpenSim/Region/CoreModules/Framework/InterfaceCommander/Command.cs create mode 100644 OpenSim/Region/CoreModules/Framework/InterfaceCommander/Commander.cs create mode 100644 OpenSim/Region/CoreModules/Hypergrid/HGStandaloneAssetService.cs create mode 100644 OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs create mode 100644 OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs create mode 100644 OpenSim/Region/CoreModules/InterGrid/OpenGridProtocolModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs create mode 100644 OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/ArchiveConstants.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestExecution.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestPreparation.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/AssetsDearchiver.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/RegionSettingsSerializer.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/TarArchiveReader.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/TarArchiveWriter.cs create mode 100644 OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs create mode 100644 OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs create mode 100644 OpenSim/Region/CoreModules/World/Land/LandChannel.cs create mode 100644 OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Land/LandObject.cs create mode 100644 OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Serialiser/IFileSerialiser.cs create mode 100644 OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs create mode 100644 OpenSim/Region/CoreModules/World/Serialiser/SerialiseTerrain.cs create mode 100644 OpenSim/Region/CoreModules/World/Serialiser/SerialiserModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Sound/SoundModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Sun/SunModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/DefaultEffects/ChannelDigger.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Effects/CookieCutter.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/FlattenArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/LowerArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RaiseArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RevertArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/SmoothArea.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainEffect.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainFloodEffect.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/ITerrainPaintableEffect.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/ErodeSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/FlattenSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/LowerSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/OlsenSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RaiseSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RevertSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/SmoothSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/WeatherSphere.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/TerrainException.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs create mode 100644 OpenSim/Region/CoreModules/World/Vegetation/VegetationModule.cs create mode 100644 OpenSim/Region/CoreModules/World/Wind/WindModule.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/IMapTileTerrainRenderer.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/MapImageModule.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/ShadedMapTileRenderer.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/TexturedMapTileRenderer.cs create mode 100644 OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs (limited to 'OpenSim/Region/CoreModules') diff --git a/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs new file mode 100644 index 0000000..d084dbd --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/AvatarFactory/AvatarFactoryModule.cs @@ -0,0 +1,232 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; + +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory +{ + public class AvatarFactoryModule : IAvatarFactory, IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene = null; + private static readonly AvatarAppearance def = new AvatarAppearance(); + + public bool TryGetAvatarAppearance(UUID avatarId, out AvatarAppearance appearance) + { + CachedUserInfo profile = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(avatarId); + //if ((profile != null) && (profile.RootFolder != null)) + if (profile != null) + { + appearance = m_scene.CommsManager.AvatarService.GetUserAppearance(avatarId); + if (appearance != null) + { + //SetAppearanceAssets(profile, ref appearance); + //m_log.DebugFormat("[APPEARANCE]: Found : {0}", appearance.ToString()); + return true; + } + } + + appearance = CreateDefault(avatarId); + m_log.ErrorFormat("[APPEARANCE]: Appearance not found for {0}, creating default", avatarId); + return false; + } + + private AvatarAppearance CreateDefault(UUID avatarId) + { + AvatarAppearance appearance = null; + AvatarWearable[] wearables; + byte[] visualParams; + GetDefaultAvatarAppearance(out wearables, out visualParams); + appearance = new AvatarAppearance(avatarId, wearables, visualParams); + + return appearance; + } + + public void Initialise(Scene scene, IConfigSource source) + { + scene.RegisterModuleInterface(this); + scene.EventManager.OnNewClient += NewClient; + + if (m_scene == null) + { + m_scene = scene; + } + + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "Default Avatar Factory"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public void NewClient(IClientAPI client) + { + client.OnAvatarNowWearing += AvatarIsWearing; + } + + public void RemoveClient(IClientAPI client) + { + // client.OnAvatarNowWearing -= AvatarIsWearing; + } + + + public void SetAppearanceAssets(CachedUserInfo profile, ref AvatarAppearance appearance) + { + if (profile.RootFolder != null) + { + for (int i = 0; i < 13; i++) + { + if (appearance.Wearables[i].ItemID == UUID.Zero) + { + appearance.Wearables[i].AssetID = UUID.Zero; + } + else + { + InventoryItemBase baseItem = profile.RootFolder.FindItem(appearance.Wearables[i].ItemID); + + if (baseItem != null) + { + appearance.Wearables[i].AssetID = baseItem.AssetID; + } + else + { + m_log.ErrorFormat("[APPEARANCE]: Can't find inventory item {0}, setting to default", appearance.Wearables[i].ItemID); + appearance.Wearables[i].AssetID = def.Wearables[i].AssetID; + } + } + } + } + else + { + m_log.Error("[APPEARANCE]: you have no inventory, appearance stuff isn't going to work"); + } + } + + /// + /// Update what the avatar is wearing using an item from their inventory. + /// + /// + /// + public void AvatarIsWearing(Object sender, AvatarWearingArgs e) + { + IClientAPI clientView = (IClientAPI)sender; + ScenePresence avatar = m_scene.GetScenePresence(clientView.AgentId); + + if (avatar == null) + { + m_log.Error("[APPEARANCE]: Avatar is child agent, ignoring AvatarIsWearing event"); + return; + } + + CachedUserInfo profile = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(clientView.AgentId); + + AvatarAppearance avatAppearance = null; + if (!TryGetAvatarAppearance(clientView.AgentId, out avatAppearance)) + { + m_log.Warn("[APPEARANCE]: We didn't seem to find the appearance, falling back to ScenePresence"); + avatAppearance = avatar.Appearance; + } + + //m_log.DebugFormat("[APPEARANCE]: Received wearables for {0}", clientView.Name); + + if (profile != null) + { + if (profile.RootFolder != null) + { + foreach (AvatarWearingArgs.Wearable wear in e.NowWearing) + { + if (wear.Type < 13) + { + avatAppearance.Wearables[wear.Type].ItemID = wear.ItemID; + } + } + + SetAppearanceAssets(profile, ref avatAppearance); + + m_scene.CommsManager.AvatarService.UpdateUserAppearance(clientView.AgentId, avatAppearance); + avatar.Appearance = avatAppearance; + } + else + { + m_log.WarnFormat( + "[APPEARANCE]: Inventory has not yet been received for {0}, cannot set wearables", + clientView.Name); + } + } + else + { + m_log.WarnFormat("[APPEARANCE]: Cannot set wearables for {0}, no user profile found", clientView.Name); + } + } + + public static void GetDefaultAvatarAppearance(out AvatarWearable[] wearables, out byte[] visualParams) + { + visualParams = GetDefaultVisualParams(); + wearables = AvatarWearable.DefaultWearables; + } + + public void UpdateDatabase(UUID user, AvatarAppearance appearance) + { + m_scene.CommsManager.AvatarService.UpdateUserAppearance(user, appearance); + } + + private static byte[] GetDefaultVisualParams() + { + byte[] visualParams; + visualParams = new byte[218]; + for (int i = 0; i < 218; i++) + { + visualParams[i] = 100; + } + return visualParams; + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs new file mode 100644 index 0000000..f036faf --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Chat/ChatModule.cs @@ -0,0 +1,287 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Chat +{ + public class ChatModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private const int DEBUG_CHANNEL = 2147483647; + + private bool m_enabled = true; + private int m_saydistance = 30; + private int m_shoutdistance = 100; + private int m_whisperdistance = 10; + private List m_scenes = new List(); + + internal object m_syncInit = new object(); + + #region IRegionModule Members + public virtual void Initialise(Scene scene, IConfigSource config) + { + // wrap this in a try block so that defaults will work if + // the config file doesn't specify otherwise. + try + { + m_enabled = config.Configs["Chat"].GetBoolean("enabled", m_enabled); + if (!m_enabled) return; + + m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); + m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); + m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); + } + catch (Exception) + { + } + + lock (m_syncInit) + { + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnChatFromWorld += OnChatFromWorld; + scene.EventManager.OnChatBroadcast += OnChatBroadcast; + } + } + + m_log.InfoFormat("[CHAT] initialized for {0} w:{1} s:{2} S:{3}", scene.RegionInfo.RegionName, + m_whisperdistance, m_saydistance, m_shoutdistance); + } + public virtual void PostInitialise() + { + } + + public virtual void Close() + { + } + + public virtual string Name + { + get { return "ChatModule"; } + } + + public virtual bool IsSharedModule + { + get { return true; } + } + + #endregion + + + public virtual void OnNewClient(IClientAPI client) + { + client.OnChatFromClient += OnChatFromClient; + } + + protected OSChatMessage FixPositionOfChatMessage(OSChatMessage c) + { + ScenePresence avatar; + Scene scene = (Scene)c.Scene; + if ((avatar = scene.GetScenePresence(c.Sender.AgentId)) != null) + c.Position = avatar.AbsolutePosition; + + return c; + } + + public virtual void OnChatFromClient(Object sender, OSChatMessage c) + { + c = FixPositionOfChatMessage(c); + + // redistribute to interested subscribers + Scene scene = (Scene)c.Scene; + scene.EventManager.TriggerOnChatFromClient(sender, c); + + // early return if not on public or debug channel + if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return; + + // sanity check: + if (c.Sender == null) + { + m_log.ErrorFormat("[CHAT] OnChatFromClient from {0} has empty Sender field!", sender); + return; + } + + DeliverChatToAvatars(ChatSourceType.Agent, c); + } + + public virtual void OnChatFromWorld(Object sender, OSChatMessage c) + { + // early return if not on public or debug channel + if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL) return; + + DeliverChatToAvatars(ChatSourceType.Object, c); + } + + protected virtual void DeliverChatToAvatars(ChatSourceType sourceType, OSChatMessage c) + { + string fromName = c.From; + UUID fromID = UUID.Zero; + string message = c.Message; + IScene scene = c.Scene; + Vector3 fromPos = c.Position; + Vector3 regionPos = new Vector3(scene.RegionInfo.RegionLocX * Constants.RegionSize, + scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); + + if (c.Channel == DEBUG_CHANNEL) c.Type = ChatTypeEnum.DebugChannel; + + switch (sourceType) + { + case ChatSourceType.Agent: + if (!(scene is Scene)) + { + m_log.WarnFormat("[CHAT]: scene {0} is not a Scene object, cannot obtain scene presence for {1}", + scene.RegionInfo.RegionName, c.Sender.AgentId); + return; + } + ScenePresence avatar = (scene as Scene).GetScenePresence(c.Sender.AgentId); + fromPos = avatar.AbsolutePosition; + fromName = avatar.Name; + fromID = c.Sender.AgentId; + + break; + + case ChatSourceType.Object: + fromID = c.SenderUUID; + + break; + } + + // TODO: iterate over message + if (message.Length >= 1000) // libomv limit + message = message.Substring(0, 1000); + + // m_log.DebugFormat("[CHAT]: DCTA: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, c.Type, sourceType); + + foreach (Scene s in m_scenes) + { + s.ForEachScenePresence(delegate(ScenePresence presence) + { + TrySendChatMessage(presence, fromPos, regionPos, fromID, fromName, + c.Type, message, sourceType); + }); + } + } + + + static private Vector3 CenterOfRegion = new Vector3(128, 128, 30); + public virtual void OnChatBroadcast(Object sender, OSChatMessage c) + { + // unless the chat to be broadcast is of type Region, we + // drop it if its channel is neither 0 nor DEBUG_CHANNEL + if (c.Channel != 0 && c.Channel != DEBUG_CHANNEL && c.Type != ChatTypeEnum.Region) return; + + ChatTypeEnum cType = c.Type; + if (c.Channel == DEBUG_CHANNEL) + cType = ChatTypeEnum.DebugChannel; + + if (cType == ChatTypeEnum.Region) + cType = ChatTypeEnum.Say; + + if (c.Message.Length > 1100) + c.Message = c.Message.Substring(0, 1000); + + // broadcast chat works by redistributing every incoming chat + // message to each avatar in the scene. + string fromName = c.From; + + UUID fromID = UUID.Zero; + ChatSourceType sourceType = ChatSourceType.Object; + if (null != c.Sender) + { + ScenePresence avatar = (c.Scene as Scene).GetScenePresence(c.Sender.AgentId); + fromID = c.Sender.AgentId; + fromName = avatar.Name; + sourceType = ChatSourceType.Agent; + } + + // m_log.DebugFormat("[CHAT] Broadcast: fromID {0} fromName {1}, cType {2}, sType {3}", fromID, fromName, cType, sourceType); + + ((Scene)c.Scene).ForEachScenePresence( + delegate(ScenePresence presence) + { + // ignore chat from child agents + if (presence.IsChildAgent) return; + + IClientAPI client = presence.ControllingClient; + + // don't forward SayOwner chat from objects to + // non-owner agents + if ((c.Type == ChatTypeEnum.Owner) && + (null != c.SenderObject) && + (((SceneObjectPart)c.SenderObject).OwnerID != client.AgentId)) + return; + + client.SendChatMessage(c.Message, (byte)cType, CenterOfRegion, fromName, fromID, + (byte)sourceType, (byte)ChatAudibleLevel.Fully); + }); + } + + + protected virtual void TrySendChatMessage(ScenePresence presence, Vector3 fromPos, Vector3 regionPos, + UUID fromAgentID, string fromName, ChatTypeEnum type, + string message, ChatSourceType src) + { + // don't send stuff to child agents + if (presence.IsChildAgent) return; + + Vector3 fromRegionPos = fromPos + regionPos; + Vector3 toRegionPos = presence.AbsolutePosition + + new Vector3(presence.Scene.RegionInfo.RegionLocX * Constants.RegionSize, + presence.Scene.RegionInfo.RegionLocY * Constants.RegionSize, 0); + + int dis = Math.Abs((int) Util.GetDistanceTo(toRegionPos, fromRegionPos)); + + if (type == ChatTypeEnum.Whisper && dis > m_whisperdistance || + type == ChatTypeEnum.Say && dis > m_saydistance || + type == ChatTypeEnum.Shout && dis > m_shoutdistance) + { + return; + } + + // TODO: should change so the message is sent through the avatar rather than direct to the ClientView + presence.ControllingClient.SendChatMessage(message, (byte) type, fromPos, fromName, + fromAgentID,(byte)src,(byte)ChatAudibleLevel.Fully); + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs b/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs new file mode 100644 index 0000000..1ed608a --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Combat/CombatModule.cs @@ -0,0 +1,154 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Communications.Cache; + +namespace OpenSim.Region.CoreModules.Avatar.Combat.CombatModule +{ + public class CombatModule : IRegionModule + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Region UUIDS indexed by AgentID + /// + //private Dictionary m_rootAgents = new Dictionary(); + + /// + /// Scenes by Region Handle + /// + private Dictionary m_scenel = new Dictionary(); + + /// + /// Startup + /// + /// + /// + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_scenel) + { + if (m_scenel.ContainsKey(scene.RegionInfo.RegionHandle)) + { + m_scenel[scene.RegionInfo.RegionHandle] = scene; + } + else + { + m_scenel.Add(scene.RegionInfo.RegionHandle, scene); + } + } + + scene.EventManager.OnAvatarKilled += KillAvatar; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "CombatModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + private void KillAvatar(uint killerObjectLocalID, ScenePresence DeadAvatar) + { + if (killerObjectLocalID == 0) + DeadAvatar.ControllingClient.SendAgentAlertMessage("You committed suicide!", true); + else + { + bool foundResult = false; + string resultstring = ""; + List allav = DeadAvatar.Scene.GetScenePresences(); + try + { + foreach (ScenePresence av in allav) + { + if (av.LocalId == killerObjectLocalID) + { + av.ControllingClient.SendAlertMessage("You fragged " + DeadAvatar.Firstname + " " + DeadAvatar.Lastname); + resultstring = av.Firstname + " " + av.Lastname; + foundResult = true; + } + } + } catch (System.InvalidOperationException) + { + + } + + if (!foundResult) + { + SceneObjectPart part = DeadAvatar.Scene.GetSceneObjectPart(killerObjectLocalID); + if (part != null) + { + ScenePresence av = DeadAvatar.Scene.GetScenePresence(part.OwnerID); + if (av != null) + { + av.ControllingClient.SendAlertMessage("You fragged " + DeadAvatar.Firstname + " " + DeadAvatar.Lastname); + resultstring = av.Firstname + " " + av.Lastname; + DeadAvatar.ControllingClient.SendAgentAlertMessage("You got killed by " + resultstring + "!", true); + } + else + { + string killer = DeadAvatar.Scene.CommsManager.UUIDNameRequestString(part.OwnerID); + DeadAvatar.ControllingClient.SendAgentAlertMessage("You impaled yourself on " + part.Name + " owned by " + killer +"!", true); + } + //DeadAvatar.Scene. part.ObjectOwner + } + else + { + DeadAvatar.ControllingClient.SendAgentAlertMessage("You died!", true); + } + } + } + DeadAvatar.Health = 100; + DeadAvatar.Scene.TeleportClientHome(DeadAvatar.UUID, DeadAvatar.ControllingClient); + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs b/OpenSim/Region/CoreModules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs new file mode 100644 index 0000000..0a1de44 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Currency/SampleMoney/SampleMoneyModule.cs @@ -0,0 +1,1605 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Xml; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Currency.SampleMoney +{ + /// + /// Demo Economy/Money Module. This is not a production quality money/economy module! + /// This is a demo for you to use when making one that works for you. + /// // To use the following you need to add: + /// -helperuri
+ /// to the command line parameters you use to start up your client + /// This commonly looks like -helperuri http://127.0.0.1:9000/ + /// + /// Centralized grid structure example using OpenSimWi Redux revision 9+ + /// svn co https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux + ///
+ public class SampleMoneyModule : IMoneyModule, IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Where Stipends come from and Fees go to. + /// + // private UUID EconomyBaseAccount = UUID.Zero; + + private float EnergyEfficiency = 0f; + private bool gridmode = false; + // private ObjectPaid handerOnObjectPaid; + private bool m_enabled = true; + + private IConfigSource m_gConfig; + + private bool m_keepMoneyAcrossLogins = true; + private Dictionary m_KnownClientFunds = new Dictionary(); + // private string m_LandAddress = String.Empty; + + private int m_minFundsBeforeRefresh = 100; + private string m_MoneyAddress = String.Empty; + + /// + /// Region UUIDS indexed by AgentID + /// + private Dictionary m_rootAgents = new Dictionary(); + + /// + /// Scenes by Region Handle + /// + private Dictionary m_scenel = new Dictionary(); + + private int m_stipend = 1000; + + private int ObjectCapacity = 45000; + private int ObjectCount = 0; + private int PriceEnergyUnit = 0; + private int PriceGroupCreate = 0; + private int PriceObjectClaim = 0; + private float PriceObjectRent = 0f; + private float PriceObjectScaleFactor = 0f; + private int PriceParcelClaim = 0; + private float PriceParcelClaimFactor = 0f; + private int PriceParcelRent = 0; + private int PricePublicObjectDecay = 0; + private int PricePublicObjectDelete = 0; + private int PriceRentLight = 0; + private int PriceUpload = 0; + private int TeleportMinPrice = 0; + + private float TeleportPriceExponent = 0f; + // private int UserLevelPaysFees = 2; + // private Scene XMLRPCHandler; + + #region IMoneyModule Members + + public event ObjectPaid OnObjectPaid; + + /// + /// Startup + /// + /// + /// + public void Initialise(Scene scene, IConfigSource config) + { + m_gConfig = config; + + IConfig startupConfig = m_gConfig.Configs["Startup"]; + IConfig economyConfig = m_gConfig.Configs["Economy"]; + + + ReadConfigAndPopulate(scene, startupConfig, "Startup"); + ReadConfigAndPopulate(scene, economyConfig, "Economy"); + + if (m_enabled) + { + scene.RegisterModuleInterface(this); + BaseHttpServer httpServer = scene.CommsManager.HttpServer; + + lock (m_scenel) + { + if (m_scenel.Count == 0) + { + // XMLRPCHandler = scene; + + // To use the following you need to add: + // -helperuri
+ // to the command line parameters you use to start up your client + // This commonly looks like -helperuri http://127.0.0.1:9000/ + + if (m_MoneyAddress.Length > 0) + { + // Centralized grid structure using OpenSimWi Redux revision 9+ + // https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux + httpServer.AddXmlRPCHandler("balanceUpdateRequest", GridMoneyUpdate); + httpServer.AddXmlRPCHandler("userAlert", UserAlert); + } + else + { + // Local Server.. enables functionality only. + httpServer.AddXmlRPCHandler("getCurrencyQuote", quote_func); + httpServer.AddXmlRPCHandler("buyCurrency", buy_func); + httpServer.AddXmlRPCHandler("preflightBuyLandPrep", preflightBuyLandPrep_func); + httpServer.AddXmlRPCHandler("buyLandPrep", landBuy_func); + } + } + + if (m_scenel.ContainsKey(scene.RegionInfo.RegionHandle)) + { + m_scenel[scene.RegionInfo.RegionHandle] = scene; + } + else + { + m_scenel.Add(scene.RegionInfo.RegionHandle, scene); + } + } + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnMoneyTransfer += MoneyTransferAction; + scene.EventManager.OnClientClosed += ClientClosed; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.EventManager.OnValidateLandBuy += ValidateLandBuy; + scene.EventManager.OnLandBuy += processLandBuy; + } + } + + // Please do not refactor these to be just one method + // Existing implementations need the distinction + // + public void ApplyUploadCharge(UUID agentID) + { + } + + public void ApplyGroupCreationCharge(UUID agentID) + { + } + + public void ApplyCharge(UUID agentID, int amount, string text) + { + } + + public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount) + { + string description = String.Format("Object {0} pays {1}", resolveObjectName(objectID), resolveAgentName(toID)); + + bool give_result = doMoneyTransfer(fromID, toID, amount, 2, description); + + if (m_MoneyAddress.Length == 0) + BalanceUpdate(fromID, toID, give_result, description); + + return give_result; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "BetaGridLikeMoneyModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + /// + /// Parse Configuration + /// + /// + /// + /// + private void ReadConfigAndPopulate(Scene scene, IConfig startupConfig, string config) + { + if (config == "Startup" && startupConfig != null) + { + gridmode = startupConfig.GetBoolean("gridmode", false); + m_enabled = (startupConfig.GetString("economymodule", "BetaGridLikeMoneyModule") == "BetaGridLikeMoneyModule"); + } + + if (config == "Economy" && startupConfig != null) + { + ObjectCapacity = startupConfig.GetInt("ObjectCapacity", 45000); + PriceEnergyUnit = startupConfig.GetInt("PriceEnergyUnit", 100); + PriceObjectClaim = startupConfig.GetInt("PriceObjectClaim", 10); + PricePublicObjectDecay = startupConfig.GetInt("PricePublicObjectDecay", 4); + PricePublicObjectDelete = startupConfig.GetInt("PricePublicObjectDelete", 4); + PriceParcelClaim = startupConfig.GetInt("PriceParcelClaim", 1); + PriceParcelClaimFactor = startupConfig.GetFloat("PriceParcelClaimFactor", 1f); + PriceUpload = startupConfig.GetInt("PriceUpload", 0); + PriceRentLight = startupConfig.GetInt("PriceRentLight", 5); + TeleportMinPrice = startupConfig.GetInt("TeleportMinPrice", 2); + TeleportPriceExponent = startupConfig.GetFloat("TeleportPriceExponent", 2f); + EnergyEfficiency = startupConfig.GetFloat("EnergyEfficiency", 1); + PriceObjectRent = startupConfig.GetFloat("PriceObjectRent", 1); + PriceObjectScaleFactor = startupConfig.GetFloat("PriceObjectScaleFactor", 10); + PriceParcelRent = startupConfig.GetInt("PriceParcelRent", 1); + PriceGroupCreate = startupConfig.GetInt("PriceGroupCreate", -1); + // string EBA = startupConfig.GetString("EconomyBaseAccount", UUID.Zero.ToString()); + // Helpers.TryParse(EBA, out EconomyBaseAccount); + + // UserLevelPaysFees = startupConfig.GetInt("UserLevelPaysFees", -1); + m_stipend = startupConfig.GetInt("UserStipend", 500); + m_minFundsBeforeRefresh = startupConfig.GetInt("IssueStipendWhenClientIsBelowAmount", 10); + m_keepMoneyAcrossLogins = startupConfig.GetBoolean("KeepMoneyAcrossLogins", true); + m_MoneyAddress = startupConfig.GetString("CurrencyServer", String.Empty); + // m_LandAddress = startupConfig.GetString("LandServer", String.Empty); + } + + // Send ObjectCapacity to Scene.. Which sends it to the SimStatsReporter. + scene.SetObjectCapacity(ObjectCapacity); + } + + public EconomyData GetEconomyData() + { + EconomyData edata = new EconomyData(); + edata.ObjectCapacity = ObjectCapacity; + edata.ObjectCount = ObjectCount; + edata.PriceEnergyUnit = PriceEnergyUnit; + edata.PriceGroupCreate = PriceGroupCreate; + edata.PriceObjectClaim = PriceObjectClaim; + edata.PriceObjectRent = PriceObjectRent; + edata.PriceObjectScaleFactor = PriceObjectScaleFactor; + edata.PriceParcelClaim = PriceParcelClaim; + edata.PriceParcelClaimFactor = PriceParcelClaimFactor; + edata.PriceParcelRent = PriceParcelRent; + edata.PricePublicObjectDecay = PricePublicObjectDecay; + edata.PricePublicObjectDelete = PricePublicObjectDelete; + edata.PriceRentLight = PriceRentLight; + edata.PriceUpload = PriceUpload; + edata.TeleportMinPrice = TeleportMinPrice; + return edata; + } + + private void GetClientFunds(IClientAPI client) + { + // Here we check if we're in grid mode + // I imagine that the 'check balance' + // function for the client should be here or shortly after + + if (gridmode) + { + if (m_MoneyAddress.Length == 0) + { + CheckExistAndRefreshFunds(client.AgentId); + } + else + { + bool childYN = true; + ScenePresence agent = null; + //client.SecureSessionId; + Scene s = LocateSceneClientIn(client.AgentId); + if (s != null) + { + agent = s.GetScenePresence(client.AgentId); + if (agent != null) + childYN = agent.IsChildAgent; + } + if (s != null && agent != null && childYN == false) + { + //s.RegionInfo.RegionHandle; + UUID agentID = UUID.Zero; + int funds = 0; + + Hashtable hbinfo = + GetBalanceForUserFromMoneyServer(client.AgentId, client.SecureSessionId, s.RegionInfo.originRegionID, + s.RegionInfo.regionSecret); + if ((bool) hbinfo["success"] == true) + { + UUID.TryParse((string)hbinfo["agentId"], out agentID); + try + { + funds = (Int32) hbinfo["funds"]; + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentID); + client.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); + } + catch (InvalidCastException) + { + funds = 0; + } + + m_KnownClientFunds[agentID] = funds; + } + else + { + m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentID, + (string) hbinfo["errorMessage"]); + client.SendAlertMessage((string) hbinfo["errorMessage"]); + } + SendMoneyBalance(client, agentID, client.SessionId, UUID.Zero); + } + } + } + else + { + CheckExistAndRefreshFunds(client.AgentId); + } + + } + + /// + /// New Client Event Handler + /// + /// + private void OnNewClient(IClientAPI client) + { + GetClientFunds(client); + + // Subscribe to Money messages + client.OnEconomyDataRequest += EconomyDataRequestHandler; + client.OnMoneyBalanceRequest += SendMoneyBalance; + client.OnRequestPayPrice += requestPayPrice; + client.OnObjectBuy += ObjectBuy; + client.OnLogout += ClientClosed; + } + + /// + /// Transfer money + /// + /// + /// + /// + /// + private bool doMoneyTransfer(UUID Sender, UUID Receiver, int amount, int transactiontype, string description) + { + bool result = false; + if (amount >= 0) + { + lock (m_KnownClientFunds) + { + // If we don't know about the sender, then the sender can't + // actually be here and therefore this is likely fraud or outdated. + if (m_MoneyAddress.Length == 0) + { + if (m_KnownClientFunds.ContainsKey(Sender)) + { + // Does the sender have enough funds to give? + if (m_KnownClientFunds[Sender] >= amount) + { + // Subtract the funds from the senders account + m_KnownClientFunds[Sender] -= amount; + + // do we know about the receiver? + if (!m_KnownClientFunds.ContainsKey(Receiver)) + { + // Make a record for them so they get the updated balance when they login + CheckExistAndRefreshFunds(Receiver); + } + if (m_enabled) + { + //Add the amount to the Receiver's funds + m_KnownClientFunds[Receiver] += amount; + result = true; + } + } + else + { + // These below are redundant to make this clearer to read + result = false; + } + } + else + { + result = false; + } + } + else + { + result = TransferMoneyonMoneyServer(Sender, Receiver, amount, transactiontype, description); + } + } + } + return result; + } + + + /// + /// Sends the the stored money balance to the client + /// + /// + /// + /// + /// + public void SendMoneyBalance(IClientAPI client, UUID agentID, UUID SessionID, UUID TransactionID) + { + if (client.AgentId == agentID && client.SessionId == SessionID) + { + int returnfunds = 0; + + try + { + returnfunds = GetFundsForAgentID(agentID); + } + catch (Exception e) + { + client.SendAlertMessage(e.Message + " "); + } + + client.SendMoneyBalance(TransactionID, true, new byte[0], returnfunds); + } + else + { + client.SendAlertMessage("Unable to send your money balance to you!"); + } + } + + /// + /// Gets the current balance for the user from the Grid Money Server + /// + /// + /// + /// + /// + /// + public Hashtable GetBalanceForUserFromMoneyServer(UUID agentId, UUID secureSessionID, UUID regionId, string regionSecret) + { + Hashtable MoneyBalanceRequestParams = new Hashtable(); + MoneyBalanceRequestParams["agentId"] = agentId.ToString(); + MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); + MoneyBalanceRequestParams["regionId"] = regionId.ToString(); + MoneyBalanceRequestParams["secret"] = regionSecret; + MoneyBalanceRequestParams["currencySecret"] = ""; // per - region/user currency secret gotten from the money system + + Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorUserBalanceRequest"); + + return MoneyRespData; + } + + + /// + /// Generic XMLRPC client abstraction + /// + /// Hashtable containing parameters to the method + /// Method to invoke + /// Hashtable with success=>bool and other values + public Hashtable genericCurrencyXMLRPCRequest(Hashtable ReqParams, string method) + { + ArrayList SendParams = new ArrayList(); + SendParams.Add(ReqParams); + // Send Request + XmlRpcResponse MoneyResp; + try + { + XmlRpcRequest BalanceRequestReq = new XmlRpcRequest(method, SendParams); + MoneyResp = BalanceRequestReq.Send(m_MoneyAddress, 30000); + } + catch (WebException ex) + { + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + //throw (ex); + } + catch (SocketException ex) + { + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + //throw (ex); + } + catch (XmlException ex) + { + m_log.ErrorFormat( + "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", + m_MoneyAddress, ex); + + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + } + if (MoneyResp.IsFault) + { + Hashtable ErrorHash = new Hashtable(); + ErrorHash["success"] = false; + ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; + ErrorHash["errorURI"] = ""; + + return ErrorHash; + } + Hashtable MoneyRespData = (Hashtable) MoneyResp.Value; + + return MoneyRespData; + } + + /// + /// This informs the Money Grid Server that the avatar is in this simulator + /// + /// + /// + /// + /// + /// + public Hashtable claim_user(UUID agentId, UUID secureSessionID, UUID regionId, string regionSecret) + { + Hashtable MoneyBalanceRequestParams = new Hashtable(); + MoneyBalanceRequestParams["agentId"] = agentId.ToString(); + MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); + MoneyBalanceRequestParams["regionId"] = regionId.ToString(); + MoneyBalanceRequestParams["secret"] = regionSecret; + + Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorClaimUserRequest"); + IClientAPI sendMoneyBal = LocateClientObject(agentId); + if (sendMoneyBal != null) + { + SendMoneyBalance(sendMoneyBal, agentId, sendMoneyBal.SessionId, UUID.Zero); + } + return MoneyRespData; + } + + private SceneObjectPart findPrim(UUID objectID) + { + lock (m_scenel) + { + foreach (Scene s in m_scenel.Values) + { + SceneObjectPart part = s.GetSceneObjectPart(objectID); + if (part != null) + { + return part; + } + } + } + return null; + } + + private string resolveObjectName(UUID objectID) + { + SceneObjectPart part = findPrim(objectID); + if (part != null) + { + return part.Name; + } + return String.Empty; + } + + private string resolveAgentName(UUID agentID) + { + // try avatar username surname + Scene scene = GetRandomScene(); + CachedUserInfo profile = scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID); + if (profile != null && profile.UserProfile != null) + { + string avatarname = profile.UserProfile.FirstName + " " + profile.UserProfile.SurName; + return avatarname; + } + else + { + m_log.ErrorFormat( + "[MONEY]: Could not resolve user {0}", + agentID); + } + + return String.Empty; + } + + private void BalanceUpdate(UUID senderID, UUID receiverID, bool transactionresult, string description) + { + IClientAPI sender = LocateClientObject(senderID); + IClientAPI receiver = LocateClientObject(receiverID); + + if (senderID != receiverID) + { + if (sender != null) + { + sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(senderID)); + } + + if (receiver != null) + { + receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(receiverID)); + } + } + } + + /// + /// Informs the Money Grid Server of a transfer. + /// + /// + /// + /// + /// + public bool TransferMoneyonMoneyServer(UUID sourceId, UUID destId, int amount, int transactiontype, string description) + { + int aggregatePermInventory = 0; + int aggregatePermNextOwner = 0; + int flags = 0; + bool rvalue = false; + + IClientAPI cli = LocateClientObject(sourceId); + if (cli != null) + { + Scene userScene = null; + lock (m_rootAgents) + { + userScene = GetSceneByUUID(m_rootAgents[sourceId]); + } + if (userScene != null) + { + Hashtable ht = new Hashtable(); + ht["agentId"] = sourceId.ToString(); + ht["secureSessionId"] = cli.SecureSessionId.ToString(); + ht["regionId"] = userScene.RegionInfo.originRegionID.ToString(); + ht["secret"] = userScene.RegionInfo.regionSecret; + ht["currencySecret"] = " "; + ht["destId"] = destId.ToString(); + ht["cash"] = amount; + ht["aggregatePermInventory"] = aggregatePermInventory; + ht["aggregatePermNextOwner"] = aggregatePermNextOwner; + ht["flags"] = flags; + ht["transactionType"] = transactiontype; + ht["description"] = description; + + Hashtable hresult = genericCurrencyXMLRPCRequest(ht, "regionMoveMoney"); + + if ((bool) hresult["success"] == true) + { + int funds1 = 0; + int funds2 = 0; + try + { + funds1 = (Int32) hresult["funds"]; + } + catch (InvalidCastException) + { + funds1 = 0; + } + SetLocalFundsForAgentID(sourceId, funds1); + if (m_KnownClientFunds.ContainsKey(destId)) + { + try + { + funds2 = (Int32) hresult["funds2"]; + } + catch (InvalidCastException) + { + funds2 = 0; + } + SetLocalFundsForAgentID(destId, funds2); + } + + + rvalue = true; + } + else + { + cli.SendAgentAlertMessage((string) hresult["errorMessage"], true); + } + } + } + else + { + m_log.ErrorFormat("[MONEY]: Client {0} not found", sourceId.ToString()); + } + + return rvalue; + } + + public int GetRemoteBalance(UUID agentId) + { + int funds = 0; + + IClientAPI aClient = LocateClientObject(agentId); + if (aClient != null) + { + Scene s = LocateSceneClientIn(agentId); + if (s != null) + { + if (m_MoneyAddress.Length > 0) + { + Hashtable hbinfo = + GetBalanceForUserFromMoneyServer(aClient.AgentId, aClient.SecureSessionId, s.RegionInfo.originRegionID, + s.RegionInfo.regionSecret); + if ((bool) hbinfo["success"] == true) + { + try + { + funds = (Int32) hbinfo["funds"]; + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentId); + aClient.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); + } + catch (InvalidCastException) + { + funds = 0; + } + } + else + { + m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentId, + (string) hbinfo["errorMessage"]); + aClient.SendAlertMessage((string) hbinfo["errorMessage"]); + } + } + + SetLocalFundsForAgentID(agentId, funds); + SendMoneyBalance(aClient, agentId, aClient.SessionId, UUID.Zero); + } + else + { + m_log.Debug("[MONEY]: Got balance request update for agent that is here, but couldn't find which scene."); + } + } + else + { + m_log.Debug("[MONEY]: Got balance request update for agent that isn't here."); + } + return funds; + } + + public XmlRpcResponse GridMoneyUpdate(XmlRpcRequest request) + { + m_log.Debug("[MONEY]: Dynamic balance update called."); + Hashtable requestData = (Hashtable) request.Params[0]; + + if (requestData.ContainsKey("agentId")) + { + UUID agentId = UUID.Zero; + + UUID.TryParse((string) requestData["agentId"], out agentId); + if (agentId != UUID.Zero) + { + GetRemoteBalance(agentId); + } + else + { + m_log.Debug("[MONEY]: invalid agentId specified, dropping."); + } + } + else + { + m_log.Debug("[MONEY]: no agentId specified, dropping."); + } + XmlRpcResponse r = new XmlRpcResponse(); + Hashtable rparms = new Hashtable(); + rparms["success"] = true; + + r.Value = rparms; + return r; + } + + /// + /// XMLRPC handler to send alert message and sound to client + /// + public XmlRpcResponse UserAlert(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable requestData = (Hashtable) request.Params[0]; + + UUID agentId; + UUID soundId; + UUID regionId; + + UUID.TryParse((string) requestData["agentId"], out agentId); + UUID.TryParse((string) requestData["soundId"], out soundId); + UUID.TryParse((string) requestData["regionId"], out regionId); + string text = (string) requestData["text"]; + string secret = (string) requestData["secret"]; + + Scene userScene = GetSceneByUUID(regionId); + if (userScene != null) + { + if (userScene.RegionInfo.regionSecret == secret) + { + + IClientAPI client = LocateClientObject(agentId); + if (client != null) + { + + if (soundId != UUID.Zero) + client.SendPlayAttachedSound(soundId, UUID.Zero, UUID.Zero, 1.0f, 0); + + client.SendBlueBoxMessage(UUID.Zero, "", text); + + retparam.Add("success", true); + } + else + { + retparam.Add("success", false); + } + } + else + { + retparam.Add("success", false); + } + } + + ret.Value = retparam; + return ret; + } + + # region Standalone box enablers only + + public XmlRpcResponse quote_func(XmlRpcRequest request) + { + Hashtable requestData = (Hashtable) request.Params[0]; + UUID agentId = UUID.Zero; + int amount = 0; + Hashtable quoteResponse = new Hashtable(); + XmlRpcResponse returnval = new XmlRpcResponse(); + + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + UUID.TryParse((string) requestData["agentId"], out agentId); + try + { + amount = (Int32) requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + } + Hashtable currencyResponse = new Hashtable(); + currencyResponse.Add("estimatedCost", 0); + currencyResponse.Add("currencyBuy", amount); + + quoteResponse.Add("success", true); + quoteResponse.Add("currency", currencyResponse); + quoteResponse.Add("confirm", "asdfad9fj39ma9fj"); + + returnval.Value = quoteResponse; + return returnval; + } + + + quoteResponse.Add("success", false); + quoteResponse.Add("errorMessage", "Invalid parameters passed to the quote box"); + quoteResponse.Add("errorURI", "http://www.opensimulator.org/wiki"); + returnval.Value = quoteResponse; + return returnval; + } + + public XmlRpcResponse buy_func(XmlRpcRequest request) + { + Hashtable requestData = (Hashtable) request.Params[0]; + UUID agentId = UUID.Zero; + int amount = 0; + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + UUID.TryParse((string) requestData["agentId"], out agentId); + try + { + amount = (Int32) requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + } + if (agentId != UUID.Zero) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(agentId)) + { + m_KnownClientFunds[agentId] += amount; + } + else + { + m_KnownClientFunds.Add(agentId, amount); + } + } + IClientAPI client = LocateClientObject(agentId); + if (client != null) + { + SendMoneyBalance(client, agentId, client.SessionId, UUID.Zero); + } + } + } + XmlRpcResponse returnval = new XmlRpcResponse(); + Hashtable returnresp = new Hashtable(); + returnresp.Add("success", true); + returnval.Value = returnresp; + return returnval; + } + + public XmlRpcResponse preflightBuyLandPrep_func(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable membershiplevels = new Hashtable(); + ArrayList levels = new ArrayList(); + Hashtable level = new Hashtable(); + level.Add("id", "00000000-0000-0000-0000-000000000000"); + level.Add("description", "some level"); + levels.Add(level); + //membershiplevels.Add("levels",levels); + + Hashtable landuse = new Hashtable(); + landuse.Add("upgrade", false); + landuse.Add("action", "http://invaliddomaininvalid.com/"); + + Hashtable currency = new Hashtable(); + currency.Add("estimatedCost", 0); + + Hashtable membership = new Hashtable(); + membershiplevels.Add("upgrade", false); + membershiplevels.Add("action", "http://invaliddomaininvalid.com/"); + membershiplevels.Add("levels", membershiplevels); + + retparam.Add("success", true); + retparam.Add("currency", currency); + retparam.Add("membership", membership); + retparam.Add("landuse", landuse); + retparam.Add("confirm", "asdfajsdkfjasdkfjalsdfjasdf"); + + ret.Value = retparam; + + return ret; + } + + public XmlRpcResponse landBuy_func(XmlRpcRequest request) + { + XmlRpcResponse ret = new XmlRpcResponse(); + Hashtable retparam = new Hashtable(); + Hashtable requestData = (Hashtable) request.Params[0]; + + UUID agentId = UUID.Zero; + int amount = 0; + if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) + { + UUID.TryParse((string) requestData["agentId"], out agentId); + try + { + amount = (Int32) requestData["currencyBuy"]; + } + catch (InvalidCastException) + { + } + if (agentId != UUID.Zero) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(agentId)) + { + m_KnownClientFunds[agentId] += amount; + } + else + { + m_KnownClientFunds.Add(agentId, amount); + } + } + IClientAPI client = LocateClientObject(agentId); + if (client != null) + { + SendMoneyBalance(client, agentId, client.SessionId, UUID.Zero); + } + } + } + retparam.Add("success", true); + ret.Value = retparam; + + return ret; + } + + #endregion + + #region local Fund Management + + /// + /// Ensures that the agent accounting data is set up in this instance. + /// + /// + private void CheckExistAndRefreshFunds(UUID agentID) + { + lock (m_KnownClientFunds) + { + if (!m_KnownClientFunds.ContainsKey(agentID)) + { + m_KnownClientFunds.Add(agentID, m_stipend); + } + else + { + if (m_KnownClientFunds[agentID] <= m_minFundsBeforeRefresh) + { + m_KnownClientFunds[agentID] = m_stipend; + } + } + } + } + + /// + /// Gets the amount of Funds for an agent + /// + /// + /// + private int GetFundsForAgentID(UUID AgentID) + { + int returnfunds = 0; + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(AgentID)) + { + returnfunds = m_KnownClientFunds[AgentID]; + } + else + { + //throw new Exception("Unable to get funds."); + } + } + return returnfunds; + } + + private void SetLocalFundsForAgentID(UUID AgentID, int amount) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(AgentID)) + { + m_KnownClientFunds[AgentID] = amount; + } + else + { + m_KnownClientFunds.Add(AgentID, amount); + } + } + } + + #endregion + + #region Utility Helpers + + /// + /// Locates a IClientAPI for the client specified + /// + /// + /// + private IClientAPI LocateClientObject(UUID AgentID) + { + ScenePresence tPresence = null; + IClientAPI rclient = null; + + lock (m_scenel) + { + foreach (Scene _scene in m_scenel.Values) + { + tPresence = _scene.GetScenePresence(AgentID); + if (tPresence != null) + { + if (!tPresence.IsChildAgent) + { + rclient = tPresence.ControllingClient; + } + } + if (rclient != null) + { + return rclient; + } + } + } + return null; + } + + private Scene LocateSceneClientIn(UUID AgentId) + { + lock (m_scenel) + { + foreach (Scene _scene in m_scenel.Values) + { + ScenePresence tPresence = _scene.GetScenePresence(AgentId); + if (tPresence != null) + { + if (!tPresence.IsChildAgent) + { + return _scene; + } + } + } + } + return null; + } + + /// + /// Utility function Gets a Random scene in the instance. For when which scene exactly you're doing something with doesn't matter + /// + /// + public Scene GetRandomScene() + { + lock (m_scenel) + { + foreach (Scene rs in m_scenel.Values) + return rs; + } + return null; + } + + /// + /// Utility function to get a Scene by RegionID in a module + /// + /// + /// + public Scene GetSceneByUUID(UUID RegionID) + { + lock (m_scenel) + { + foreach (Scene rs in m_scenel.Values) + { + if (rs.RegionInfo.originRegionID == RegionID) + { + return rs; + } + } + } + return null; + } + + #endregion + + #region event Handlers + + public void requestPayPrice(IClientAPI client, UUID objectID) + { + Scene scene = LocateSceneClientIn(client.AgentId); + if (scene == null) + return; + + SceneObjectPart task = scene.GetSceneObjectPart(objectID); + if (task == null) + return; + SceneObjectGroup group = task.ParentGroup; + SceneObjectPart root = group.RootPart; + + client.SendPayPrice(objectID, root.PayPrice); + } + + /// + /// When the client closes the connection we remove their accounting info from memory to free up resources. + /// + /// + public void ClientClosed(UUID AgentID) + { + lock (m_KnownClientFunds) + { + if (m_keepMoneyAcrossLogins && m_MoneyAddress.Length == 0) + { + } + else + { + m_KnownClientFunds.Remove(AgentID); + } + } + } + + /// + /// Event called Economy Data Request handler. + /// + /// + public void EconomyDataRequestHandler(UUID agentId) + { + IClientAPI user = LocateClientObject(agentId); + + if (user != null) + { + user.SendEconomyData(EnergyEfficiency, ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, + PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, + PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, + TeleportMinPrice, TeleportPriceExponent); + } + } + + private void ValidateLandBuy(Object osender, EventManager.LandBuyArgs e) + { + if (m_MoneyAddress.Length == 0) + { + lock (m_KnownClientFunds) + { + if (m_KnownClientFunds.ContainsKey(e.agentId)) + { + // Does the sender have enough funds to give? + if (m_KnownClientFunds[e.agentId] >= e.parcelPrice) + { + lock (e) + { + e.economyValidated = true; + } + } + } + } + } + else + { + if (GetRemoteBalance(e.agentId) >= e.parcelPrice) + { + lock (e) + { + e.economyValidated = true; + } + } + } + } + + private void processLandBuy(Object osender, EventManager.LandBuyArgs e) + { + lock (e) + { + if (e.economyValidated == true && e.transactionID == 0) + { + e.transactionID = Util.UnixTimeSinceEpoch(); + + if (doMoneyTransfer(e.agentId, e.parcelOwnerID, e.parcelPrice, 0, "Land purchase")) + { + lock (e) + { + e.amountDebited = e.parcelPrice; + } + } + } + } + } + + /// + /// THis method gets called when someone pays someone else as a gift. + /// + /// + /// + private void MoneyTransferAction(Object osender, EventManager.MoneyTransferArgs e) + { + IClientAPI sender = null; + IClientAPI receiver = null; + + if (m_MoneyAddress.Length > 0) // Handled on server + e.description = String.Empty; + + if (e.transactiontype == 5008) // Object gets paid + { + sender = LocateClientObject(e.sender); + if (sender != null) + { + SceneObjectPart part = findPrim(e.receiver); + if (part == null) + return; + + string name = resolveAgentName(part.OwnerID); + if (name == String.Empty) + name = "(hippos)"; + + receiver = LocateClientObject(part.OwnerID); + + string description = String.Format("Paid {0} via object {1}", name, e.description); + bool transactionresult = doMoneyTransfer(e.sender, part.OwnerID, e.amount, e.transactiontype, description); + + if (transactionresult) + { + ObjectPaid handlerOnObjectPaid = OnObjectPaid; + if (handlerOnObjectPaid != null) + { + handlerOnObjectPaid(e.receiver, e.sender, e.amount); + } + } + + if (e.sender != e.receiver) + { + sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.sender)); + } + if (receiver != null) + { + receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(part.OwnerID)); + } + } + return; + } + + sender = LocateClientObject(e.sender); + if (sender != null) + { + receiver = LocateClientObject(e.receiver); + + bool transactionresult = doMoneyTransfer(e.sender, e.receiver, e.amount, e.transactiontype, e.description); + + if (e.sender != e.receiver) + { + if (sender != null) + { + sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.sender)); + } + } + + if (receiver != null) + { + receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.receiver)); + } + } + else + { + m_log.Warn("[MONEY]: Potential Fraud Warning, got money transfer request for avatar that isn't in this simulator - Details; Sender:" + + e.sender.ToString() + " Receiver: " + e.receiver.ToString() + " Amount: " + e.amount.ToString()); + } + } + + /// + /// Event Handler for when a root agent becomes a child agent + /// + /// + private void MakeChildAgent(ScenePresence avatar) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (m_rootAgents[avatar.UUID] == avatar.Scene.RegionInfo.originRegionID) + { + m_rootAgents.Remove(avatar.UUID); +// m_log.Debug("[MONEY]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent"); + } + } + } + } + + /// + /// Event Handler for when the client logs out. + /// + /// + private void ClientLoggedOut(UUID AgentId) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + //m_log.Info("[MONEY]: Removing " + AgentId + ". Agent logged out."); + } + } + } + + /// + /// Call this when the client disconnects. + /// + /// + public void ClientClosed(IClientAPI client) + { + ClientClosed(client.AgentId); + } + + /// + /// Event Handler for when an Avatar enters one of the parcels in the simulator. + /// + /// + /// + /// + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (avatar.Scene.RegionInfo.originRegionID != m_rootAgents[avatar.UUID]) + { + m_rootAgents[avatar.UUID] = avatar.Scene.RegionInfo.originRegionID; + + + //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + // Claim User! my user! Mine mine mine! + if (m_MoneyAddress.Length > 0) + { + Scene RegionItem = GetSceneByUUID(regionID); + if (RegionItem != null) + { + Hashtable hresult = + claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); + if ((bool)hresult["success"] == true) + { + int funds = 0; + try + { + funds = (Int32)hresult["funds"]; + } + catch (InvalidCastException) + { + } + SetLocalFundsForAgentID(avatar.UUID, funds); + } + else + { + avatar.ControllingClient.SendAgentAlertMessage((string)hresult["errorMessage"], true); + } + } + } + } + else + { + ILandObject obj = avatar.Scene.LandChannel.GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); + if ((obj.landData.Flags & (uint)Parcel.ParcelFlags.AllowDamage) != 0) + { + avatar.Invulnerable = false; + } + else + { + avatar.Invulnerable = true; + } + } + } + else + { + lock (m_rootAgents) + { + m_rootAgents.Add(avatar.UUID, avatar.Scene.RegionInfo.originRegionID); + } + if (m_MoneyAddress.Length > 0) + { + Scene RegionItem = GetSceneByUUID(regionID); + if (RegionItem != null) + { + Hashtable hresult = claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); + if ((bool) hresult["success"] == true) + { + int funds = 0; + try + { + funds = (Int32) hresult["funds"]; + } + catch (InvalidCastException) + { + } + SetLocalFundsForAgentID(avatar.UUID, funds); + } + else + { + avatar.ControllingClient.SendAgentAlertMessage((string) hresult["errorMessage"], true); + } + } + } + + //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); + } + } + //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); + } + + public int GetBalance(IClientAPI client) + { + GetClientFunds(client); + + lock (m_KnownClientFunds) + { + if (!m_KnownClientFunds.ContainsKey(client.AgentId)) + return 0; + + return m_KnownClientFunds[client.AgentId]; + } + } + + // Please do not refactor these to be just one method + // Existing implementations need the distinction + // + public bool UploadCovered(IClientAPI client) + { + return AmountCovered(client, PriceUpload); + } + + public bool GroupCreationCovered(IClientAPI client) + { + return AmountCovered(client, PriceGroupCreate); + } + + public bool AmountCovered(IClientAPI client, int amount) + { + if (GetBalance(client) < amount) + return false; + return true; + } + + #endregion + + public void ObjectBuy(IClientAPI remoteClient, UUID agentID, + UUID sessionID, UUID groupID, UUID categoryID, + uint localID, byte saleType, int salePrice) + { + GetClientFunds(remoteClient); + + if (!m_KnownClientFunds.ContainsKey(remoteClient.AgentId)) + { + remoteClient.SendAgentAlertMessage("Unable to buy now. Your account balance was not found.", false); + return; + } + + int funds = m_KnownClientFunds[remoteClient.AgentId]; + + if (salePrice != 0 && funds < salePrice) + { + remoteClient.SendAgentAlertMessage("Unable to buy now. You don't have sufficient funds.", false); + return; + } + + Scene s = LocateSceneClientIn(remoteClient.AgentId); + + SceneObjectPart part = s.GetSceneObjectPart(localID); + if (part == null) + { + remoteClient.SendAgentAlertMessage("Unable to buy now. The object was not found.", false); + return; + } + + if (s.PerformObjectBuy(remoteClient, categoryID, localID, saleType)) + doMoneyTransfer(remoteClient.AgentId, part.OwnerID, salePrice, 5000, "Object buy"); + } + } + + public enum TransactionType : int + { + SystemGenerated = 0, + RegionMoneyRequest = 1, + Gift = 2, + Purchase = 3 + } + + +} diff --git a/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs b/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs new file mode 100644 index 0000000..7326373 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Dialog/DialogModule.cs @@ -0,0 +1,143 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Dialog +{ + public class DialogModule : IRegionModule, IDialogModule + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() {} + public void Close() {} + public string Name { get { return "Dialog Module"; } } + public bool IsSharedModule { get { return false; } } + + public void SendAlertToUser(IClientAPI client, string message) + { + SendAlertToUser(client, message, false); + } + + public void SendAlertToUser(IClientAPI client, string message, bool modal) + { + client.SendAgentAlertMessage(message, modal); + } + + public void SendAlertToUser(UUID agentID, string message) + { + SendAlertToUser(agentID, message, false); + } + + public void SendAlertToUser(UUID agentID, string message, bool modal) + { + ScenePresence sp = m_scene.GetScenePresence(agentID); + + if (sp != null) + sp.ControllingClient.SendAgentAlertMessage(message, modal); + } + + public void SendAlertToUser(string firstName, string lastName, string message, bool modal) + { + List presenceList = m_scene.GetScenePresences(); + + foreach (ScenePresence presence in presenceList) + { + if (presence.Firstname == firstName && presence.Lastname == lastName) + { + presence.ControllingClient.SendAgentAlertMessage(message, modal); + break; + } + } + } + + public void SendGeneralAlert(string message) + { + List presenceList = m_scene.GetScenePresences(); + + foreach (ScenePresence presence in presenceList) + { + if (!presence.IsChildAgent) + presence.ControllingClient.SendAlertMessage(message); + } + } + + public void SendDialogToUser( + UUID avatarID, string objectName, UUID objectID, UUID ownerID, + string message, UUID textureID, int ch, string[] buttonlabels) + { + ScenePresence sp = m_scene.GetScenePresence(avatarID); + + if (sp != null) + sp.ControllingClient.SendDialog(objectName, objectID, ownerID, message, textureID, ch, buttonlabels); + } + + public void SendUrlToUser( + UUID avatarID, string objectName, UUID objectID, UUID ownerID, bool groupOwned, string message, string url) + { + ScenePresence sp = m_scene.GetScenePresence(avatarID); + + if (sp != null) + sp.ControllingClient.SendLoadURL(objectName, objectID, ownerID, groupOwned, message, url); + } + + public void SendNotificationToUsersInEstate( + UUID fromAvatarID, string fromAvatarName, string message) + { + // TODO: This does not yet do what it says on the tin - it only sends the message to users in the same + // region as the sending avatar. + SendNotificationToUsersInRegion(fromAvatarID, fromAvatarName, message); + } + + public void SendNotificationToUsersInRegion( + UUID fromAvatarID, string fromAvatarName, string message) + { + List presenceList = m_scene.GetScenePresences(); + + foreach (ScenePresence presence in presenceList) + { + if (!presence.IsChildAgent) + presence.ControllingClient.SendBlueBoxMessage(fromAvatarID, fromAvatarName, message); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs new file mode 100644 index 0000000..fb4d08a --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Friends/FriendsModule.cs @@ -0,0 +1,1003 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Friends +{ + /* + This module handles adding/removing friends, and the the presence + notification process for login/logoff of friends. + + The presence notification works as follows: + - After the user initially connects to a region (so we now have a UDP + connection to work with), this module fetches the friends of user + (those are cached), their on-/offline status, and info about the + region they are in from the MessageServer. + - (*) It then informs the user about the on-/offline status of her friends. + - It then informs all online friends currently on this region-server about + user's new online status (this will save some network traffic, as local + messages don't have to be transferred inter-region, and it will be all + that has to be done in Standalone Mode). + - For the rest of the online friends (those not on this region-server), + this module uses the provided region-information to map users to + regions, and sends one notification to every region containing the + friends to inform on that server. + - The region-server will handle that in the following way: + - If it finds the friend, it informs her about the user being online. + - If it doesn't find the friend (maybe she TPed away in the meantime), + it stores that information. + - After it processed all friends, it returns the list of friends it + couldn't find. + - If this list isn't empty, the FriendsModule re-requests information + about those online friends that have been missed and starts at (*) + again until all friends have been found, or until it tried 3 times + (to prevent endless loops due to some uncaught error). + + NOTE: Online/Offline notifications don't need to be sent on region change. + + We implement two XMLRpc handlers here, handling all the inter-region things + we have to handle: + - On-/Offline-Notifications (bulk) + - Terminate Friendship messages (single) + */ + + public class FriendsModule : IRegionModule, IFriendsModule + { + private class Transaction + { + public UUID agentID; + public string agentName; + public uint count; + + public Transaction(UUID agentID, string agentName) + { + this.agentID = agentID; + this.agentName = agentName; + this.count = 1; + } + } + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Cache m_friendLists = new Cache(CacheFlags.AllowUpdate); + + private Dictionary m_rootAgents = new Dictionary(); + + private Dictionary m_pendingCallingcardRequests = new Dictionary(); + + private Scene m_initialScene; // saves a lookup if we don't have a specific scene + private Dictionary m_scenes = new Dictionary(); + private IMessageTransferModule m_TransferModule = null; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_scenes) + { + if (m_scenes.Count == 0) + { + scene.CommsManager.HttpServer.AddXmlRPCHandler("presence_update_bulk", processPresenceUpdateBulk); + scene.CommsManager.HttpServer.AddXmlRPCHandler("terminate_friend", processTerminateFriend); + m_friendLists.DefaultTTL = new TimeSpan(1, 0, 0); // store entries for one hour max + m_initialScene = scene; + } + + if (!m_scenes.ContainsKey(scene.RegionInfo.RegionHandle)) + m_scenes[scene.RegionInfo.RegionHandle] = scene; + } + + scene.RegisterModuleInterface(this); + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnClientClosed += ClientClosed; + } + + public void PostInitialise() + { + if (m_scenes.Count > 0) + { + m_TransferModule = m_initialScene.RequestModuleInterface(); + } + if (m_TransferModule == null) + m_log.Error("[FRIENDS]: Unable to find a message transfer module, friendship offers will not work"); + } + + public void Close() + { + } + + public string Name + { + get { return "FriendsModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + /// + /// Receive presence information changes about clients in other regions. + /// + /// + /// + public XmlRpcResponse processPresenceUpdateBulk(XmlRpcRequest req) + { + Hashtable requestData = (Hashtable)req.Params[0]; + + List friendsNotHere = new List(); + + // this is called with the expectation that all the friends in the request are on this region-server. + // But as some time passed since we checked (on the other region-server, via the MessagingServer), + // some of the friends might have teleported away. + // Actually, even now, between this line and the sending below, some people could TP away. So, + // we'll have to lock the m_rootAgents list for the duration to prevent/delay that. + lock (m_rootAgents) + { + List friendsHere = new List(); + + try + { + UUID agentID = new UUID((string)requestData["agentID"]); + bool agentOnline = (bool)requestData["agentOnline"]; + int count = (int)requestData["friendCount"]; + for (int i = 0; i < count; ++i) + { + UUID uuid; + if (UUID.TryParse((string)requestData["friendID_" + i], out uuid)) + { + if (m_rootAgents.ContainsKey(uuid)) friendsHere.Add(GetRootPresenceFromAgentID(uuid)); + else friendsNotHere.Add(uuid); + } + } + + // now send, as long as they are still here... + UUID[] agentUUID = new UUID[] { agentID }; + if (agentOnline) + { + foreach (ScenePresence agent in friendsHere) + { + agent.ControllingClient.SendAgentOnline(agentUUID); + } + } + else + { + foreach (ScenePresence agent in friendsHere) + { + agent.ControllingClient.SendAgentOffline(agentUUID); + } + } + } + catch(Exception e) + { + m_log.Warn("[FRIENDS]: Got exception while parsing presence_update_bulk request:", e); + } + } + + // no need to lock anymore; if TPs happen now, worst case is that we have an additional agent in this region, + // which should be caught on the next iteration... + Hashtable result = new Hashtable(); + int idx = 0; + foreach (UUID uuid in friendsNotHere) + { + result["friendID_" + idx++] = uuid.ToString(); + } + result["friendCount"] = idx; + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = result; + + return response; + } + + public XmlRpcResponse processTerminateFriend(XmlRpcRequest req) + { + Hashtable requestData = (Hashtable)req.Params[0]; + + bool success = false; + + UUID agentID; + UUID friendID; + if (requestData.ContainsKey("agentID") && UUID.TryParse((string)requestData["agentID"], out agentID) && + requestData.ContainsKey("friendID") && UUID.TryParse((string)requestData["friendID"], out friendID)) + { + // try to find it and if it is there, prevent it to vanish before we sent the message + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(agentID)) + { + m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", friendID, agentID); + GetRootPresenceFromAgentID(agentID).ControllingClient.SendTerminateFriend(friendID); + success = true; + } + } + } + + // return whether we were successful + Hashtable result = new Hashtable(); + result["success"] = success; + + XmlRpcResponse response = new XmlRpcResponse(); + response.Value = result; + return response; + } + + private void OnNewClient(IClientAPI client) + { + // All friends establishment protocol goes over instant message + // There's no way to send a message from the sim + // to a user to 'add a friend' without causing dialog box spam + + // Subscribe to instant messages + client.OnInstantMessage += OnInstantMessage; + + // Friend list management + client.OnApproveFriendRequest += OnApproveFriendRequest; + client.OnDenyFriendRequest += OnDenyFriendRequest; + client.OnTerminateFriendship += OnTerminateFriendship; + + // ... calling card handling... + client.OnOfferCallingCard += OnOfferCallingCard; + client.OnAcceptCallingCard += OnAcceptCallingCard; + client.OnDeclineCallingCard += OnDeclineCallingCard; + + // we need this one exactly once per agent session (see comments in the handler below) + client.OnEconomyDataRequest += OnEconomyDataRequest; + + // if it leaves, we want to know, too + client.OnLogout += OnLogout; + } + + private void ClientClosed(UUID AgentId) + { + // agent's client was closed. As we handle logout in OnLogout, this here has only to handle + // TPing away (root agent is closed) or TPing/crossing in a region far enough away (client + // agent is closed). + // NOTE: In general, this doesn't mean that the agent logged out, just that it isn't around + // in one of the regions here anymore. + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + } + } + } + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + lock (m_rootAgents) + { + m_rootAgents[avatar.UUID] = avatar.RegionHandle; + // Claim User! my user! Mine mine mine! + } + } + + private void MakeChildAgent(ScenePresence avatar) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + // only delete if the region matches. As this is a shared module, the avatar could be + // root agent in another region on this server. + if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) + { + m_rootAgents.Remove(avatar.UUID); +// m_log.Debug("[FRIEND]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent"); + } + } + } + } + + private ScenePresence GetRootPresenceFromAgentID(UUID AgentID) + { + ScenePresence returnAgent = null; + lock (m_scenes) + { + ScenePresence queryagent = null; + foreach (Scene scene in m_scenes.Values) + { + queryagent = scene.GetScenePresence(AgentID); + if (queryagent != null) + { + if (!queryagent.IsChildAgent) + { + returnAgent = queryagent; + break; + } + } + } + } + return returnAgent; + } + + private ScenePresence GetAnyPresenceFromAgentID(UUID AgentID) + { + ScenePresence returnAgent = null; + lock (m_scenes) + { + ScenePresence queryagent = null; + foreach (Scene scene in m_scenes.Values) + { + queryagent = scene.GetScenePresence(AgentID); + if (queryagent != null) + { + returnAgent = queryagent; + break; + } + } + } + return returnAgent; + } + + public void OfferFriendship(UUID fromUserId, IClientAPI toUserClient, string offerMessage) + { + CachedUserInfo userInfo = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(fromUserId); + + if (userInfo != null) + { + GridInstantMessage msg = new GridInstantMessage( + toUserClient.Scene, fromUserId, userInfo.UserProfile.Name, toUserClient.AgentId, + (byte)InstantMessageDialog.FriendshipOffered, offerMessage, false, Vector3.Zero); + + FriendshipOffered(msg); + } + else + { + m_log.ErrorFormat("[FRIENDS]: No user found for id {0} in OfferFriendship()", fromUserId); + } + } + + #region FriendRequestHandling + + private void OnInstantMessage(IClientAPI client, GridInstantMessage im) + { + // Friend Requests go by Instant Message.. using the dialog param + // https://wiki.secondlife.com/wiki/ImprovedInstantMessage + + if (im.dialog == (byte)InstantMessageDialog.FriendshipOffered) // 38 + { + // fromAgentName is the *destination* name (the friend we offer friendship to) + ScenePresence initiator = GetAnyPresenceFromAgentID(new UUID(im.fromAgentID)); + im.fromAgentName = initiator != null ? initiator.Name : "(hippo)"; + + FriendshipOffered(im); + } + else if (im.dialog == (byte)InstantMessageDialog.FriendshipAccepted) // 39 + { + FriendshipAccepted(client, im); + } + else if (im.dialog == (byte)InstantMessageDialog.FriendshipDeclined) // 40 + { + FriendshipDeclined(client, im); + } + } + + /// + /// Invoked when a user offers a friendship. + /// + /// + /// + /// + private void FriendshipOffered(GridInstantMessage im) + { + // this is triggered by the initiating agent: + // A local agent offers friendship to some possibly remote friend. + // A IM is triggered, processed here and sent to the friend (possibly in a remote region). + + m_log.DebugFormat("[FRIEND]: Offer(38) - From: {0}, FromName: {1} To: {2}, Session: {3}, Message: {4}, Offline {5}", + im.fromAgentID, im.fromAgentName, im.toAgentID, im.imSessionID, im.message, im.offline); + + // 1.20 protocol sends an UUID in the message field, instead of the friendship offer text. + // For interoperability, we have to clear that + if (Util.isUUID(im.message)) im.message = ""; + + // be sneeky and use the initiator-UUID as transactionID. This means we can be stateless. + // we have to look up the agent name on friendship-approval, though. + im.imSessionID = im.fromAgentID; + + if (m_TransferModule != null) + { + // Send it to whoever is the destination. + // If new friend is local, it will send an IM to the viewer. + // If new friend is remote, it will cause a OnGridInstantMessage on the remote server + m_TransferModule.SendInstantMessage( + im, + delegate(bool success) + { + m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); + } + ); + } + } + + /// + /// Invoked when a user accepts a friendship offer. + /// + /// + /// + private void FriendshipAccepted(IClientAPI client, GridInstantMessage im) + { + m_log.DebugFormat("[FRIEND]: 39 - from client {0}, agent {2} {3}, imsession {4} to {5}: {6} (dialog {7})", + client.AgentId, im.fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog); + } + + /// + /// Invoked when a user declines a friendship offer. + /// + /// May not currently be used - see OnDenyFriendRequest() instead + /// + /// + private void FriendshipDeclined(IClientAPI client, GridInstantMessage im) + { + UUID fromAgentID = new UUID(im.fromAgentID); + UUID toAgentID = new UUID(im.toAgentID); + + // declining the friendship offer causes a type 40 IM being sent to the (possibly remote) initiator + // toAgentID is initiator, fromAgentID declined friendship + m_log.DebugFormat("[FRIEND]: 40 - from client {0}, agent {1} {2}, imsession {3} to {4}: {5} (dialog {6})", + client != null ? client.AgentId.ToString() : "", + fromAgentID, im.fromAgentName, im.imSessionID, im.toAgentID, im.message, im.dialog); + + // Send the decline to whoever is the destination. + GridInstantMessage msg + = new GridInstantMessage( + client.Scene, fromAgentID, client.Name, toAgentID, + im.dialog, im.message, im.offline != 0, im.Position); + + // If new friend is local, it will send an IM to the viewer. + // If new friend is remote, it will cause a OnGridInstantMessage on the remote server + m_TransferModule.SendInstantMessage(msg, + delegate(bool success) { + m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); + } + ); + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // This event won't be raised unless we have that agent, + // so we can depend on the above not trying to send + // via grid again + m_log.DebugFormat("[FRIEND]: Got GridIM from {0}, to {1}, imSession {2}, message {3}, dialog {4}", + msg.fromAgentID, msg.toAgentID, msg.imSessionID, msg.message, msg.dialog); + + if (msg.dialog == (byte)InstantMessageDialog.FriendshipOffered || + msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted || + msg.dialog == (byte)InstantMessageDialog.FriendshipDeclined) + { + // this should succeed as we *know* the root agent is here. + m_TransferModule.SendInstantMessage(msg, + delegate(bool success) { + m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); + } + ); + } + + if (msg.dialog == (byte)InstantMessageDialog.FriendshipAccepted) + { + // for accept friendship, we have to do a bit more + ApproveFriendship(new UUID(msg.fromAgentID), new UUID(msg.toAgentID), msg.fromAgentName); + } + } + + private void ApproveFriendship(UUID fromAgentID, UUID toAgentID, string fromName) + { + m_log.DebugFormat("[FRIEND]: Approve friendship from {0} (ID: {1}) to {2}", + fromAgentID, fromName, toAgentID); + + // a new friend was added in the initiator's and friend's data, so the cache entries are wrong now. + lock (m_friendLists) + { + m_friendLists.Invalidate(fromAgentID); + m_friendLists.Invalidate(toAgentID); + } + + // now send presence update and add a calling card for the new friend + + ScenePresence initiator = GetAnyPresenceFromAgentID(toAgentID); + if (initiator == null) + { + // quite wrong. Shouldn't happen. + m_log.WarnFormat("[FRIEND]: Coudn't find initiator of friend request {0}", toAgentID); + return; + } + + m_log.DebugFormat("[FRIEND]: Tell {0} that {1} is online", + initiator.Name, fromName); + // tell initiator that friend is online + initiator.ControllingClient.SendAgentOnline(new UUID[] { fromAgentID }); + + // find the folder for the friend... + InventoryFolderImpl folder = + initiator.Scene.CommsManager.UserProfileCacheService.GetUserDetails(toAgentID).FindFolderForType((int)InventoryType.CallingCard); + if (folder != null) + { + // ... and add the calling card + CreateCallingCard(initiator.ControllingClient, fromAgentID, folder.ID, fromName); + } + } + + private void OnApproveFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List callingCardFolders) + { + m_log.DebugFormat("[FRIEND]: Got approve friendship from {0} {1}, agentID {2}, tid {3}", + client.Name, client.AgentId, agentID, friendID); + + // store the new friend persistently for both avatars + m_initialScene.StoreAddFriendship(friendID, agentID, (uint) FriendRights.CanSeeOnline); + + // The cache entries aren't valid anymore either, as we just added a friend to both sides. + lock (m_friendLists) + { + m_friendLists.Invalidate(agentID); + m_friendLists.Invalidate(friendID); + } + + // if it's a local friend, we don't have to do the lookup + ScenePresence friendPresence = GetAnyPresenceFromAgentID(friendID); + + if (friendPresence != null) + { + m_log.Debug("[FRIEND]: Local agent detected."); + + // create calling card + CreateCallingCard(client, friendID, callingCardFolders[0], friendPresence.Name); + + // local message means OnGridInstantMessage won't be triggered, so do the work here. + friendPresence.ControllingClient.SendInstantMessage(agentID, agentID.ToString(), friendID, client.Name, + (byte)InstantMessageDialog.FriendshipAccepted, + (uint)Util.UnixTimeSinceEpoch()); + ApproveFriendship(agentID, friendID, client.Name); + } + else + { + m_log.Debug("[FRIEND]: Remote agent detected."); + + // fetch the friend's name for the calling card. + CachedUserInfo info = m_initialScene.CommsManager.UserProfileCacheService.GetUserDetails(friendID); + + // create calling card + CreateCallingCard(client, friendID, callingCardFolders[0], + info.UserProfile.FirstName + " " + info.UserProfile.SurName); + + // Compose (remote) response to friend. + GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID, + (byte)InstantMessageDialog.FriendshipAccepted, + agentID.ToString(), false, Vector3.Zero); + if (m_TransferModule != null) + { + m_TransferModule.SendInstantMessage(msg, + delegate(bool success) { + m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); + } + ); + } + } + + // tell client that new friend is online + client.SendAgentOnline(new UUID[] { friendID }); + } + + private void OnDenyFriendRequest(IClientAPI client, UUID agentID, UUID friendID, List callingCardFolders) + { + m_log.DebugFormat("[FRIEND]: Got deny friendship from {0} {1}, agentID {2}, tid {3}", + client.Name, client.AgentId, agentID, friendID); + + // Compose response to other agent. + GridInstantMessage msg = new GridInstantMessage(client.Scene, agentID, client.Name, friendID, + (byte)InstantMessageDialog.FriendshipDeclined, + agentID.ToString(), false, Vector3.Zero); + // send decline to initiator + if (m_TransferModule != null) + { + m_TransferModule.SendInstantMessage(msg, + delegate(bool success) { + m_log.DebugFormat("[FRIEND]: sending IM success = {0}", success); + } + ); + } + } + + private void OnTerminateFriendship(IClientAPI client, UUID agentID, UUID exfriendID) + { + // client.AgentId == agentID! + + // this removes the friends from the stored friendlists. After the next login, they will be gone... + m_initialScene.StoreRemoveFriendship(agentID, exfriendID); + + // ... now tell the two involved clients that they aren't friends anymore. + + // I don't know why we have to tell , as this was caused by her, but that's how it works in SL... + client.SendTerminateFriend(exfriendID); + + // now send the friend, if online + ScenePresence presence = GetAnyPresenceFromAgentID(exfriendID); + if (presence != null) + { + m_log.DebugFormat("[FRIEND]: Sending terminate friend {0} to agent {1}", agentID, exfriendID); + presence.ControllingClient.SendTerminateFriend(agentID); + } + else + { + // retry 3 times, in case the agent TPed from the last known region... + for (int retry = 0; retry < 3; ++retry) + { + // wasn't sent, so ex-friend wasn't around on this region-server. Fetch info and try to send + UserAgentData data = m_initialScene.CommsManager.UserService.GetAgentByUUID(exfriendID); + + if (null == data) + break; + + if (!data.AgentOnline) + { + m_log.DebugFormat("[FRIEND]: {0} is offline, so not sending TerminateFriend", exfriendID); + break; // if ex-friend isn't online, we don't need to send + } + + m_log.DebugFormat("[FRIEND]: Sending remote terminate friend {0} to agent {1}@{2}", + agentID, exfriendID, data.Handle); + + // try to send to foreign region, retry if it fails (friend TPed away, for example) + if (m_initialScene.TriggerTerminateFriend(data.Handle, exfriendID, agentID)) break; + } + } + + // clean up cache: FriendList is wrong now... + lock (m_friendLists) + { + m_friendLists.Invalidate(agentID); + m_friendLists.Invalidate(exfriendID); + } + } + + #endregion + + #region CallingCards + + private void OnOfferCallingCard(IClientAPI client, UUID destID, UUID transactionID) + { + m_log.DebugFormat("[CALLING CARD]: got offer from {0} for {1}, transaction {2}", + client.AgentId, destID, transactionID); + // This might be slightly wrong. On a multi-region server, we might get the child-agent instead of the root-agent + // (or the root instead of the child) + ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); + if (destAgent == null) + { + client.SendAlertMessage("The person you have offered a card to can't be found anymore."); + return; + } + + lock (m_pendingCallingcardRequests) + { + m_pendingCallingcardRequests[transactionID] = client.AgentId; + } + // inform the destination agent about the offer + destAgent.ControllingClient.SendOfferCallingCard(client.AgentId, transactionID); + } + + private void CreateCallingCard(IClientAPI client, UUID creator, UUID folder, string name) + { + InventoryItemBase item = new InventoryItemBase(); + item.AssetID = UUID.Zero; + item.AssetType = (int)AssetType.CallingCard; + item.BasePermissions = (uint)PermissionMask.Copy; + item.CreationDate = Util.UnixTimeSinceEpoch(); + item.Creator = creator; + item.CurrentPermissions = item.BasePermissions; + item.Description = ""; + item.EveryOnePermissions = (uint)PermissionMask.None; + item.Flags = 0; + item.Folder = folder; + item.GroupID = UUID.Zero; + item.GroupOwned = false; + item.ID = UUID.Random(); + item.InvType = (int)InventoryType.CallingCard; + item.Name = name; + item.NextPermissions = item.EveryOnePermissions; + item.Owner = client.AgentId; + item.SalePrice = 10; + item.SaleType = (byte)SaleType.Not; + ((Scene)client.Scene).AddInventoryItem(client, item); + } + + private void OnAcceptCallingCard(IClientAPI client, UUID transactionID, UUID folderID) + { + m_log.DebugFormat("[CALLING CARD]: User {0} ({1} {2}) accepted tid {3}, folder {4}", + client.AgentId, + client.FirstName, client.LastName, + transactionID, folderID); + UUID destID; + lock (m_pendingCallingcardRequests) + { + if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + { + m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", + client.Name); + return; + } + // else found pending calling card request with that transaction. + m_pendingCallingcardRequests.Remove(transactionID); + } + + + ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); + // inform sender of the card that destination declined the offer + if (destAgent != null) destAgent.ControllingClient.SendAcceptCallingCard(transactionID); + + // put a calling card into the inventory of receiver + CreateCallingCard(client, destID, folderID, destAgent.Name); + } + + private void OnDeclineCallingCard(IClientAPI client, UUID transactionID) + { + m_log.DebugFormat("[CALLING CARD]: User {0} (ID:{1}) declined card, tid {2}", + client.Name, client.AgentId, transactionID); + UUID destID; + lock (m_pendingCallingcardRequests) + { + if (!m_pendingCallingcardRequests.TryGetValue(transactionID, out destID)) + { + m_log.WarnFormat("[CALLING CARD]: Got a AcceptCallingCard from {0} without an offer before.", + client.Name); + return; + } + // else found pending calling card request with that transaction. + m_pendingCallingcardRequests.Remove(transactionID); + } + + ScenePresence destAgent = GetAnyPresenceFromAgentID(destID); + // inform sender of the card that destination declined the offer + if (destAgent != null) destAgent.ControllingClient.SendDeclineCallingCard(transactionID); + } + + /// + /// Send presence information about a client to other clients in both this region and others. + /// + /// + /// + /// + private void SendPresenceState(IClientAPI client, List friendList, bool iAmOnline) + { + //m_log.DebugFormat("[FRIEND]: {0} logged {1}; sending presence updates", client.Name, iAmOnline ? "in" : "out"); + + if (friendList == null || friendList.Count == 0) + { + //m_log.DebugFormat("[FRIEND]: {0} doesn't have friends.", client.Name); + return; // nothing we can do if she doesn't have friends... + } + + // collect sets of friendIDs; to send to (online and offline), and to receive from + // TODO: If we ever switch to .NET >= 3, replace those Lists with HashSets. + // I can't believe that we have Dictionaries, but no Sets, considering Java introduced them years ago... + List friendIDsToSendTo = new List(); + List candidateFriendIDsToReceive = new List(); + + foreach (FriendListItem item in friendList) + { + if (((item.FriendListOwnerPerms | item.FriendPerms) & (uint)FriendRights.CanSeeOnline) != 0) + { + // friend is allowed to see my presence => add + if ((item.FriendListOwnerPerms & (uint)FriendRights.CanSeeOnline) != 0) + friendIDsToSendTo.Add(item.Friend); + + if ((item.FriendPerms & (uint)FriendRights.CanSeeOnline) != 0) + candidateFriendIDsToReceive.Add(item.Friend); + } + } + + // we now have a list of "interesting" friends (which we have to find out on-/offline state for), + // friends we want to send our online state to (if *they* are online, too), and + // friends we want to receive online state for (currently unknown whether online or not) + + // as this processing might take some time and friends might TP away, we try up to three times to + // reach them. Most of the time, we *will* reach them, and this loop won't loop + int retry = 0; + do + { + // build a list of friends to look up region-information and on-/offline-state for + List friendIDsToLookup = new List(friendIDsToSendTo); + foreach (UUID uuid in candidateFriendIDsToReceive) + { + if (!friendIDsToLookup.Contains(uuid)) friendIDsToLookup.Add(uuid); + } + + m_log.DebugFormat( + "[FRIEND]: {0} to lookup, {1} to send to, {2} candidates to receive from for agent {3}", + friendIDsToLookup.Count, friendIDsToSendTo.Count, candidateFriendIDsToReceive.Count, client.Name); + + // we have to fetch FriendRegionInfos, as the (cached) FriendListItems don't + // necessarily contain the correct online state... + Dictionary friendRegions = m_initialScene.GetFriendRegionInfos(friendIDsToLookup); + m_log.DebugFormat( + "[FRIEND]: Found {0} regionInfos for {1} friends of {2}", + friendRegions.Count, friendIDsToLookup.Count, client.Name); + + // argument for SendAgentOn/Offline; we shouldn't generate that repeatedly within loops. + UUID[] agentArr = new UUID[] { client.AgentId }; + + // first, send to friend presence state to me, if I'm online... + if (iAmOnline) + { + List friendIDsToReceive = new List(); + + for (int i = candidateFriendIDsToReceive.Count - 1; i >= 0; --i) + { + UUID uuid = candidateFriendIDsToReceive[i]; + FriendRegionInfo info; + if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline) + { + friendIDsToReceive.Add(uuid); + } + } + + m_log.DebugFormat( + "[FRIEND]: Sending {0} online friends to {1}", friendIDsToReceive.Count, client.Name); + + if (friendIDsToReceive.Count > 0) + client.SendAgentOnline(friendIDsToReceive.ToArray()); + + // clear them for a possible second iteration; we don't have to repeat this + candidateFriendIDsToReceive.Clear(); + } + + // now, send my presence state to my friends + for (int i = friendIDsToSendTo.Count - 1; i >= 0; --i) + { + UUID uuid = friendIDsToSendTo[i]; + FriendRegionInfo info; + if (friendRegions.TryGetValue(uuid, out info) && info != null && info.isOnline) + { + // any client is good enough, root or child... + ScenePresence agent = GetAnyPresenceFromAgentID(uuid); + if (agent != null) + { + m_log.DebugFormat("[FRIEND]: Found local agent {0}", agent.Name); + + // friend is online and on this server... + if (iAmOnline) agent.ControllingClient.SendAgentOnline(agentArr); + else agent.ControllingClient.SendAgentOffline(agentArr); + + // done, remove it + friendIDsToSendTo.RemoveAt(i); + } + } + else + { + m_log.DebugFormat("[FRIEND]: Friend {0} ({1}) is offline; not sending.", uuid, i); + + // friend is offline => no need to try sending + friendIDsToSendTo.RemoveAt(i); + } + } + + m_log.DebugFormat("[FRIEND]: Have {0} friends to contact via inter-region comms.", friendIDsToSendTo.Count); + + // we now have all the friends left that are online (we think), but not on this region-server + if (friendIDsToSendTo.Count > 0) + { + // sort them into regions + Dictionary> friendsInRegion = new Dictionary>(); + foreach (UUID uuid in friendIDsToSendTo) + { + ulong handle = friendRegions[uuid].regionHandle; // this can't fail as we filtered above already + List friends; + if (!friendsInRegion.TryGetValue(handle, out friends)) + { + friends = new List(); + friendsInRegion[handle] = friends; + } + friends.Add(uuid); + } + m_log.DebugFormat("[FRIEND]: Found {0} regions to send to.", friendRegions.Count); + + // clear uuids list and collect missed friends in it for the next retry + friendIDsToSendTo.Clear(); + + // send bulk updates to the region + foreach (KeyValuePair> pair in friendsInRegion) + { + m_log.DebugFormat("[FRIEND]: Inform {0} friends in region {1} that user {2} is {3}line", + pair.Value.Count, pair.Key, client.Name, iAmOnline ? "on" : "off"); + + friendIDsToSendTo.AddRange(m_initialScene.InformFriendsInOtherRegion(client.AgentId, pair.Key, pair.Value, iAmOnline)); + } + } + // now we have in friendIDsToSendTo only the agents left that TPed away while we tried to contact them. + // In most cases, it will be empty, and it won't loop here. But sometimes, we have to work harder and try again... + } + while (++retry < 3 && friendIDsToSendTo.Count > 0); + } + + private void OnEconomyDataRequest(UUID agentID) + { + // KLUDGE: This is the only way I found to get a message (only) after login was completed and the + // client is connected enough to receive UDP packets). + // This packet seems to be sent only once, just after connection was established to the first + // region after login. + // We use it here to trigger a presence update; the old update-on-login was never be heard by + // the freshly logged in viewer, as it wasn't connected to the region at that time. + // TODO: Feel free to replace this by a better solution if you find one. + + // get the agent. This should work every time, as we just got a packet from it + //ScenePresence agent = GetRootPresenceFromAgentID(agentID); + // KLUDGE 2: As this is sent quite early, the avatar isn't here as root agent yet. So, we have to cheat a bit + ScenePresence agent = GetAnyPresenceFromAgentID(agentID); + + // just to be paranoid... + if (agent == null) + { + m_log.ErrorFormat("[FRIEND]: Got a packet from agent {0} who can't be found anymore!?", agentID); + return; + } + + List fl; + lock (m_friendLists) + { + fl = (List)m_friendLists.Get(agent.ControllingClient.AgentId, + m_initialScene.GetFriendList); + } + + // tell everyone that we are online + SendPresenceState(agent.ControllingClient, fl, true); + } + + private void OnLogout(IClientAPI remoteClient) + { + List fl; + lock (m_friendLists) + { + fl = (List)m_friendLists.Get(remoteClient.AgentId, + m_initialScene.GetFriendList); + } + + // tell everyone that we are offline + SendPresenceState(remoteClient, fl, false); + } + } + + #endregion +} diff --git a/OpenSim/Region/CoreModules/Avatar/Gestures/GesturesModule.cs b/OpenSim/Region/CoreModules/Avatar/Gestures/GesturesModule.cs new file mode 100644 index 0000000..a3a642f --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Gestures/GesturesModule.cs @@ -0,0 +1,104 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Reflection; + +namespace OpenSim.Region.CoreModules.Avatar.Gestures +{ + public class GesturesModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + + m_scene.EventManager.OnNewClient += OnNewClient; + } + + public void PostInitialise() {} + public void Close() {} + public string Name { get { return "Gestures Module"; } } + public bool IsSharedModule { get { return false; } } + + private void OnNewClient(IClientAPI client) + { + client.OnActivateGesture += ActivateGesture; + client.OnDeactivateGesture += DeactivateGesture; + } + + public virtual void ActivateGesture(IClientAPI client, UUID assetId, UUID gestureId) + { + CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(client.AgentId); + + if (userInfo != null) + { + InventoryItemBase item = userInfo.RootFolder.FindItem(gestureId); + if (item != null) + { + item.Flags = 1; + userInfo.UpdateItem(item); + } + else + m_log.ErrorFormat( + "[GESTURES]: Unable to find gesture to activate {0} for {1}", gestureId, client.Name); + } + else + m_log.ErrorFormat("[GESTURES]: Unable to find user {0}", client.Name); + } + + public virtual void DeactivateGesture(IClientAPI client, UUID gestureId) + { + CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(client.AgentId); + + if (userInfo != null) + { + InventoryItemBase item = userInfo.RootFolder.FindItem(gestureId); + if (item != null) + { + item.Flags = 0; + userInfo.UpdateItem(item); + } + else + m_log.ErrorFormat( + "[GESTURES]: Unable to find gesture to deactivate {0} for {1}", gestureId, client.Name); + } + else + m_log.ErrorFormat("[GESTURES]: Unable to find user {0}", client.Name); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Avatar/Groups/GroupsModule.cs b/OpenSim/Region/CoreModules/Avatar/Groups/GroupsModule.cs new file mode 100644 index 0000000..92d0fdd --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Groups/GroupsModule.cs @@ -0,0 +1,223 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Groups +{ + public class GroupsModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Dictionary m_GroupMap = + new Dictionary(); + + private Dictionary m_ClientMap = + new Dictionary(); + + private UUID opensimulatorGroupID = + new UUID("00000000-68f9-1111-024e-222222111123"); + + private List m_SceneList = new List(); + + private static GroupMembershipData osGroup = + new GroupMembershipData(); + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + IConfig groupsConfig = config.Configs["Groups"]; + + if (groupsConfig == null) + { + m_log.Info("[GROUPS]: No configuration found. Using defaults"); + } + else + { + if (!groupsConfig.GetBoolean("Enabled", false)) + { + m_log.Info("[GROUPS]: Groups disabled in configuration"); + return; + } + + if (groupsConfig.GetString("Module", "Default") != "Default") + return; + } + + lock (m_SceneList) + { + if (!m_SceneList.Contains(scene)) + { + if (m_SceneList.Count == 0) + { + osGroup.GroupID = opensimulatorGroupID; + osGroup.GroupName = "OpenSimulator Testing"; + osGroup.GroupPowers = + (uint)(GroupPowers.AllowLandmark | + GroupPowers.AllowSetHome); + m_GroupMap[opensimulatorGroupID] = osGroup; + } + m_SceneList.Add(scene); + } + } + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnClientClosed += OnClientClosed; + scene.EventManager.OnIncomingInstantMessage += + OnGridInstantMessage; + } + + public void PostInitialise() + { + } + + public void Close() + { +// m_log.Debug("[GROUPS]: Shutting down group module."); + + lock (m_ClientMap) + { + m_ClientMap.Clear(); + } + + lock (m_GroupMap) + { + m_GroupMap.Clear(); + } + } + + public string Name + { + get { return "GroupsModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + private void OnNewClient(IClientAPI client) + { + // Subscribe to instant messages + client.OnInstantMessage += OnInstantMessage; + client.OnAgentDataUpdateRequest += OnAgentDataUpdateRequest; + client.OnUUIDGroupNameRequest += HandleUUIDGroupNameRequest; + lock (m_ClientMap) + { + if (!m_ClientMap.ContainsKey(client.AgentId)) + { + m_ClientMap.Add(client.AgentId, client); + } + } + + GroupMembershipData[] updateGroups = new GroupMembershipData[1]; + updateGroups[0] = osGroup; + + client.SendGroupMembership(updateGroups); + } + + private void OnAgentDataUpdateRequest(IClientAPI remoteClient, + UUID AgentID, UUID SessionID) + { + UUID ActiveGroupID; + string ActiveGroupName; + ulong ActiveGroupPowers; + + string firstname = remoteClient.FirstName; + string lastname = remoteClient.LastName; + + string ActiveGroupTitle = "I IZ N0T"; + + ActiveGroupID = osGroup.GroupID; + ActiveGroupName = osGroup.GroupName; + ActiveGroupPowers = osGroup.GroupPowers; + + remoteClient.SendAgentDataUpdate(AgentID, ActiveGroupID, firstname, + lastname, ActiveGroupPowers, ActiveGroupName, + ActiveGroupTitle); + } + + private void OnInstantMessage(IClientAPI client, GridInstantMessage im) + { + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Trigger the above event handler + OnInstantMessage(null, msg); + } + + private void HandleUUIDGroupNameRequest(UUID id,IClientAPI remote_client) + { + string groupnamereply = "Unknown"; + UUID groupUUID = UUID.Zero; + + lock (m_GroupMap) + { + if (m_GroupMap.ContainsKey(id)) + { + GroupMembershipData grp = m_GroupMap[id]; + groupnamereply = grp.GroupName; + groupUUID = grp.GroupID; + } + } + remote_client.SendGroupNameReply(groupUUID, groupnamereply); + } + + private void OnClientClosed(UUID agentID) + { + lock (m_ClientMap) + { + if (m_ClientMap.ContainsKey(agentID)) + { +// IClientAPI cli = m_ClientMap[agentID]; +// if (cli != null) +// { +// //m_log.Info("[GROUPS]: Removing all reference to groups for " + cli.Name); +// } +// else +// { +// //m_log.Info("[GROUPS]: Removing all reference to groups for " + agentID.ToString()); +// } + m_ClientMap.Remove(agentID); + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs new file mode 100644 index 0000000..3ad2c91 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/InstantMessageModule.cs @@ -0,0 +1,170 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.InstantMessage +{ + public class InstantMessageModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Is this module enabled? + /// + private bool m_enabled = false; + + private readonly List m_scenes = new List(); + + #region IRegionModule Members + + private IMessageTransferModule m_TransferModule = null; + + public void Initialise(Scene scene, IConfigSource config) + { + if (config.Configs["Messaging"] != null) + { + if (config.Configs["Messaging"].GetString( + "InstantMessageModule", "InstantMessageModule") != + "InstantMessageModule") + return; + } + + m_enabled = true; + + lock (m_scenes) + { + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnClientConnect += OnClientConnect; + scene.EventManager.OnIncomingInstantMessage += OnGridInstantMessage; + } + } + } + + void OnClientConnect(IClientCore client) + { + IClientIM clientIM; + if (client.TryGet(out clientIM)) + { + clientIM.OnInstantMessage += OnInstantMessage; + } + } + + public void PostInitialise() + { + if (!m_enabled) + return; + + m_TransferModule = + m_scenes[0].RequestModuleInterface(); + + if (m_TransferModule == null) + m_log.Error("[INSTANT MESSAGE]: No message transfer module, "+ + "IM will not work!"); + } + + public void Close() + { + } + + public string Name + { + get { return "InstantMessageModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + public void OnInstantMessage(IClientAPI client, GridInstantMessage im) + { + byte dialog = im.dialog; + + if ( dialog != (byte)InstantMessageDialog.MessageFromAgent + && dialog != (byte)InstantMessageDialog.StartTyping + && dialog != (byte)InstantMessageDialog.StopTyping) + { + return; + } + + if (m_TransferModule != null) + { + m_TransferModule.SendInstantMessage(im, + delegate(bool success) + { + if (dialog == (uint)InstantMessageDialog.StartTyping || + dialog == (uint)InstantMessageDialog.StopTyping) + { + return; + } + + if ((client != null) && !success) + { + client.SendInstantMessage(new UUID(im.toAgentID), + "Unable to send instant message. "+ + "User is not logged in.", + new UUID(im.fromAgentID), "System", + (byte)InstantMessageDialog.BusyAutoResponse, + (uint)Util.UnixTimeSinceEpoch()); + } + } + ); + } + } + + /// + /// + /// + /// + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Just call the Text IM handler above + // This event won't be raised unless we have that agent, + // so we can depend on the above not trying to send + // via grid again + // + OnInstantMessage(null, msg); + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs new file mode 100644 index 0000000..91c22eb --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/MessageTransferModule.cs @@ -0,0 +1,655 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.InstantMessage +{ + public class MessageTransferModule : IRegionModule, IMessageTransferModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // private bool m_Enabled = false; + private bool m_Gridmode = false; + private List m_Scenes = new List(); + private Dictionary m_UserRegionMap = new Dictionary(); + + public void Initialise(Scene scene, IConfigSource config) + { + IConfig cnf = config.Configs["Messaging"]; + if (cnf != null && cnf.GetString( + "MessageTransferModule", "MessageTransferModule") != + "MessageTransferModule") + return; + + cnf = config.Configs["Startup"]; + if (cnf != null) + m_Gridmode = cnf.GetBoolean("gridmode", false); + + // m_Enabled = true; + + lock (m_Scenes) + { + if (m_Scenes.Count == 0) + { + scene.CommsManager.HttpServer.AddXmlRPCHandler( + "grid_instant_message", processXMLRPCGridInstantMessage); + } + + scene.RegisterModuleInterface(this); + m_Scenes.Add(scene); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "MessageTransferModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public void SendInstantMessage(GridInstantMessage im, MessageResultNotification result) + { + UUID toAgentID = new UUID(im.toAgentID); + + m_log.DebugFormat("[INSTANT MESSAGE]: Attempting delivery of IM from {0} to {1}", im.fromAgentName, toAgentID.ToString()); + + // Try root avatar only first + foreach (Scene scene in m_Scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { + m_log.DebugFormat("[INSTANT MESSAGE]: Looking for {0} in {1}", toAgentID.ToString(), scene.RegionInfo.RegionName); + // Local message + ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; + if (!user.IsChildAgent) + { + m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client"); + user.ControllingClient.SendInstantMessage( + new UUID(im.fromAgentID), + im.message, + new UUID(im.toAgentID), + im.fromAgentName, + im.dialog, + im.timestamp, + new UUID(im.imSessionID), + im.fromGroup, + im.binaryBucket); + // Message sent + result(true); + return; + } + } + } + + // try child avatar second + foreach (Scene scene in m_Scenes) + { +// m_log.DebugFormat( +// "[INSTANT MESSAGE]: Looking for child of {0} in {1}", toAgentID, scene.RegionInfo.RegionName); + + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { + // Local message + ScenePresence user = (ScenePresence) scene.Entities[toAgentID]; + + m_log.DebugFormat("[INSTANT MESSAGE]: Delivering to client"); + user.ControllingClient.SendInstantMessage( + new UUID(im.fromAgentID), + im.message, + new UUID(im.toAgentID), + im.fromAgentName, + im.dialog, + im.timestamp, + new UUID(im.imSessionID), + im.fromGroup, + im.binaryBucket); + // Message sent + result(true); + return; + } + } + + if (m_Gridmode) + { + //m_log.DebugFormat("[INSTANT MESSAGE]: Delivering via grid"); + // Still here, try send via Grid + SendGridInstantMessageViaXMLRPC(im, result); + return; + } + + //m_log.DebugFormat("[INSTANT MESSAGE]: Undeliverable"); + result(false); + return; + } + + /// + /// Process a XMLRPC Grid Instant Message + /// + /// XMLRPC parameters + /// + /// Nothing much + protected virtual XmlRpcResponse processXMLRPCGridInstantMessage(XmlRpcRequest request) + { + bool successful = false; + + // TODO: For now, as IMs seem to be a bit unreliable on OSGrid, catch all exception that + // happen here and aren't caught and log them. + try + { + // various rational defaults + UUID fromAgentID = UUID.Zero; + UUID toAgentID = UUID.Zero; + UUID imSessionID = UUID.Zero; + uint timestamp = 0; + string fromAgentName = ""; + string message = ""; + byte dialog = (byte)0; + bool fromGroup = false; + byte offline = (byte)0; + uint ParentEstateID=0; + Vector3 Position = Vector3.Zero; + UUID RegionID = UUID.Zero ; + byte[] binaryBucket = new byte[0]; + + float pos_x = 0; + float pos_y = 0; + float pos_z = 0; + //m_log.Info("Processing IM"); + + + Hashtable requestData = (Hashtable)request.Params[0]; + // Check if it's got all the data + if (requestData.ContainsKey("from_agent_id") + && requestData.ContainsKey("to_agent_id") && requestData.ContainsKey("im_session_id") + && requestData.ContainsKey("timestamp") && requestData.ContainsKey("from_agent_name") + && requestData.ContainsKey("message") && requestData.ContainsKey("dialog") + && requestData.ContainsKey("from_group") + && requestData.ContainsKey("offline") && requestData.ContainsKey("parent_estate_id") + && requestData.ContainsKey("position_x") && requestData.ContainsKey("position_y") + && requestData.ContainsKey("position_z") && requestData.ContainsKey("region_id") + && requestData.ContainsKey("binary_bucket")) + { + // Do the easy way of validating the UUIDs + UUID.TryParse((string)requestData["from_agent_id"], out fromAgentID); + UUID.TryParse((string)requestData["to_agent_id"], out toAgentID); + UUID.TryParse((string)requestData["im_session_id"], out imSessionID); + UUID.TryParse((string)requestData["region_id"], out RegionID); + + try + { + timestamp = (uint)Convert.ToInt32((string)requestData["timestamp"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + fromAgentName = (string)requestData["from_agent_name"]; + message = (string)requestData["message"]; + + // Bytes don't transfer well over XMLRPC, so, we Base64 Encode them. + string requestData1 = (string)requestData["dialog"]; + if (string.IsNullOrEmpty(requestData1)) + { + dialog = 0; + } + else + { + byte[] dialogdata = Convert.FromBase64String(requestData1); + dialog = dialogdata[0]; + } + + if ((string)requestData["from_group"] == "TRUE") + fromGroup = true; + + string requestData2 = (string)requestData["offline"]; + if (String.IsNullOrEmpty(requestData2)) + { + offline = 0; + } + else + { + byte[] offlinedata = Convert.FromBase64String(requestData2); + offline = offlinedata[0]; + } + + try + { + ParentEstateID = (uint)Convert.ToInt32((string)requestData["parent_estate_id"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + try + { + pos_x = (uint)Convert.ToInt32((string)requestData["position_x"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + try + { + pos_y = (uint)Convert.ToInt32((string)requestData["position_y"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + try + { + pos_z = (uint)Convert.ToInt32((string)requestData["position_z"]); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + + Position = new Vector3(pos_x, pos_y, pos_z); + + string requestData3 = (string)requestData["binary_bucket"]; + if (string.IsNullOrEmpty(requestData3)) + { + binaryBucket = new byte[0]; + } + else + { + binaryBucket = Convert.FromBase64String(requestData3); + } + + // Create a New GridInstantMessageObject the the data + GridInstantMessage gim = new GridInstantMessage(); + gim.fromAgentID = fromAgentID.Guid; + gim.fromAgentName = fromAgentName; + gim.fromGroup = fromGroup; + gim.imSessionID = imSessionID.Guid; + gim.RegionID = RegionID.Guid; + gim.timestamp = timestamp; + gim.toAgentID = toAgentID.Guid; + gim.message = message; + gim.dialog = dialog; + gim.offline = offline; + gim.ParentEstateID = ParentEstateID; + gim.Position = Position; + gim.binaryBucket = binaryBucket; + + + // Trigger the Instant message in the scene. + foreach (Scene scene in m_Scenes) + { + if (scene.Entities.ContainsKey(toAgentID) && + scene.Entities[toAgentID] is ScenePresence) + { + ScenePresence user = + (ScenePresence)scene.Entities[toAgentID]; + + if (!user.IsChildAgent) + { + scene.EventManager.TriggerIncomingInstantMessage(gim); + successful = true; + } + } + } + if (!successful) + { + // If the message can't be delivered to an agent, it + // is likely to be a group IM. On a group IM, the + // imSessionID = toAgentID = group id. Raise the + // unhandled IM event to give the groups module + // a chance to pick it up. We raise that in a random + // scene, since the groups module is shared. + // + m_Scenes[0].EventManager.TriggerUnhandledInstantMessage(gim); + } + } + } + catch (Exception e) + { + m_log.Error("[INSTANT MESSAGE]: Caught unexpected exception:", e); + successful = false; + } + + //Send response back to region calling if it was successful + // calling region uses this to know when to look up a user's location again. + XmlRpcResponse resp = new XmlRpcResponse(); + Hashtable respdata = new Hashtable(); + if (successful) + respdata["success"] = "TRUE"; + else + respdata["success"] = "FALSE"; + resp.Value = respdata; + + return resp; + } + + /// + /// delegate for sending a grid instant message asynchronously + /// + public delegate void GridInstantMessageDelegate(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle); + + private void GridInstantMessageCompleted(IAsyncResult iar) + { + GridInstantMessageDelegate icon = + (GridInstantMessageDelegate)iar.AsyncState; + icon.EndInvoke(iar); + } + + + protected virtual void SendGridInstantMessageViaXMLRPC(GridInstantMessage im, MessageResultNotification result) + { + GridInstantMessageDelegate d = SendGridInstantMessageViaXMLRPCAsync; + + d.BeginInvoke(im, result, 0, GridInstantMessageCompleted, d); + } + + /// + /// Recursive SendGridInstantMessage over XMLRPC method. + /// This is called from within a dedicated thread. + /// The first time this is called, prevRegionHandle will be 0 Subsequent times this is called from + /// itself, prevRegionHandle will be the last region handle that we tried to send. + /// If the handles are the same, we look up the user's location using the grid. + /// If the handles are still the same, we end. The send failed. + /// + /// + /// Pass in 0 the first time this method is called. It will be called recursively with the last + /// regionhandle tried + /// + protected virtual void SendGridInstantMessageViaXMLRPCAsync(GridInstantMessage im, MessageResultNotification result, ulong prevRegionHandle) + { + UUID toAgentID = new UUID(im.toAgentID); + + UserAgentData upd = null; + + bool lookupAgent = false; + + lock (m_UserRegionMap) + { + if (m_UserRegionMap.ContainsKey(toAgentID)) + { + upd = new UserAgentData(); + upd.AgentOnline = true; + upd.Handle = m_UserRegionMap[toAgentID]; + + // We need to compare the current regionhandle with the previous region handle + // or the recursive loop will never end because it will never try to lookup the agent again + if (prevRegionHandle == upd.Handle) + { + lookupAgent = true; + } + } + else + { + lookupAgent = true; + } + } + + + // Are we needing to look-up an agent? + if (lookupAgent) + { + // Non-cached user agent lookup. + upd = m_Scenes[0].CommsManager.UserService.GetAgentByUUID(toAgentID); + + if (upd != null) + { + // check if we've tried this before.. + // This is one way to end the recursive loop + // + if (upd.Handle == prevRegionHandle) + { + m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); + result(false); + return; + } + } + else + { + m_log.Error("[GRID INSTANT MESSAGE]: Unable to deliver an instant message"); + result(false); + return; + } + } + + if (upd != null) + { + if (upd.AgentOnline) + { + RegionInfo reginfo = m_Scenes[0].SceneGridService.RequestNeighbouringRegionInfo(upd.Handle); + if (reginfo != null) + { + Hashtable msgdata = ConvertGridInstantMessageToXMLRPC(im); + // Not actually used anymore, left in for compatibility + // Remove at next interface change + // + msgdata["region_handle"] = 0; + bool imresult = doIMSending(reginfo, msgdata); + if (imresult) + { + // IM delivery successful, so store the Agent's location in our local cache. + lock (m_UserRegionMap) + { + if (m_UserRegionMap.ContainsKey(toAgentID)) + { + m_UserRegionMap[toAgentID] = upd.Handle; + } + else + { + m_UserRegionMap.Add(toAgentID, upd.Handle); + } + } + result(true); + } + else + { + // try again, but lookup user this time. + // Warning, this must call the Async version + // of this method or we'll be making thousands of threads + // The version within the spawned thread is SendGridInstantMessageViaXMLRPCAsync + // The version that spawns the thread is SendGridInstantMessageViaXMLRPC + + // This is recursive!!!!! + SendGridInstantMessageViaXMLRPCAsync(im, result, + upd.Handle); + } + + } + else + { + m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find region {0}", upd.Handle); + result(false); + } + } + else + { + result(false); + } + } + else + { + m_log.WarnFormat("[GRID INSTANT MESSAGE]: Unable to find user {0}", toAgentID); + result(false); + } + + } + + /// + /// This actually does the XMLRPC Request + /// + /// RegionInfo we pull the data out of to send the request to + /// The Instant Message data Hashtable + /// Bool if the message was successfully delivered at the other side. + private bool doIMSending(RegionInfo reginfo, Hashtable xmlrpcdata) + { + + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + XmlRpcRequest GridReq = new XmlRpcRequest("grid_instant_message", SendParams); + try + { + + XmlRpcResponse GridResp = GridReq.Send("http://" + reginfo.ExternalHostName + ":" + reginfo.HttpPort, 3000); + + Hashtable responseData = (Hashtable)GridResp.Value; + + if (responseData.ContainsKey("success")) + { + if ((string)responseData["success"] == "TRUE") + { + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + catch (WebException e) + { + m_log.ErrorFormat("[GRID INSTANT MESSAGE]: Error sending message to http://{0}:{1} the host didn't respond ({2})", + reginfo.ExternalHostName, reginfo.HttpPort, e.Message); + } + + return false; + } + + /// + /// Get ulong region handle for region by it's Region UUID. + /// We use region handles over grid comms because there's all sorts of free and cool caching. + /// + /// UUID of region to get the region handle for + /// +// private ulong getLocalRegionHandleFromUUID(UUID regionID) +// { +// ulong returnhandle = 0; +// +// lock (m_Scenes) +// { +// foreach (Scene sn in m_Scenes) +// { +// if (sn.RegionInfo.RegionID == regionID) +// { +// returnhandle = sn.RegionInfo.RegionHandle; +// break; +// } +// } +// } +// return returnhandle; +// } + + /// + /// Takes a GridInstantMessage and converts it into a Hashtable for XMLRPC + /// + /// The GridInstantMessage object + /// Hashtable containing the XMLRPC request + private Hashtable ConvertGridInstantMessageToXMLRPC(GridInstantMessage msg) + { + Hashtable gim = new Hashtable(); + gim["from_agent_id"] = msg.fromAgentID.ToString(); + // Kept for compatibility + gim["from_agent_session"] = UUID.Zero.ToString(); + gim["to_agent_id"] = msg.toAgentID.ToString(); + gim["im_session_id"] = msg.imSessionID.ToString(); + gim["timestamp"] = msg.timestamp.ToString(); + gim["from_agent_name"] = msg.fromAgentName; + gim["message"] = msg.message; + byte[] dialogdata = new byte[1];dialogdata[0] = msg.dialog; + gim["dialog"] = Convert.ToBase64String(dialogdata,Base64FormattingOptions.None); + + if (msg.fromGroup) + gim["from_group"] = "TRUE"; + else + gim["from_group"] = "FALSE"; + byte[] offlinedata = new byte[1]; offlinedata[0] = msg.offline; + gim["offline"] = Convert.ToBase64String(offlinedata, Base64FormattingOptions.None); + gim["parent_estate_id"] = msg.ParentEstateID.ToString(); + gim["position_x"] = msg.Position.X.ToString(); + gim["position_y"] = msg.Position.Y.ToString(); + gim["position_z"] = msg.Position.Z.ToString(); + gim["region_id"] = msg.RegionID.ToString(); + gim["binary_bucket"] = Convert.ToBase64String(msg.binaryBucket,Base64FormattingOptions.None); + return gim; + } + + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs new file mode 100644 index 0000000..49fd70a --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/PresenceModule.cs @@ -0,0 +1,426 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.InstantMessage +{ + public class PresenceModule : IRegionModule, IPresenceModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool m_Enabled = false; + private bool m_Gridmode = false; + + // some default scene for doing things that aren't connected to a specific scene. Avoids locking. + private Scene m_initialScene; + + private List m_Scenes = new List(); + + // we currently are only interested in root-agents. If the root isn't here, we don't know the region the + // user is in, so we have to ask the messaging server anyway. + private Dictionary m_RootAgents = + new Dictionary(); + + public event PresenceChange OnPresenceChange; + public event BulkPresenceData OnBulkPresenceData; + + public void Initialise(Scene scene, IConfigSource config) + { + lock (m_Scenes) + { + // This is a shared module; Initialise will be called for every region on this server. + // Only check config once for the first region. + if (m_Scenes.Count == 0) + { + IConfig cnf = config.Configs["Messaging"]; + if (cnf != null && cnf.GetString( + "PresenceModule", "PresenceModule") != + "PresenceModule") + return; + + cnf = config.Configs["Startup"]; + if (cnf != null) + m_Gridmode = cnf.GetBoolean("gridmode", false); + + m_Enabled = true; + + m_initialScene = scene; + } + + if (m_Gridmode) + NotifyMessageServerOfStartup(scene); + + m_Scenes.Add(scene); + } + + scene.RegisterModuleInterface(this); + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnSetRootAgentScene += OnSetRootAgentScene; + scene.EventManager.OnMakeChildAgent += OnMakeChildAgent; + } + + public void PostInitialise() + { + } + + public void Close() + { + if (!m_Gridmode || !m_Enabled) + return; + + if (OnPresenceChange != null) + { + lock (m_RootAgents) + { + // on shutdown, users are kicked, too + foreach (KeyValuePair pair in m_RootAgents) + { + OnPresenceChange(new PresenceInfo(pair.Key, UUID.Zero)); + } + } + } + + lock (m_Scenes) + { + foreach (Scene scene in m_Scenes) + NotifyMessageServerOfShutdown(scene); + } + } + + public string Name + { + get { return "PresenceModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public void RequestBulkPresenceData(UUID[] users) + { + if (OnBulkPresenceData != null) + { + PresenceInfo[] result = new PresenceInfo[users.Length]; + if (m_Gridmode) + { + // first check the local information + List uuids = new List(); // the uuids to check remotely + List indices = new List(); // just for performance. + lock (m_RootAgents) + { + for (int i = 0; i < uuids.Count; ++i) + { + Scene scene; + if (m_RootAgents.TryGetValue(users[i], out scene)) + { + result[i] = new PresenceInfo(users[i], scene.RegionInfo.RegionID); + } + else + { + uuids.Add(users[i]); + indices.Add(i); + } + } + } + + // now we have filtered out all the local root agents. The rest we have to request info about + Dictionary infos = m_initialScene.GetFriendRegionInfos(uuids); + for (int i = 0; i < uuids.Count; ++i) + { + FriendRegionInfo info; + if (infos.TryGetValue(uuids[i], out info) && info.isOnline) + { + UUID regionID = info.regionID; + if (regionID == UUID.Zero) + { + // TODO this is the old messaging-server protocol; only the regionHandle is available. + // Fetch region-info to get the id + RegionInfo regionInfo = m_initialScene.RequestNeighbouringRegionInfo(info.regionHandle); + regionID = regionInfo.RegionID; + } + result[indices[i]] = new PresenceInfo(uuids[i], regionID); + } + else result[indices[i]] = new PresenceInfo(uuids[i], UUID.Zero); + } + } + else + { + // in standalone mode, we have all the info locally available. + lock (m_RootAgents) + { + for (int i = 0; i < users.Length; ++i) + { + Scene scene; + if (m_RootAgents.TryGetValue(users[i], out scene)) + { + result[i] = new PresenceInfo(users[i], scene.RegionInfo.RegionID); + } + else + { + result[i] = new PresenceInfo(users[i], UUID.Zero); + } + } + } + } + + // tell everyone + OnBulkPresenceData(result); + } + } + + // new client doesn't mean necessarily that user logged in, it just means it entered one of the + // the regions on this server + public void OnNewClient(IClientAPI client) + { + client.OnConnectionClosed += OnConnectionClosed; + client.OnLogout += OnLogout; + + // KLUDGE: See handler for details. + client.OnEconomyDataRequest += OnEconomyDataRequest; + } + + // connection closed just means *one* client connection has been closed. It doesn't mean that the + // user has logged off; it might have just TPed away. + public void OnConnectionClosed(IClientAPI client) + { + // TODO: Have to think what we have to do here... + // Should we just remove the root from the list (if scene matches)? + if (!(client.Scene is Scene)) + return; + Scene scene = (Scene)client.Scene; + + lock (m_RootAgents) + { + Scene rootScene; + if (!(m_RootAgents.TryGetValue(client.AgentId, out rootScene)) || scene != rootScene) + return; + + m_RootAgents.Remove(client.AgentId); + } + + // Should it have logged off, we'll do the logout part in OnLogout, even if no root is stored + // anymore. It logged off, after all... + } + + // Triggered when the user logs off. + public void OnLogout(IClientAPI client) + { + if (!(client.Scene is Scene)) + return; + Scene scene = (Scene)client.Scene; + + // On logout, we really remove the client from rootAgents, even if the scene doesn't match + lock (m_RootAgents) + { + if (m_RootAgents.ContainsKey(client.AgentId)) m_RootAgents.Remove(client.AgentId); + } + + // now inform the messaging server and anyone who is interested + NotifyMessageServerOfAgentLeaving(client.AgentId, scene.RegionInfo.RegionID, scene.RegionInfo.RegionHandle); + if (OnPresenceChange != null) OnPresenceChange(new PresenceInfo(client.AgentId, UUID.Zero)); + } + + public void OnSetRootAgentScene(UUID agentID, Scene scene) + { + // OnSetRootAgentScene can be called from several threads at once (with different agentID). + // Concurrent access to m_RootAgents is prone to failure on multi-core/-processor systems without + // correct locking). + lock (m_RootAgents) + { + Scene rootScene; + if (m_RootAgents.TryGetValue(agentID, out rootScene) && scene == rootScene) + { + return; + } + m_RootAgents[agentID] = scene; + } + // inform messaging server that agent changed the region + NotifyMessageServerOfAgentLocation(agentID, scene.RegionInfo.RegionID, scene.RegionInfo.RegionHandle); + } + + private void OnEconomyDataRequest(UUID agentID) + { + // KLUDGE: This is the only way I found to get a message (only) after login was completed and the + // client is connected enough to receive UDP packets. + // This packet seems to be sent only once, just after connection was established to the first + // region after login. + // We use it here to trigger a presence update; the old update-on-login was never be heard by + // the freshly logged in viewer, as it wasn't connected to the region at that time. + // TODO: Feel free to replace this by a better solution if you find one. + + // get the agent. This should work every time, as we just got a packet from it + ScenePresence agent = null; + lock (m_Scenes) + { + foreach (Scene scene in m_Scenes) + { + agent = scene.GetScenePresence(agentID); + if (agent != null) break; + } + } + + // just to be paranoid... + if (agent == null) + { + m_log.ErrorFormat("[PRESENCE]: Got a packet from agent {0} who can't be found anymore!?", agentID); + return; + } + + // we are a bit premature here, but the next packet will switch this child agent to root. + if (OnPresenceChange != null) OnPresenceChange(new PresenceInfo(agentID, agent.Scene.RegionInfo.RegionID)); + } + + public void OnMakeChildAgent(ScenePresence agent) + { + // OnMakeChildAgent can be called from several threads at once (with different agent). + // Concurrent access to m_RootAgents is prone to failure on multi-core/-processor systems without + // correct locking). + lock (m_RootAgents) + { + Scene rootScene; + if (m_RootAgents.TryGetValue(agent.UUID, out rootScene) && agent.Scene == rootScene) + { + m_RootAgents.Remove(agent.UUID); + } + } + // don't notify the messaging-server; either this agent just had been downgraded and another one will be upgraded + // to root momentarily (which will notify the messaging-server), or possibly it will be closed in a moment, + // which will update the messaging-server, too. + } + + private void NotifyMessageServerOfStartup(Scene scene) + { + Hashtable xmlrpcdata = new Hashtable(); + xmlrpcdata["RegionUUID"] = scene.RegionInfo.RegionID.ToString(); + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + try + { + XmlRpcRequest UpRequest = new XmlRpcRequest("region_startup", SendParams); + XmlRpcResponse resp = UpRequest.Send(scene.CommsManager.NetworkServersInfo.MessagingURL, 5000); + + Hashtable responseData = (Hashtable)resp.Value; + if (responseData == null || (!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE") + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region startup for region {0}", scene.RegionInfo.RegionName); + } + } + catch (System.Net.WebException) + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region startup for region {0}", scene.RegionInfo.RegionName); + } + } + + private void NotifyMessageServerOfShutdown(Scene scene) + { + Hashtable xmlrpcdata = new Hashtable(); + xmlrpcdata["RegionUUID"] = scene.RegionInfo.RegionID.ToString(); + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + try + { + XmlRpcRequest DownRequest = new XmlRpcRequest("region_shutdown", SendParams); + XmlRpcResponse resp = DownRequest.Send(scene.CommsManager.NetworkServersInfo.MessagingURL, 5000); + + Hashtable responseData = (Hashtable)resp.Value; + if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE") + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region shutdown for region {0}", scene.RegionInfo.RegionName); + } + } + catch (System.Net.WebException) + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of region shutdown for region {0}", scene.RegionInfo.RegionName); + } + } + + private void NotifyMessageServerOfAgentLocation(UUID agentID, UUID region, ulong regionHandle) + { + Hashtable xmlrpcdata = new Hashtable(); + xmlrpcdata["AgentID"] = agentID.ToString(); + xmlrpcdata["RegionUUID"] = region.ToString(); + xmlrpcdata["RegionHandle"] = regionHandle.ToString(); + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + try + { + XmlRpcRequest LocationRequest = new XmlRpcRequest("agent_location", SendParams); + XmlRpcResponse resp = LocationRequest.Send(m_Scenes[0].CommsManager.NetworkServersInfo.MessagingURL, 5000); + + Hashtable responseData = (Hashtable)resp.Value; + if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE") + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent location for {0}", agentID.ToString()); + } + } + catch (System.Net.WebException) + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent location for {0}", agentID.ToString()); + } + } + + private void NotifyMessageServerOfAgentLeaving(UUID agentID, UUID region, ulong regionHandle) + { + Hashtable xmlrpcdata = new Hashtable(); + xmlrpcdata["AgentID"] = agentID.ToString(); + xmlrpcdata["RegionUUID"] = region.ToString(); + xmlrpcdata["RegionHandle"] = regionHandle.ToString(); + ArrayList SendParams = new ArrayList(); + SendParams.Add(xmlrpcdata); + try + { + XmlRpcRequest LeavingRequest = new XmlRpcRequest("agent_leaving", SendParams); + XmlRpcResponse resp = LeavingRequest.Send(m_Scenes[0].CommsManager.NetworkServersInfo.MessagingURL, 5000); + + Hashtable responseData = (Hashtable)resp.Value; + if ((!responseData.ContainsKey("success")) || (string)responseData["success"] != "TRUE") + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent leaving for {0}", agentID.ToString()); + } + } + catch (System.Net.WebException) + { + m_log.ErrorFormat("[PRESENCE]: Failed to notify message server of agent leaving for {0}", agentID.ToString()); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs new file mode 100644 index 0000000..49006a2 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveReadRequest.cs @@ -0,0 +1,279 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Archiver; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using log4net; + + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver +{ + public class InventoryArchiveReadRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene scene; + protected TarArchiveReader archive; + private static System.Text.ASCIIEncoding m_asciiEncoding = new System.Text.ASCIIEncoding(); + + CommunicationsManager commsManager; + + public InventoryArchiveReadRequest(Scene currentScene, CommunicationsManager commsManager) + { + //List serialisedObjects = new List(); + scene = currentScene; + this.commsManager = commsManager; + } + + protected InventoryItemBase loadInvItem(string path, string contents) + { + InventoryItemBase item = new InventoryItemBase(); + StringReader sr = new StringReader(contents); + XmlTextReader reader = new XmlTextReader(sr); + + if (contents.Equals("")) return null; + + reader.ReadStartElement("InventoryObject"); + reader.ReadStartElement("Name"); + item.Name = reader.ReadString(); + reader.ReadEndElement(); + reader.ReadStartElement("ID"); + item.ID = UUID.Parse(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("InvType"); + item.InvType = System.Convert.ToInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("CreatorUUID"); + item.Creator = UUID.Parse(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("CreationDate"); + item.CreationDate = System.Convert.ToInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("Owner"); + item.Owner = UUID.Parse(reader.ReadString()); + reader.ReadEndElement(); + //No description would kill it + if (reader.IsEmptyElement) + { + reader.ReadStartElement("Description"); + } + else + { + reader.ReadStartElement("Description"); + item.Description = reader.ReadString(); + reader.ReadEndElement(); + } + reader.ReadStartElement("AssetType"); + item.AssetType = System.Convert.ToInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("AssetID"); + item.AssetID = UUID.Parse(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("SaleType"); + item.SaleType = System.Convert.ToByte(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("SalePrice"); + item.SalePrice = System.Convert.ToInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("BasePermissions"); + item.BasePermissions = System.Convert.ToUInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("CurrentPermissions"); + item.CurrentPermissions = System.Convert.ToUInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("EveryOnePermssions"); + item.EveryOnePermissions = System.Convert.ToUInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("NextPermissions"); + item.NextPermissions = System.Convert.ToUInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("Flags"); + item.Flags = System.Convert.ToUInt32(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("GroupID"); + item.GroupID = UUID.Parse(reader.ReadString()); + reader.ReadEndElement(); + reader.ReadStartElement("GroupOwned"); + item.GroupOwned = System.Convert.ToBoolean(reader.ReadString()); + reader.ReadEndElement(); + //reader.ReadStartElement("ParentFolderID"); + //item.Folder = UUID.Parse(reader.ReadString()); + //reader.ReadEndElement(); + //reader.ReadEndElement(); + + return item; + } + + public void execute(string firstName, string lastName, string invPath, string loadPath) + { + string filePath = "ERROR"; + int successfulAssetRestores = 0; + int failedAssetRestores = 0; + int successfulItemRestores = 0; + + UserProfileData userProfile = commsManager.UserService.GetUserProfile(firstName, lastName); + if (null == userProfile) + { + m_log.ErrorFormat("[CONSOLE]: Failed to find user {0} {1}", firstName, lastName); + return; + } + + CachedUserInfo userInfo = commsManager.UserProfileCacheService.GetUserDetails(userProfile.ID); + if (null == userInfo) + { + m_log.ErrorFormat( + "[CONSOLE]: Failed to find user info for {0} {1} {2}", + firstName, lastName, userProfile.ID); + + return; + } + + if (!userInfo.HasReceivedInventory) + { + m_log.ErrorFormat( + "[CONSOLE]: Have not yet received inventory info for user {0} {1} {2}", + firstName, lastName, userProfile.ID); + + return; + } + + InventoryFolderImpl inventoryFolder = userInfo.RootFolder.FindFolderByPath(invPath); + + if (null == inventoryFolder) + { + // TODO: Later on, automatically create this folder if it does not exist + m_log.ErrorFormat("[ARCHIVER]: Inventory path {0} does not exist", invPath); + + return; + } + + archive + = new TarArchiveReader(new GZipStream( + new FileStream(loadPath, FileMode.Open), CompressionMode.Decompress)); + + byte[] data; + TarArchiveReader.TarEntryType entryType; + while ((data = archive.ReadEntry(out filePath, out entryType)) != null) + { + if (entryType == TarArchiveReader.TarEntryType.TYPE_DIRECTORY) { + m_log.WarnFormat("[ARCHIVER]: Ignoring directory entry {0}", filePath); + } else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + if (LoadAsset(filePath, data)) + successfulAssetRestores++; + else + failedAssetRestores++; + } + else + { + InventoryItemBase item = loadInvItem(filePath, m_asciiEncoding.GetString(data)); + + if (item != null) + { + item.Creator = userProfile.ID; + item.Owner = userProfile.ID; + + // Reset folder ID to the one in which we want to load it + // TODO: Properly restore entire folder structure. At the moment all items are dumped in this + // single folder no matter where in the saved folder structure they are. + item.Folder = inventoryFolder.ID; + + userInfo.AddItem(item); + successfulItemRestores++; + } + } + } + + archive.Close(); + + m_log.DebugFormat("[ARCHIVER]: Restored {0} assets", successfulAssetRestores); + m_log.InfoFormat("[ARCHIVER]: Restored {0} items", successfulItemRestores); + } + + /// + /// Load an asset + /// + /// + /// + /// true if asset was successfully loaded, false otherwise + private bool LoadAsset(string assetPath, byte[] data) + { + //IRegionSerialiser serialiser = scene.RequestModuleInterface(); + // Right now we're nastily obtaining the UUID from the filename + string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); + int i = filename.LastIndexOf(ArchiveConstants.ASSET_EXTENSION_SEPARATOR); + + if (i == -1) + { + m_log.ErrorFormat( + "[ARCHIVER]: Could not find extension information in asset path {0} since it's missing the separator {1}. Skipping", + assetPath, ArchiveConstants.ASSET_EXTENSION_SEPARATOR); + + return false; + } + + string extension = filename.Substring(i); + string uuid = filename.Remove(filename.Length - extension.Length); + + if (ArchiveConstants.EXTENSION_TO_ASSET_TYPE.ContainsKey(extension)) + { + sbyte assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension]; + + m_log.DebugFormat("[ARCHIVER]: Importing asset {0}, type {1}", uuid, assetType); + + AssetBase asset = new AssetBase(new UUID(uuid), "RandomName"); + + asset.Metadata.Type = assetType; + asset.Data = data; + + scene.AssetCache.AddAsset(asset); + + + return true; + } + else + { + m_log.ErrorFormat( + "[ARCHIVER]: Tried to dearchive data with path {0} with an unknown type extension {1}", + assetPath, extension); + + return false; + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs new file mode 100644 index 0000000..f548296 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/InventoryArchiveWriteRequest.cs @@ -0,0 +1,247 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Archiver; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using log4net; + + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver +{ + public class InventoryArchiveWriteRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene scene; + protected TarArchiveWriter archive; + protected CommunicationsManager commsManager; + Dictionary assetUuids; + + /// + /// The path to which the inventory archive will be saved. + /// + private string m_savePath; + + public InventoryArchiveWriteRequest(Scene currentScene, CommunicationsManager commsManager) + { + scene = currentScene; + archive = new TarArchiveWriter(); + this.commsManager = commsManager; + assetUuids = new Dictionary(); + } + + protected void ReceivedAllAssets(IDictionary assetsFound, ICollection assetsNotFoundUuids) + { + AssetsArchiver assetsArchiver = new AssetsArchiver(assetsFound); + assetsArchiver.Archive(archive); + archive.WriteTar(new GZipStream(new FileStream(m_savePath, FileMode.Create), CompressionMode.Compress)); + } + + protected void saveInvItem(InventoryItemBase inventoryItem, string path) + { + string filename + = string.Format("{0}{1}_{2}.xml", + path, inventoryItem.Name, inventoryItem.ID); + StringWriter sw = new StringWriter(); + XmlTextWriter writer = new XmlTextWriter(sw); + writer.WriteStartElement("InventoryObject"); + writer.WriteStartElement("Name"); + writer.WriteString(inventoryItem.Name); + writer.WriteEndElement(); + writer.WriteStartElement("ID"); + writer.WriteString(inventoryItem.ID.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("InvType"); + writer.WriteString(inventoryItem.InvType.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("CreatorUUID"); + writer.WriteString(inventoryItem.Creator.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("CreationDate"); + writer.WriteString(inventoryItem.CreationDate.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("Owner"); + writer.WriteString(inventoryItem.Owner.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("Description"); + if (inventoryItem.Description.Length > 0) + writer.WriteString(inventoryItem.Description); + else writer.WriteString("No Description"); + writer.WriteEndElement(); + writer.WriteStartElement("AssetType"); + writer.WriteString(inventoryItem.AssetType.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("AssetID"); + writer.WriteString(inventoryItem.AssetID.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("SaleType"); + writer.WriteString(inventoryItem.SaleType.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("SalePrice"); + writer.WriteString(inventoryItem.SalePrice.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("BasePermissions"); + writer.WriteString(inventoryItem.BasePermissions.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("CurrentPermissions"); + writer.WriteString(inventoryItem.CurrentPermissions.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("EveryOnePermssions"); + writer.WriteString(inventoryItem.EveryOnePermissions.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("NextPermissions"); + writer.WriteString(inventoryItem.NextPermissions.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("Flags"); + writer.WriteString(inventoryItem.Flags.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("GroupID"); + writer.WriteString(inventoryItem.GroupID.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("GroupOwned"); + writer.WriteString(inventoryItem.GroupOwned.ToString()); + writer.WriteEndElement(); + writer.WriteStartElement("ParentFolderID"); + writer.WriteString(inventoryItem.Folder.ToString()); + writer.WriteEndElement(); + writer.WriteEndElement(); + + archive.AddFile(filename, sw.ToString()); + + assetUuids[inventoryItem.AssetID] = 1; + } + + protected void saveInvDir(InventoryFolderImpl inventoryFolder, string path) + { + List inventories = inventoryFolder.RequestListOfFolderImpls(); + List items = inventoryFolder.RequestListOfItems(); + string newPath = path + inventoryFolder.Name + InventoryFolderImpl.PATH_DELIMITER; + archive.AddDir(newPath); + foreach (InventoryFolderImpl folder in inventories) + { + saveInvDir(folder, newPath); + } + foreach (InventoryItemBase item in items) + { + saveInvItem(item, newPath); + } + } + + public void execute(string firstName, string lastName, string invPath, string savePath) + { + m_savePath = savePath; + + UserProfileData userProfile = commsManager.UserService.GetUserProfile(firstName, lastName); + if (null == userProfile) + { + m_log.ErrorFormat("[CONSOLE]: Failed to find user {0} {1}", firstName, lastName); + return; + } + + CachedUserInfo userInfo = commsManager.UserProfileCacheService.GetUserDetails(userProfile.ID); + if (null == userInfo) + { + m_log.ErrorFormat("[CONSOLE]: Failed to find user info for {0} {1} {2}", firstName, lastName, userProfile.ID); + return; + } + + InventoryFolderImpl inventoryFolder = null; + InventoryItemBase inventoryItem = null; + + if (userInfo.HasReceivedInventory) + { + // Eliminate double slashes and any leading / on the path. This might be better done within InventoryFolderImpl + // itself (possibly at a small loss in efficiency). + string[] components + = invPath.Split(new string[] { InventoryFolderImpl.PATH_DELIMITER }, StringSplitOptions.RemoveEmptyEntries); + invPath = String.Empty; + foreach (string c in components) + { + invPath += c + InventoryFolderImpl.PATH_DELIMITER; + } + + // Annoyingly Split actually returns the original string if the input string consists only of delimiters + // Therefore if we still start with a / after the split, then we need the root folder + if (invPath.Length == 0) + { + inventoryFolder = userInfo.RootFolder; + } + else + { + invPath = invPath.Remove(invPath.LastIndexOf(InventoryFolderImpl.PATH_DELIMITER)); + inventoryFolder = userInfo.RootFolder.FindFolderByPath(invPath); + } + + // The path may point to an item instead + if (inventoryFolder == null) + { + inventoryItem = userInfo.RootFolder.FindItemByPath(invPath); + } + } + else + { + m_log.ErrorFormat("[CONSOLE]: Have not yet received inventory info for user {0} {1} {2}", firstName, lastName, userProfile.ID); + return; + } + + if (null == inventoryFolder) + { + if (null == inventoryItem) + { + m_log.ErrorFormat("[CONSOLE]: Could not find inventory entry at path {0}", invPath); + return; + } + else + { + m_log.InfoFormat("[CONSOLE]: Found item {0} {1} at {2}", inventoryItem.Name, inventoryItem.ID, + invPath); + //get and export item info + saveInvItem(inventoryItem, invPath); + } + } + else + { + m_log.InfoFormat("[CONSOLE]: Found folder {0} {1} at {2}", inventoryFolder.Name, inventoryFolder.ID, + invPath); + //recurse through all dirs getting dirs and files + saveInvDir(inventoryFolder, ""); + } + + new AssetsRequest(assetUuids.Keys, scene.AssetCache, ReceivedAllAssets).Execute(); + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs new file mode 100644 index 0000000..d2cf6c9 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Inventory/Transfer/InventoryTransferModule.cs @@ -0,0 +1,389 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Communications.Cache; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer +{ + public class InventoryTransferModule : IInventoryTransferModule, IRegionModule + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + private List m_Scenelist = new List(); + private Dictionary m_AgentRegions = + new Dictionary(); + + private IMessageTransferModule m_TransferModule = null; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (config.Configs["Messaging"] != null) + { + // Allow disabling this module in config + // + if (config.Configs["Messaging"].GetString( + "InventoryTransferModule", "InventoryTransferModule") != + "InventoryTransferModule") + return; + } + + if (!m_Scenelist.Contains(scene)) + { + if (m_Scenelist.Count == 0) + { + m_TransferModule = scene.RequestModuleInterface(); + if (m_TransferModule == null) + m_log.Error("[INVENTORY TRANSFER] No Message transfer module found, transfers will be local only"); + } + + m_Scenelist.Add(scene); + + scene.RegisterModuleInterface(this); + + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnClientClosed += ClientLoggedOut; + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "InventoryModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + private void OnNewClient(IClientAPI client) + { + // Inventory giving is conducted via instant message + client.OnInstantMessage += OnInstantMessage; + } + + private Scene FindClientScene(UUID agentId) + { + lock (m_Scenelist) + { + foreach (Scene scene in m_Scenelist) + { + ScenePresence presence = scene.GetScenePresence(agentId); + if (presence != null) + { + if (!presence.IsChildAgent) + return scene; + } + } + } + return null; + } + + private void OnInstantMessage(IClientAPI client, GridInstantMessage im) + { + Scene scene = FindClientScene(client.AgentId); + + if (scene == null) // Something seriously wrong here. + return; + + if (im.dialog == (byte) InstantMessageDialog.InventoryOffered) + { + //m_log.DebugFormat("Asset type {0}", ((AssetType)im.binaryBucket[0])); + + ScenePresence user = scene.GetScenePresence(new UUID(im.toAgentID)); + UUID copyID; + + // First byte is the asset type + AssetType assetType = (AssetType)im.binaryBucket[0]; + + if (AssetType.Folder == assetType) + { + UUID folderID = new UUID(im.binaryBucket, 1); + + m_log.DebugFormat("[AGENT INVENTORY]: Inserting original folder {0} "+ + "into agent {1}'s inventory", + folderID, new UUID(im.toAgentID)); + + InventoryFolderImpl folderCopy + = scene.GiveInventoryFolder(new UUID(im.toAgentID), client.AgentId, folderID, UUID.Zero); + + if (folderCopy == null) + { + client.SendAgentAlertMessage("Can't find folder to give. Nothing given.", false); + return; + } + + // The outgoing binary bucket should contain only the byte which signals an asset folder is + // being copied and the following bytes for the copied folder's UUID + copyID = folderCopy.ID; + byte[] copyIDBytes = copyID.GetBytes(); + im.binaryBucket = new byte[1 + copyIDBytes.Length]; + im.binaryBucket[0] = (byte)AssetType.Folder; + Array.Copy(copyIDBytes, 0, im.binaryBucket, 1, copyIDBytes.Length); + + if (user != null && !user.IsChildAgent) + { + user.ControllingClient.SendBulkUpdateInventory(folderCopy); + } + } + else + { + // First byte of the array is probably the item type + // Next 16 bytes are the UUID + + UUID itemID = new UUID(im.binaryBucket, 1); + + m_log.DebugFormat("[AGENT INVENTORY]: Inserting item {0} "+ + "into agent {1}'s inventory", + itemID, new UUID(im.toAgentID)); + + InventoryItemBase itemCopy = scene.GiveInventoryItem( + new UUID(im.toAgentID), + client.AgentId, itemID); + + if (itemCopy == null) + { + client.SendAgentAlertMessage("Can't find item to give. Nothing given.", false); + return; + } + + copyID = itemCopy.ID; + Array.Copy(copyID.GetBytes(), 0, im.binaryBucket, 1, 16); + + if (user != null && !user.IsChildAgent) + { + user.ControllingClient.SendBulkUpdateInventory(itemCopy); + } + } + + // Send the IM to the recipient. The item is already + // in their inventory, so it will not be lost if + // they are offline. + // + if (user != null && !user.IsChildAgent) + { + // And notify. Transaction ID is the item ID. We get that + // same ID back on the reply so we know what to act on + // + user.ControllingClient.SendInstantMessage( + new UUID(im.fromAgentID), im.message, + new UUID(im.toAgentID), + im.fromAgentName, im.dialog, im.timestamp, + copyID, false, im.binaryBucket); + + return; + } + else + { + if (m_TransferModule != null) + m_TransferModule.SendInstantMessage(im, delegate(bool success) {} ); + } + } + else if (im.dialog == (byte) InstantMessageDialog.InventoryAccepted) + { + ScenePresence user = scene.GetScenePresence(new UUID(im.toAgentID)); + + if (user != null) // Local + { + user.ControllingClient.SendInstantMessage( + new UUID(im.fromAgentID), im.message, + new UUID(im.toAgentID), + im.fromAgentName, im.dialog, im.timestamp, + UUID.Zero, false, im.binaryBucket); + } + else + { + if (m_TransferModule != null) + m_TransferModule.SendInstantMessage(im, delegate(bool success) {} ); + } + } + else if (im.dialog == (byte) InstantMessageDialog.InventoryDeclined) + { + // Here, the recipient is local and we can assume that the + // inventory is loaded. Courtesy of the above bulk update, + // It will have been pushed to the client, too + // + + CachedUserInfo userInfo = + scene.CommsManager.UserProfileCacheService. + GetUserDetails(client.AgentId); + + if (userInfo != null) + { + InventoryFolderImpl trashFolder = + userInfo.FindFolderForType((int)AssetType.TrashFolder); + + UUID inventoryEntityID = new UUID(im.imSessionID); // The inventory item/folder, back from it's trip + + InventoryItemBase item = userInfo.RootFolder.FindItem(inventoryEntityID); + InventoryFolderBase folder = null; + + if (item != null && trashFolder != null) + { + item.Folder = trashFolder.ID; + + userInfo.DeleteItem(inventoryEntityID); + + scene.AddInventoryItem(client, item); + } + else + { + folder = userInfo.RootFolder.FindFolder(inventoryEntityID); + + if (folder != null & trashFolder != null) + { + userInfo.MoveFolder(inventoryEntityID, trashFolder.ID); + } + } + + if ((null == item && null == folder) | null == trashFolder) + { + string reason = String.Empty; + + if (trashFolder == null) + reason += " Trash folder not found."; + if (item == null) + reason += " Item not found."; + if (folder == null) + reason += " Folder not found."; + + client.SendAgentAlertMessage("Unable to delete "+ + "received inventory" + reason, false); + } + } + + ScenePresence user = scene.GetScenePresence(new UUID(im.toAgentID)); + + if (user != null) // Local + { + user.ControllingClient.SendInstantMessage( + new UUID(im.fromAgentID), im.message, + new UUID(im.toAgentID), + im.fromAgentName, im.dialog, im.timestamp, + UUID.Zero, false, im.binaryBucket); + } + else + { + if (m_TransferModule != null) + m_TransferModule.SendInstantMessage(im, delegate(bool success) {} ); + } + } + } + + public void SetRootAgentScene(UUID agentID, Scene scene) + { + m_AgentRegions[agentID] = scene; + } + + public bool NeedSceneCacheClear(UUID agentID, Scene scene) + { + if (!m_AgentRegions.ContainsKey(agentID)) + { + // Since we can get here two ways, we need to scan + // the scenes here. This is somewhat more expensive + // but helps avoid a nasty bug + // + + foreach (Scene s in m_Scenelist) + { + ScenePresence presence; + + if (s.TryGetAvatar(agentID, out presence)) + { + // If the agent is in this scene, then we + // are being called twice in a single + // teleport. This is wasteful of cycles + // but harmless due to this 2nd level check + // + // If the agent is found in another scene + // then the list wasn't current + // + // If the agent is totally unknown, then what + // are we even doing here?? + // + if (s == scene) + { + //m_log.Debug("[INVTRANSFERMOD]: s == scene. Returning true in " + scene.RegionInfo.RegionName); + return true; + } + else + { + //m_log.Debug("[INVTRANSFERMOD]: s != scene. Returning false in " + scene.RegionInfo.RegionName); + return false; + } + } + } + //m_log.Debug("[INVTRANSFERMOD]: agent not in scene. Returning true in " + scene.RegionInfo.RegionName); + return true; + } + + // The agent is left in current Scene, so we must be + // going to another instance + // + if (m_AgentRegions[agentID] == scene) + { + //m_log.Debug("[INVTRANSFERMOD]: m_AgentRegions[agentID] == scene. Returning true in " + scene.RegionInfo.RegionName); + m_AgentRegions.Remove(agentID); + return true; + } + + // Another region has claimed the agent + // + //m_log.Debug("[INVTRANSFERMOD]: last resort. Returning false in " + scene.RegionInfo.RegionName); + return false; + } + + public void ClientLoggedOut(UUID agentID) + { + if (m_AgentRegions.ContainsKey(agentID)) + m_AgentRegions.Remove(agentID); + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs b/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs new file mode 100644 index 0000000..ebf4f0a --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Lure/LureModule.cs @@ -0,0 +1,176 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Net; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Client; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Lure +{ + public class LureModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private readonly List m_scenes = new List(); + + private IMessageTransferModule m_TransferModule = null; + + public void Initialise(Scene scene, IConfigSource config) + { + if (config.Configs["Messaging"] != null) + { + if (config.Configs["Messaging"].GetString( + "LureModule", "LureModule") != + "LureModule") + return; + } + + lock (m_scenes) + { + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + scene.EventManager.OnNewClient += OnNewClient; + scene.EventManager.OnIncomingInstantMessage += + OnGridInstantMessage; + } + } + } + + void OnNewClient(IClientAPI client) + { + client.OnInstantMessage += OnInstantMessage; + client.OnStartLure += OnStartLure; + client.OnTeleportLureRequest += OnTeleportLureRequest; + } + + public void PostInitialise() + { + m_TransferModule = + m_scenes[0].RequestModuleInterface(); + + if (m_TransferModule == null) + m_log.Error("[INSTANT MESSAGE]: No message transfer module, "+ + "lures will not work!"); + } + + public void Close() + { + } + + public string Name + { + get { return "LureModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + public void OnInstantMessage(IClientAPI client, GridInstantMessage im) + { + } + + public void OnStartLure(byte lureType, string message, UUID targetid, IClientAPI client) + { + if (!(client.Scene is Scene)) + return; + + Scene scene = (Scene)(client.Scene); + ScenePresence presence = scene.GetScenePresence(client.AgentId); + + UUID dest = Util.BuildFakeParcelID( + scene.RegionInfo.RegionHandle, + (uint)presence.AbsolutePosition.X, + (uint)presence.AbsolutePosition.Y, + (uint)presence.AbsolutePosition.Z); + + m_log.DebugFormat("TP invite with message {0}", message); + + GridInstantMessage m = new GridInstantMessage(scene, client.AgentId, + client.FirstName+" "+client.LastName, targetid, + (byte)InstantMessageDialog.RequestTeleport, false, + message, dest, false, presence.AbsolutePosition, + new Byte[0]); + + if (m_TransferModule != null) + { + m_TransferModule.SendInstantMessage(m, + delegate(bool success) { }); + } + } + + public void OnTeleportLureRequest(UUID lureID, uint teleportFlags, IClientAPI client) + { + if (!(client.Scene is Scene)) + return; + + Scene scene = (Scene)(client.Scene); + + ulong handle = 0; + uint x = 128; + uint y = 128; + uint z = 70; + + Util.ParseFakeParcelID(lureID, out handle, out x, out y, out z); + + Vector3 position = new Vector3(); + position.X = (float)x; + position.Y = (float)y; + position.Z = (float)z; + + scene.RequestTeleportLocation(client, handle, position, + Vector3.Zero, teleportFlags); + } + + private void OnGridInstantMessage(GridInstantMessage msg) + { + // Forward remote teleport requests + // + if (msg.dialog != 22) + return; + + if (m_TransferModule != null) + { + m_TransferModule.SendInstantMessage(msg, + delegate(bool success) { }); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/ObjectCaps/ObjectAdd.cs b/OpenSim/Region/CoreModules/Avatar/ObjectCaps/ObjectAdd.cs new file mode 100644 index 0000000..193307d --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/ObjectCaps/ObjectAdd.cs @@ -0,0 +1,368 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +namespace OpenSim.Region.CoreModules.Avatar.ObjectCaps +{ + public class ObjectAdd : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; + #region IRegionModule Members + + public void Initialise(Scene pScene, IConfigSource pSource) + { + m_scene = pScene; + m_scene.EventManager.OnRegisterCaps += RegisterCaps; + } + + public void PostInitialise() + { + + } + + public void RegisterCaps(UUID agentID, Caps caps) + { + UUID capuuid = UUID.Random(); + + m_log.InfoFormat("[OBJECTADD]: {0}", "/CAPS/OA/" + capuuid + "/"); + + caps.RegisterHandler("ObjectAdd", + new RestHTTPHandler("POST", "/CAPS/OA/" + capuuid + "/", + delegate(Hashtable m_dhttpMethod) + { + return ProcessAdd(m_dhttpMethod, agentID, caps); + })); + } + + public Hashtable ProcessAdd(Hashtable request, UUID AgentId, Caps cap) + { + Hashtable responsedata = new Hashtable(); + responsedata["int_response_code"] = 400; //501; //410; //404; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = "Request wasn't what was expected"; + ScenePresence avatar; + + if (!m_scene.TryGetAvatar(AgentId, out avatar)) + return responsedata; + + + OSD r = OSDParser.DeserializeLLSDXml((string)request["requestbody"]); + //UUID session_id = UUID.Zero; + bool bypass_raycast = false; + uint everyone_mask = 0; + uint group_mask = 0; + uint next_owner_mask = 0; + uint flags = 0; + UUID group_id = UUID.Zero; + int hollow = 0; + int material = 0; + int p_code = 0; + int path_begin = 0; + int path_curve = 0; + int path_end = 0; + int path_radius_offset = 0; + int path_revolutions = 0; + int path_scale_x = 0; + int path_scale_y = 0; + int path_shear_x = 0; + int path_shear_y = 0; + int path_skew = 0; + int path_taper_x = 0; + int path_taper_y = 0; + int path_twist = 0; + int path_twist_begin = 0; + int profile_begin = 0; + int profile_curve = 0; + int profile_end = 0; + Vector3 ray_end = Vector3.Zero; + bool ray_end_is_intersection = false; + Vector3 ray_start = Vector3.Zero; + UUID ray_target_id = UUID.Zero; + Quaternion rotation = Quaternion.Identity; + Vector3 scale = Vector3.Zero; + int state = 0; + + if (r.Type != OSDType.Map) // not a proper req + return responsedata; + + OSDMap rm = (OSDMap)r; + + if (rm.ContainsKey("ObjectData")) //v2 + { + if (rm["ObjectData"].Type != OSDType.Map) + { + responsedata["str_response_string"] = "Has ObjectData key, but data not in expected format"; + return responsedata; + } + + OSDMap ObjMap = (OSDMap) rm["ObjectData"]; + + bypass_raycast = ObjMap["BypassRaycast"].AsBoolean(); + everyone_mask = readuintval(ObjMap["EveryoneMask"]); + flags = readuintval(ObjMap["Flags"]); + group_mask = readuintval(ObjMap["GroupMask"]); + material = ObjMap["Material"].AsInteger(); + next_owner_mask = readuintval(ObjMap["NextOwnerMask"]); + p_code = ObjMap["PCode"].AsInteger(); + + if (ObjMap.ContainsKey("Path")) + { + if (ObjMap["Path"].Type != OSDType.Map) + { + responsedata["str_response_string"] = "Has Path key, but data not in expected format"; + return responsedata; + } + + OSDMap PathMap = (OSDMap)ObjMap["Path"]; + path_begin = PathMap["Begin"].AsInteger(); + path_curve = PathMap["Curve"].AsInteger(); + path_end = PathMap["End"].AsInteger(); + path_radius_offset = PathMap["RadiusOffset"].AsInteger(); + path_revolutions = PathMap["Revolutions"].AsInteger(); + path_scale_x = PathMap["ScaleX"].AsInteger(); + path_scale_y = PathMap["ScaleY"].AsInteger(); + path_shear_x = PathMap["ShearX"].AsInteger(); + path_shear_y = PathMap["ShearY"].AsInteger(); + path_skew = PathMap["Skew"].AsInteger(); + path_taper_x = PathMap["TaperX"].AsInteger(); + path_taper_y = PathMap["TaperY"].AsInteger(); + path_twist = PathMap["Twist"].AsInteger(); + path_twist_begin = PathMap["TwistBegin"].AsInteger(); + + } + + if (ObjMap.ContainsKey("Profile")) + { + if (ObjMap["Profile"].Type != OSDType.Map) + { + responsedata["str_response_string"] = "Has Profile key, but data not in expected format"; + return responsedata; + } + + OSDMap ProfileMap = (OSDMap)ObjMap["Profile"]; + + profile_begin = ProfileMap["Begin"].AsInteger(); + profile_curve = ProfileMap["Curve"].AsInteger(); + profile_end = ProfileMap["End"].AsInteger(); + hollow = ProfileMap["Hollow"].AsInteger(); + } + ray_end_is_intersection = ObjMap["RayEndIsIntersection"].AsBoolean(); + + ray_target_id = ObjMap["RayTargetId"].AsUUID(); + state = ObjMap["State"].AsInteger(); + try + { + ray_end = ((OSDArray) ObjMap["RayEnd"]).AsVector3(); + ray_start = ((OSDArray) ObjMap["RayStart"]).AsVector3(); + scale = ((OSDArray) ObjMap["Scale"]).AsVector3(); + rotation = ((OSDArray)ObjMap["Rotation"]).AsQuaternion(); + } + catch (Exception) + { + responsedata["str_response_string"] = "RayEnd, RayStart, Scale or Rotation wasn't in the expected format"; + return responsedata; + } + + if (rm.ContainsKey("AgentData")) + { + if (rm["AgentData"].Type != OSDType.Map) + { + responsedata["str_response_string"] = "Has AgentData key, but data not in expected format"; + return responsedata; + } + + OSDMap AgentDataMap = (OSDMap) rm["AgentData"]; + + //session_id = AgentDataMap["SessionId"].AsUUID(); + group_id = AgentDataMap["GroupId"].AsUUID(); + } + + } + else + { //v1 + bypass_raycast = rm["bypass_raycast"].AsBoolean(); + + everyone_mask = readuintval(rm["everyone_mask"]); + flags = readuintval(rm["flags"]); + group_id = rm["group_id"].AsUUID(); + group_mask = readuintval(rm["group_mask"]); + hollow = rm["hollow"].AsInteger(); + material = rm["material"].AsInteger(); + next_owner_mask = readuintval(rm["next_owner_mask"]); + hollow = rm["hollow"].AsInteger(); + p_code = rm["p_code"].AsInteger(); + path_begin = rm["path_begin"].AsInteger(); + path_curve = rm["path_curve"].AsInteger(); + path_end = rm["path_end"].AsInteger(); + path_radius_offset = rm["path_radius_offset"].AsInteger(); + path_revolutions = rm["path_revolutions"].AsInteger(); + path_scale_x = rm["path_scale_x"].AsInteger(); + path_scale_y = rm["path_scale_y"].AsInteger(); + path_shear_x = rm["path_shear_x"].AsInteger(); + path_shear_y = rm["path_shear_y"].AsInteger(); + path_skew = rm["path_skew"].AsInteger(); + path_taper_x = rm["path_taper_x"].AsInteger(); + path_taper_y = rm["path_taper_y"].AsInteger(); + path_twist = rm["path_twist"].AsInteger(); + path_twist_begin = rm["path_twist_begin"].AsInteger(); + profile_begin = rm["profile_begin"].AsInteger(); + profile_curve = rm["profile_curve"].AsInteger(); + profile_end = rm["profile_end"].AsInteger(); + + ray_end_is_intersection = rm["ray_end_is_intersection"].AsBoolean(); + + ray_target_id = rm["ray_target_id"].AsUUID(); + + + //session_id = rm["session_id"].AsUUID(); + state = rm["state"].AsInteger(); + try + { + ray_end = ((OSDArray)rm["ray_end"]).AsVector3(); + ray_start = ((OSDArray)rm["ray_start"]).AsVector3(); + rotation = ((OSDArray)rm["rotation"]).AsQuaternion(); + scale = ((OSDArray)rm["scale"]).AsVector3(); + } + catch (Exception) + { + responsedata["str_response_string"] = "RayEnd, RayStart, Scale or Rotation wasn't in the expected format"; + return responsedata; + } + } + + + + Vector3 pos = m_scene.GetNewRezLocation(ray_start, ray_end, ray_target_id, rotation, (bypass_raycast) ? (byte)1 : (byte)0, (ray_end_is_intersection) ? (byte)1 : (byte)0, true, scale, false); + + PrimitiveBaseShape pbs = PrimitiveBaseShape.CreateBox(); + + pbs.PathBegin = (ushort)path_begin; + pbs.PathCurve = (byte)path_curve; + pbs.PathEnd = (ushort)path_end; + pbs.PathRadiusOffset = (sbyte)path_radius_offset; + pbs.PathRevolutions = (byte)path_revolutions; + pbs.PathScaleX = (byte)path_scale_x; + pbs.PathScaleY = (byte)path_scale_y; + pbs.PathShearX = (byte) path_shear_x; + pbs.PathShearY = (byte)path_shear_y; + pbs.PathSkew = (sbyte)path_skew; + pbs.PathTaperX = (sbyte)path_taper_x; + pbs.PathTaperY = (sbyte)path_taper_y; + pbs.PathTwist = (sbyte)path_twist; + pbs.PathTwistBegin = (sbyte)path_twist_begin; + pbs.HollowShape = (HollowShape) hollow; + pbs.PCode = (byte)p_code; + pbs.ProfileBegin = (ushort) profile_begin; + pbs.ProfileCurve = (byte) profile_curve; + pbs.ProfileEnd = (ushort)profile_end; + pbs.Scale = scale; + pbs.State = (byte)state; + + SceneObjectGroup obj = null; ; + + if (m_scene.Permissions.CanRezObject(1, avatar.UUID, pos)) + { + // rez ON the ground, not IN the ground + pos.Z += 0.25F; + + obj = m_scene.AddNewPrim(avatar.UUID, group_id, pos, rotation, pbs); + } + + + if (obj == null) + return responsedata; + + SceneObjectPart rootpart = obj.RootPart; + rootpart.Shape = pbs; + rootpart.Flags |= (PrimFlags)flags; + rootpart.EveryoneMask = everyone_mask; + rootpart.GroupID = group_id; + rootpart.GroupMask = group_mask; + rootpart.NextOwnerMask = next_owner_mask; + rootpart.Material = (byte)material; + + + + m_scene.PhysicsScene.AddPhysicsActorTaint(rootpart.PhysActor); + + responsedata["int_response_code"] = 200; //501; //410; //404; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = String.Format("local_id{0}",ConvertUintToBytes(obj.LocalId)); + + return responsedata; + } + + private uint readuintval(OSD obj) + { + byte[] tmp = obj.AsBinary(); + if (BitConverter.IsLittleEndian) + Array.Reverse(tmp); + return Utils.BytesToUInt(tmp); + + } + private string ConvertUintToBytes(uint val) + { + byte[] resultbytes = Utils.UIntToBytes(val); + if (BitConverter.IsLittleEndian) + Array.Reverse(resultbytes); + return String.Format("{0}",Convert.ToBase64String(resultbytes)); + } + + public void Close() + { + + } + + public string Name + { + get { return "ObjectAddModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Avatar/Profiles/AvatarProfilesModule.cs b/OpenSim/Region/CoreModules/Avatar/Profiles/AvatarProfilesModule.cs new file mode 100644 index 0000000..6ea5b56 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Profiles/AvatarProfilesModule.cs @@ -0,0 +1,145 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Globalization; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Profiles +{ + public class AvatarProfilesModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene; + + public AvatarProfilesModule() + { + } + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_scene.EventManager.OnNewClient += NewClient; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "AvatarProfilesModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + public void NewClient(IClientAPI client) + { + client.OnRequestAvatarProperties += RequestAvatarProperty; + client.OnUpdateAvatarProperties += UpdateAvatarProperties; + } + + public void RemoveClient(IClientAPI client) + { + client.OnRequestAvatarProperties -= RequestAvatarProperty; + client.OnUpdateAvatarProperties -= UpdateAvatarProperties; + } + + /// + /// + /// + /// + /// + public void RequestAvatarProperty(IClientAPI remoteClient, UUID avatarID) + { + // FIXME: finish adding fields such as url, masking, etc. + UserProfileData profile = m_scene.CommsManager.UserService.GetUserProfile(avatarID); + if (null != profile) + { + Byte[] charterMember; + if (profile.CustomType == "") + { + charterMember = new Byte[1]; + charterMember[0] = (Byte)((profile.UserFlags & 0xf00) >> 8); + } + else + { + charterMember = Utils.StringToBytes(profile.CustomType); + } + + remoteClient.SendAvatarProperties(profile.ID, profile.AboutText, + Util.ToDateTime(profile.Created).ToString("M/d/yyyy", CultureInfo.InvariantCulture), + charterMember, profile.FirstLifeAboutText, (uint)(profile.UserFlags & 0xff), + profile.FirstLifeImage, profile.Image, String.Empty, profile.Partner); + } + else + { + m_log.Debug("[AvatarProfilesModule]: Got null for profile for " + avatarID.ToString()); + } + } + + public void UpdateAvatarProperties(IClientAPI remoteClient, UserProfileData newProfile) + { + UserProfileData Profile = m_scene.CommsManager.UserService.GetUserProfile(newProfile.ID); + + // if it's the profile of the user requesting the update, then we change only a few things. + if (remoteClient.AgentId.CompareTo(Profile.ID) == 0) + { + Profile.Image = newProfile.Image; + Profile.FirstLifeImage = newProfile.FirstLifeImage; + Profile.AboutText = newProfile.AboutText; + Profile.FirstLifeAboutText = newProfile.FirstLifeAboutText; + } + else + { + return; + } + + if (m_scene.CommsManager.UserService.UpdateUserProfile(Profile)) + { + RequestAvatarProperty(remoteClient, newProfile.ID); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Communications/Local/LocalInterregionComms.cs b/OpenSim/Region/CoreModules/Communications/Local/LocalInterregionComms.cs new file mode 100644 index 0000000..3a9df4a --- /dev/null +++ b/OpenSim/Region/CoreModules/Communications/Local/LocalInterregionComms.cs @@ -0,0 +1,244 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; +using System.Xml; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Communications.Local +{ + public class LocalInterregionComms : IRegionModule, IInterregionCommsOut, IInterregionCommsIn + { + private bool m_enabled = false; + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private List m_sceneList = new List(); + + #region Events + public event ChildAgentUpdateReceived OnChildAgentUpdate; + + #endregion /* Events */ + + #region IRegionModule + + public void Initialise(Scene scene, IConfigSource config) + { + if (m_sceneList.Count == 0) + { + IConfig startupConfig = config.Configs["Communications"]; + + if ((startupConfig != null) && (startupConfig.GetString("InterregionComms", "RESTCommms") == "LocalComms")) + { + m_log.Debug("[LOCAL COMMS]: Enabling InterregionComms LocalComms module"); + m_enabled = true; + } + } + + if (!m_enabled) + return; + + Init(scene); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "LocalInterregionCommsModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + /// + /// Can be called from other modules. + /// + /// + public void Init(Scene scene) + { + if (!m_sceneList.Contains(scene)) + { + lock (m_sceneList) + { + m_sceneList.Add(scene); + if (m_enabled) + scene.RegisterModuleInterface(this); + scene.RegisterModuleInterface(this); + } + + } + } + + #endregion /* IRegionModule */ + + #region IInterregionComms + + /** + * Agent-related communications + */ + + public bool SendCreateChildAgent(ulong regionHandle, AgentCircuitData aCircuit) + { + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { +// m_log.DebugFormat("[LOCAL COMMS]: Found region {0} to send SendCreateChildAgent", regionHandle); + s.NewUserConnection(aCircuit); + return true; + } + } + +// m_log.DebugFormat("[LOCAL COMMS]: Did not find region {0} for SendCreateChildAgent", regionHandle); + return false; + } + + public bool SendChildAgentUpdate(ulong regionHandle, AgentData cAgentData) + { + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { + //m_log.DebugFormat( + // "[LOCAL COMMS]: Found region {0} {1} to send ChildAgentUpdate", + // s.RegionInfo.RegionName, regionHandle); + + s.IncomingChildAgentDataUpdate(cAgentData); + return true; + } + } + +// m_log.DebugFormat("[LOCAL COMMS]: Did not find region {0} for ChildAgentUpdate", regionHandle); + return false; + } + + public bool SendChildAgentUpdate(ulong regionHandle, AgentPosition cAgentData) + { + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { + //m_log.Debug("[LOCAL COMMS]: Found region to send ChildAgentUpdate"); + s.IncomingChildAgentDataUpdate(cAgentData); + return true; + } + } + //m_log.Debug("[LOCAL COMMS]: region not found for ChildAgentUpdate"); + return false; + } + + public bool SendReleaseAgent(ulong regionHandle, UUID id, string uri) + { + //uint x, y; + //Utils.LongToUInts(regionHandle, out x, out y); + //x = x / Constants.RegionSize; + //y = y / Constants.RegionSize; + //Console.WriteLine("\n >>> Local SendReleaseAgent " + x + "-" + y); + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { + //m_log.Debug("[LOCAL COMMS]: Found region to SendReleaseAgent"); + return s.IncomingReleaseAgent(id); + } + } + //m_log.Debug("[LOCAL COMMS]: region not found in SendReleaseAgent"); + return false; + } + + public bool SendCloseAgent(ulong regionHandle, UUID id) + { + //uint x, y; + //Utils.LongToUInts(regionHandle, out x, out y); + //x = x / Constants.RegionSize; + //y = y / Constants.RegionSize; + //Console.WriteLine("\n >>> Local SendCloseAgent " + x + "-" + y); + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { + //m_log.Debug("[LOCAL COMMS]: Found region to SendCloseAgent"); + return s.IncomingCloseAgent(id); + } + } + //m_log.Debug("[LOCAL COMMS]: region not found in SendCloseAgent"); + return false; + } + + /** + * Object-related communications + */ + + public bool SendCreateObject(ulong regionHandle, ISceneObject sog) + { + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionHandle) + { + //m_log.Debug("[LOCAL COMMS]: Found region to SendCreateObject"); + return s.IncomingCreateObject(sog); + } + } + return false; + } + + #endregion /* IInterregionComms */ + + #region Misc + + public UUID GetRegionID(ulong regionhandle) + { + foreach (Scene s in m_sceneList) + { + if (s.RegionInfo.RegionHandle == regionhandle) + return s.RegionInfo.RegionID; + } + // ? weird. should not happen + return m_sceneList[0].RegionInfo.RegionID; + } + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Communications/REST/RESTInterregionComms.cs b/OpenSim/Region/CoreModules/Communications/REST/RESTInterregionComms.cs new file mode 100644 index 0000000..b4f4814 --- /dev/null +++ b/OpenSim/Region/CoreModules/Communications/REST/RESTInterregionComms.cs @@ -0,0 +1,960 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; +using System.Xml; +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Hypergrid; +using OpenSim.Region.CoreModules.Communications.Local; + +namespace OpenSim.Region.CoreModules.Communications.REST +{ + public class RESTInterregionComms : IRegionModule, IInterregionCommsOut + { + private static bool initialized = false; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected bool m_enabled = false; + protected Scene m_aScene; + // RESTInterregionComms does not care about local regions; it delegates that to the Local module + protected LocalInterregionComms m_localBackend; + + protected CommunicationsManager m_commsManager; + + #region IRegionModule + + public virtual void Initialise(Scene scene, IConfigSource config) + { + if (!initialized) + { + initialized = true; + IConfig startupConfig = config.Configs["Communications"]; + + if ((startupConfig == null) + || (startupConfig != null) + && (startupConfig.GetString("InterregionComms", "RESTComms") == "RESTComms")) + { + m_log.Info("[REST COMMS]: Enabling InterregionComms RESTComms module"); + m_enabled = true; + InitOnce(scene); + } + } + + if (!m_enabled) + return; + + InitEach(scene); + + } + + public virtual void PostInitialise() + { + if (m_enabled) + AddHTTPHandlers(); + } + + public virtual void Close() + { + } + + public virtual string Name + { + get { return "RESTInterregionCommsModule"; } + } + + public virtual bool IsSharedModule + { + get { return true; } + } + + protected virtual void InitEach(Scene scene) + { + m_localBackend.Init(scene); + scene.RegisterModuleInterface(this); + } + + protected virtual void InitOnce(Scene scene) + { + m_localBackend = new LocalInterregionComms(); + m_commsManager = scene.CommsManager; + m_aScene = scene; + } + + protected virtual void AddHTTPHandlers() + { + m_aScene.CommsManager.HttpServer.AddHTTPHandler("/agent/", AgentHandler); + m_aScene.CommsManager.HttpServer.AddHTTPHandler("/object/", ObjectHandler); + } + + #endregion /* IRegionModule */ + + #region IInterregionComms + + /** + * Agent-related communications + */ + + public bool SendCreateChildAgent(ulong regionHandle, AgentCircuitData aCircuit) + { + // Try local first + if (m_localBackend.SendCreateChildAgent(regionHandle, aCircuit)) + return true; + + // else do the remote thing + RegionInfo regInfo = m_commsManager.GridService.RequestNeighbourInfo(regionHandle); + if (regInfo != null) + { + SendUserInformation(regInfo, aCircuit); + + return DoCreateChildAgentCall(regInfo, aCircuit); + } + //else + // m_log.Warn("[REST COMMS]: Region not found " + regionHandle); + return false; + } + + public bool SendChildAgentUpdate(ulong regionHandle, AgentData cAgentData) + { + // Try local first + if (m_localBackend.SendChildAgentUpdate(regionHandle, cAgentData)) + return true; + + // else do the remote thing + RegionInfo regInfo = m_commsManager.GridService.RequestNeighbourInfo(regionHandle); + if (regInfo != null) + { + return DoChildAgentUpdateCall(regInfo, cAgentData); + } + //else + // m_log.Warn("[REST COMMS]: Region not found " + regionHandle); + return false; + + } + + public bool SendChildAgentUpdate(ulong regionHandle, AgentPosition cAgentData) + { + // Try local first + if (m_localBackend.SendChildAgentUpdate(regionHandle, cAgentData)) + return true; + + // else do the remote thing + RegionInfo regInfo = m_commsManager.GridService.RequestNeighbourInfo(regionHandle); + if (regInfo != null) + { + return DoChildAgentUpdateCall(regInfo, cAgentData); + } + //else + // m_log.Warn("[REST COMMS]: Region not found " + regionHandle); + return false; + + } + + public bool SendReleaseAgent(ulong regionHandle, UUID id, string uri) + { + // Try local first + if (m_localBackend.SendReleaseAgent(regionHandle, id, uri)) + return true; + + // else do the remote thing + return DoReleaseAgentCall(regionHandle, id, uri); + } + + public bool SendCloseAgent(ulong regionHandle, UUID id) + { + // Try local first + if (m_localBackend.SendCloseAgent(regionHandle, id)) + return true; + + // else do the remote thing + RegionInfo regInfo = m_commsManager.GridService.RequestNeighbourInfo(regionHandle); + if (regInfo != null) + { + return DoCloseAgentCall(regInfo, id); + } + //else + // m_log.Warn("[REST COMMS]: Region not found " + regionHandle); + return false; + } + + /** + * Object-related communications + */ + + public bool SendCreateObject(ulong regionHandle, ISceneObject sog) + { + // Try local first + ISceneObject sogClone = sog.CloneForNewScene(); + if (m_localBackend.SendCreateObject(regionHandle, sogClone)) + { + //m_log.Debug("[REST COMMS]: LocalBackEnd SendCreateObject succeeded"); + return true; + } + + // else do the remote thing + RegionInfo regInfo = m_commsManager.GridService.RequestNeighbourInfo(regionHandle); + if (regInfo != null) + { + return DoCreateObjectCall(regInfo, sog); + } + //else + // m_log.Warn("[REST COMMS]: Region not found " + regionHandle); + return false; + } + + #endregion /* IInterregionComms */ + + #region DoWork functions for the above public interface + + //------------------------------------------------------------------- + // Internal functions for the above public interface + //------------------------------------------------------------------- + + protected bool DoCreateChildAgentCall(RegionInfo region, AgentCircuitData aCircuit) + { + // Eventually, we want to use a caps url instead of the agentID + string uri = "http://" + region.ExternalEndPoint.Address + ":" + region.HttpPort + "/agent/" + aCircuit.AgentID + "/"; + //Console.WriteLine(" >>> DoCreateChildAgentCall <<< " + uri); + + WebRequest AgentCreateRequest = WebRequest.Create(uri); + AgentCreateRequest.Method = "POST"; + AgentCreateRequest.ContentType = "application/json"; + AgentCreateRequest.Timeout = 10000; + + // Fill it in + OSDMap args = null; + try + { + args = aCircuit.PackAgentCircuitData(); + } + catch (Exception e) + { + m_log.Debug("[REST COMMS]: PackAgentCircuitData failed with exception: " + e.Message); + } + // Add the regionhandle of the destination region + ulong regionHandle = GetRegionHandle(region.RegionHandle); + args["destination_handle"] = OSD.FromString(regionHandle.ToString()); + + string strBuffer = ""; + byte[] buffer = new byte[1]; + try + { + strBuffer = OSDParser.SerializeJsonString(args); + System.Text.UTF8Encoding str = new System.Text.UTF8Encoding(); + buffer = str.GetBytes(strBuffer); + + } + catch (Exception e) + { + m_log.WarnFormat("[OSG2]: Exception thrown on serialization of ChildCreate: {0}", e.Message); + // ignore. buffer will be empty, caller should check. + } + + Stream os = null; + try + { // send the Post + AgentCreateRequest.ContentLength = buffer.Length; //Count bytes to send + os = AgentCreateRequest.GetRequestStream(); + os.Write(buffer, 0, strBuffer.Length); //Send it + os.Close(); + //m_log.InfoFormat("[REST COMMS]: Posted ChildAgentUpdate request to remote sim {0}", uri); + } + //catch (WebException ex) + catch + { + //m_log.InfoFormat("[REST COMMS]: Bad send on ChildAgentUpdate {0}", ex.Message); + + return false; + } + + // Let's wait for the response + //m_log.Info("[REST COMMS]: Waiting for a reply after DoCreateChildAgentCall"); + + try + { + WebResponse webResponse = AgentCreateRequest.GetResponse(); + if (webResponse == null) + { + m_log.Info("[REST COMMS]: Null reply on DoCreateChildAgentCall post"); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + //reply = sr.ReadToEnd().Trim(); + sr.ReadToEnd().Trim(); + sr.Close(); + //m_log.InfoFormat("[REST COMMS]: DoCreateChildAgentCall reply was {0} ", reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[REST COMMS]: exception on reply of DoCreateChildAgentCall {0}", ex.Message); + // ignore, really + } + + return true; + + } + + protected bool DoChildAgentUpdateCall(RegionInfo region, IAgentData cAgentData) + { + // Eventually, we want to use a caps url instead of the agentID + string uri = "http://" + region.ExternalEndPoint.Address + ":" + region.HttpPort + "/agent/" + cAgentData.AgentID + "/"; + //Console.WriteLine(" >>> DoChildAgentUpdateCall <<< " + uri); + + WebRequest ChildUpdateRequest = WebRequest.Create(uri); + ChildUpdateRequest.Method = "PUT"; + ChildUpdateRequest.ContentType = "application/json"; + ChildUpdateRequest.Timeout = 10000; + + // Fill it in + OSDMap args = null; + try + { + args = cAgentData.PackUpdateMessage(); + } + catch (Exception e) + { + m_log.Debug("[REST COMMS]: PackUpdateMessage failed with exception: " + e.Message); + } + // Add the regionhandle of the destination region + ulong regionHandle = GetRegionHandle(region.RegionHandle); + args["destination_handle"] = OSD.FromString(regionHandle.ToString()); + + string strBuffer = ""; + byte[] buffer = new byte[1]; + try + { + strBuffer = OSDParser.SerializeJsonString(args); + System.Text.UTF8Encoding str = new System.Text.UTF8Encoding(); + buffer = str.GetBytes(strBuffer); + + } + catch (Exception e) + { + m_log.WarnFormat("[REST COMMS]: Exception thrown on serialization of ChildUpdate: {0}", e.Message); + // ignore. buffer will be empty, caller should check. + } + + Stream os = null; + try + { // send the Post + ChildUpdateRequest.ContentLength = buffer.Length; //Count bytes to send + os = ChildUpdateRequest.GetRequestStream(); + os.Write(buffer, 0, strBuffer.Length); //Send it + os.Close(); + //m_log.InfoFormat("[REST COMMS]: Posted ChildAgentUpdate request to remote sim {0}", uri); + } + //catch (WebException ex) + catch + { + //m_log.InfoFormat("[REST COMMS]: Bad send on ChildAgentUpdate {0}", ex.Message); + + return false; + } + + // Let's wait for the response + //m_log.Info("[REST COMMS]: Waiting for a reply after ChildAgentUpdate"); + + try + { + WebResponse webResponse = ChildUpdateRequest.GetResponse(); + if (webResponse == null) + { + m_log.Info("[REST COMMS]: Null reply on ChilAgentUpdate post"); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + //reply = sr.ReadToEnd().Trim(); + sr.ReadToEnd().Trim(); + sr.Close(); + //m_log.InfoFormat("[REST COMMS]: ChilAgentUpdate reply was {0} ", reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[REST COMMS]: exception on reply of ChilAgentUpdate {0}", ex.Message); + // ignore, really + } + + return true; + } + + protected bool DoReleaseAgentCall(ulong regionHandle, UUID id, string uri) + { + //Console.WriteLine(" >>> DoReleaseAgentCall <<< " + uri); + + WebRequest request = WebRequest.Create(uri); + request.Method = "DELETE"; + request.Timeout = 10000; + + try + { + WebResponse webResponse = request.GetResponse(); + if (webResponse == null) + { + m_log.Info("[REST COMMS]: Null reply on agent get "); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + //reply = sr.ReadToEnd().Trim(); + sr.ReadToEnd().Trim(); + sr.Close(); + //m_log.InfoFormat("[REST COMMS]: ChilAgentUpdate reply was {0} ", reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[REST COMMS]: exception on reply of agent get {0}", ex.Message); + // ignore, really + } + + return true; + } + + protected bool DoCloseAgentCall(RegionInfo region, UUID id) + { + string uri = "http://" + region.ExternalEndPoint.Address + ":" + region.HttpPort + "/agent/" + id + "/" + region.RegionHandle.ToString() +"/"; + + //Console.WriteLine(" >>> DoCloseAgentCall <<< " + uri); + + WebRequest request = WebRequest.Create(uri); + request.Method = "DELETE"; + request.Timeout = 10000; + + try + { + WebResponse webResponse = request.GetResponse(); + if (webResponse == null) + { + m_log.Info("[REST COMMS]: Null reply on agent get "); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + //reply = sr.ReadToEnd().Trim(); + sr.ReadToEnd().Trim(); + sr.Close(); + //m_log.InfoFormat("[REST COMMS]: ChilAgentUpdate reply was {0} ", reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[REST COMMS]: exception on reply of agent get {0}", ex.Message); + // ignore, really + } + + return true; + } + + protected bool DoCreateObjectCall(RegionInfo region, ISceneObject sog) + { + ulong regionHandle = GetRegionHandle(region.RegionHandle); + string uri = "http://" + region.ExternalEndPoint.Address + ":" + region.HttpPort + "/object/" + sog.UUID + "/" + regionHandle.ToString() + "/"; + //Console.WriteLine(" >>> DoCreateChildAgentCall <<< " + uri); + + WebRequest ObjectCreateRequest = WebRequest.Create(uri); + ObjectCreateRequest.Method = "POST"; + ObjectCreateRequest.ContentType = "text/xml"; + ObjectCreateRequest.Timeout = 10000; + + OSDMap args = new OSDMap(2); + args["sog"] = OSD.FromString(sog.ToXmlString2()); + args["extra"] = OSD.FromString(sog.ExtraToXmlString()); + if (m_aScene.m_allowScriptCrossings) + { + string state = sog.GetStateSnapshot(); + if (state.Length > 0) + args["state"] = OSD.FromString(state); + } + + string strBuffer = ""; + byte[] buffer = new byte[1]; + try + { + strBuffer = OSDParser.SerializeJsonString(args); + System.Text.UTF8Encoding str = new System.Text.UTF8Encoding(); + buffer = str.GetBytes(strBuffer); + + } + catch (Exception e) + { + m_log.WarnFormat("[REST COMMS]: Exception thrown on serialization of CreateObject: {0}", e.Message); + // ignore. buffer will be empty, caller should check. + } + + Stream os = null; + try + { // send the Post + ObjectCreateRequest.ContentLength = buffer.Length; //Count bytes to send + os = ObjectCreateRequest.GetRequestStream(); + os.Write(buffer, 0, strBuffer.Length); //Send it + os.Close(); + m_log.InfoFormat("[REST COMMS]: Posted ChildAgentUpdate request to remote sim {0}", uri); + } + //catch (WebException ex) + catch + { + // m_log.InfoFormat("[REST COMMS]: Bad send on CreateObject {0}", ex.Message); + + return false; + } + + // Let's wait for the response + //m_log.Info("[REST COMMS]: Waiting for a reply after DoCreateChildAgentCall"); + + try + { + WebResponse webResponse = ObjectCreateRequest.GetResponse(); + if (webResponse == null) + { + m_log.Info("[REST COMMS]: Null reply on DoCreateObjectCall post"); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + //reply = sr.ReadToEnd().Trim(); + sr.ReadToEnd().Trim(); + sr.Close(); + //m_log.InfoFormat("[REST COMMS]: DoCreateChildAgentCall reply was {0} ", reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[REST COMMS]: exception on reply of DoCreateObjectCall {0}", ex.Message); + // ignore, really + } + + return true; + + } + + #endregion /* Do Work */ + + #region Incoming calls from remote instances + + /** + * Agent-related incoming calls + */ + + public Hashtable AgentHandler(Hashtable request) + { + //m_log.Debug("[CONNECTION DEBUGGING]: AgentHandler Called"); + + //Console.WriteLine("---------------------------"); + //Console.WriteLine(" >> uri=" + request["uri"]); + //Console.WriteLine(" >> content-type=" + request["content-type"]); + //Console.WriteLine(" >> http-method=" + request["http-method"]); + //Console.WriteLine("---------------------------\n"); + + Hashtable responsedata = new Hashtable(); + responsedata["content_type"] = "text/html"; + + UUID agentID; + string action; + ulong regionHandle; + if (!GetParams((string)request["uri"], out agentID, out regionHandle, out action)) + { + m_log.InfoFormat("[REST COMMS]: Invalid parameters for agent message {0}", request["uri"]); + responsedata["int_response_code"] = 404; + responsedata["str_response_string"] = "false"; + + return responsedata; + } + + // Next, let's parse the verb + string method = (string)request["http-method"]; + if (method.Equals("PUT")) + { + DoAgentPut(request, responsedata); + return responsedata; + } + else if (method.Equals("POST")) + { + DoAgentPost(request, responsedata, agentID); + return responsedata; + } + else if (method.Equals("DELETE")) + { + DoAgentDelete(request, responsedata, agentID, action, regionHandle); + + return responsedata; + } + else + { + m_log.InfoFormat("[REST COMMS]: method {0} not supported in agent message", method); + responsedata["int_response_code"] = 404; + responsedata["str_response_string"] = "false"; + + return responsedata; + } + + } + + protected OSDMap GetOSDMap(Hashtable request) + { + OSDMap args = null; + try + { + OSD buffer; + // We should pay attention to the content-type, but let's assume we know it's Json + buffer = OSDParser.DeserializeJson((string)request["body"]); + if (buffer.Type == OSDType.Map) + { + args = (OSDMap)buffer; + return args; + } + else + { + // uh? + m_log.Debug("[REST COMMS]: Got OSD of type " + buffer.Type.ToString()); + return null; + } + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on parse of REST message {0}", ex.Message); + return null; + } + } + + protected virtual void DoAgentPost(Hashtable request, Hashtable responsedata, UUID id) + { + OSDMap args = GetOSDMap(request); + if (args == null) + { + responsedata["int_response_code"] = 400; + responsedata["str_response_string"] = "false"; + return; + } + + // retrieve the regionhandle + ulong regionhandle = 0; + if (args["destination_handle"] != null) + UInt64.TryParse(args["destination_handle"].AsString(), out regionhandle); + + AgentCircuitData aCircuit = new AgentCircuitData(); + try + { + aCircuit.UnpackAgentCircuitData(args); + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on unpacking ChildCreate message {0}", ex.Message); + return; + } + + // This is the meaning of POST agent + AdjustUserInformation(aCircuit); + bool result = m_localBackend.SendCreateChildAgent(regionhandle, aCircuit); + + responsedata["int_response_code"] = 200; + responsedata["str_response_string"] = result.ToString(); + } + + protected virtual void DoAgentPut(Hashtable request, Hashtable responsedata) + { + OSDMap args = GetOSDMap(request); + if (args == null) + { + responsedata["int_response_code"] = 400; + responsedata["str_response_string"] = "false"; + return; + } + + // retrieve the regionhandle + ulong regionhandle = 0; + if (args["destination_handle"] != null) + UInt64.TryParse(args["destination_handle"].AsString(), out regionhandle); + + string messageType; + if (args["message_type"] != null) + messageType = args["message_type"].AsString(); + else + { + m_log.Warn("[REST COMMS]: Agent Put Message Type not found. "); + messageType = "AgentData"; + } + + bool result = true; + if ("AgentData".Equals(messageType)) + { + AgentData agent = new AgentData(); + try + { + agent.UnpackUpdateMessage(args); + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); + return; + } + //agent.Dump(); + // This is one of the meanings of PUT agent + result = m_localBackend.SendChildAgentUpdate(regionhandle, agent); + + } + else if ("AgentPosition".Equals(messageType)) + { + AgentPosition agent = new AgentPosition(); + try + { + agent.UnpackUpdateMessage(args); + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); + return; + } + //agent.Dump(); + // This is one of the meanings of PUT agent + result = m_localBackend.SendChildAgentUpdate(regionhandle, agent); + + } + + + + responsedata["int_response_code"] = 200; + responsedata["str_response_string"] = result.ToString(); + } + + protected virtual void DoAgentDelete(Hashtable request, Hashtable responsedata, UUID id, string action, ulong regionHandle) + { + //Console.WriteLine(" >>> DoDelete action:" + action + "; regionHandle:" + regionHandle); + + if (action.Equals("release")) + m_localBackend.SendReleaseAgent(regionHandle, id, ""); + else + m_localBackend.SendCloseAgent(regionHandle, id); + + responsedata["int_response_code"] = 200; + responsedata["str_response_string"] = "OpenSim agent " + id.ToString(); + } + + /** + * Object-related incoming calls + */ + + public Hashtable ObjectHandler(Hashtable request) + { + //m_log.Debug("[CONNECTION DEBUGGING]: ObjectHandler Called"); + + //Console.WriteLine("---------------------------"); + //Console.WriteLine(" >> uri=" + request["uri"]); + //Console.WriteLine(" >> content-type=" + request["content-type"]); + //Console.WriteLine(" >> http-method=" + request["http-method"]); + //Console.WriteLine("---------------------------\n"); + + Hashtable responsedata = new Hashtable(); + responsedata["content_type"] = "text/html"; + + UUID objectID; + string action; + ulong regionHandle; + if (!GetParams((string)request["uri"], out objectID, out regionHandle, out action)) + { + m_log.InfoFormat("[REST COMMS]: Invalid parameters for object message {0}", request["uri"]); + responsedata["int_response_code"] = 404; + responsedata["str_response_string"] = "false"; + + return responsedata; + } + + // Next, let's parse the verb + string method = (string)request["http-method"]; + if (method.Equals("POST")) + { + DoObjectPost(request, responsedata, regionHandle); + return responsedata; + } + //else if (method.Equals("PUT")) + //{ + // DoObjectPut(request, responsedata, agentID); + // return responsedata; + //} + //else if (method.Equals("DELETE")) + //{ + // DoObjectDelete(request, responsedata, agentID, action, regionHandle); + // return responsedata; + //} + else + { + m_log.InfoFormat("[REST COMMS]: method {0} not supported in object message", method); + responsedata["int_response_code"] = 404; + responsedata["str_response_string"] = "false"; + + return responsedata; + } + + } + + protected virtual void DoObjectPost(Hashtable request, Hashtable responsedata, ulong regionhandle) + { + OSDMap args = GetOSDMap(request); + if (args == null) + { + responsedata["int_response_code"] = 400; + responsedata["str_response_string"] = "false"; + return; + } + + string sogXmlStr = "", extraStr = "", stateXmlStr = ""; + if (args["sog"] != null) + sogXmlStr = args["sog"].AsString(); + if (args["extra"] != null) + extraStr = args["extra"].AsString(); + + UUID regionID = m_localBackend.GetRegionID(regionhandle); + SceneObjectGroup sog = null; + try + { + sog = new SceneObjectGroup(sogXmlStr); + sog.ExtraFromXmlString(extraStr); + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on deserializing scene object {0}", ex.Message); + responsedata["int_response_code"] = 400; + responsedata["str_response_string"] = "false"; + return; + } + + if ((args["state"] != null) && m_aScene.m_allowScriptCrossings) + { + stateXmlStr = args["state"].AsString(); + if (stateXmlStr != "") + { + try + { + sog.SetState(stateXmlStr, regionID); + } + catch (Exception ex) + { + m_log.InfoFormat("[REST COMMS]: exception on setting state for scene object {0}", ex.Message); + + } + } + } + // This is the meaning of POST object + bool result = m_localBackend.SendCreateObject(regionhandle, sog); + + responsedata["int_response_code"] = 200; + responsedata["str_response_string"] = result.ToString(); + } + + #endregion + + #region Misc + + /// + /// Extract the param from an uri. + /// + /// Something like this: /agent/uuid/ or /agent/uuid/handle/release + /// uuid on uuid field + /// optional action + protected bool GetParams(string uri, out UUID uuid, out ulong regionHandle, out string action) + { + uuid = UUID.Zero; + action = ""; + regionHandle = 0; + + uri = uri.Trim(new char[] { '/' }); + string[] parts = uri.Split('/'); + if (parts.Length <= 1) + { + return false; + } + else + { + if (!UUID.TryParse(parts[1], out uuid)) + return false; + + if (parts.Length >= 3) + UInt64.TryParse(parts[2], out regionHandle); + if (parts.Length >= 4) + action = parts[3]; + + return true; + } + } + + #endregion Misc + + #region Hyperlinks + + protected virtual ulong GetRegionHandle(ulong handle) + { + if (m_aScene.SceneGridService is HGSceneCommunicationService) + return ((HGSceneCommunicationService)(m_aScene.SceneGridService)).m_hg.FindRegionHandle(handle); + + return handle; + } + + protected virtual bool IsHyperlink(ulong handle) + { + if (m_aScene.SceneGridService is HGSceneCommunicationService) + return ((HGSceneCommunicationService)(m_aScene.SceneGridService)).m_hg.IsHyperlinkRegion(handle); + + return false; + } + + protected virtual void SendUserInformation(RegionInfo regInfo, AgentCircuitData aCircuit) + { + try + { + //if (IsHyperlink(regInfo.RegionHandle)) + if (m_aScene.SceneGridService is HGSceneCommunicationService) + { + ((HGSceneCommunicationService)(m_aScene.SceneGridService)).m_hg.SendUserInformation(regInfo, aCircuit); + } + } + catch // Bad cast + { } + + } + + protected virtual void AdjustUserInformation(AgentCircuitData aCircuit) + { + if (m_aScene.SceneGridService is HGSceneCommunicationService) + ((HGSceneCommunicationService)(m_aScene.SceneGridService)).m_hg.AdjustUserInformation(aCircuit); + } + #endregion /* Hyperlinks */ + + } +} diff --git a/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueGetModule.cs b/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueGetModule.cs new file mode 100644 index 0000000..e81466a --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueGetModule.cs @@ -0,0 +1,630 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Threading; +using System.Xml; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OSD = OpenMetaverse.StructuredData.OSD; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; +using BlockingLLSDQueue = OpenSim.Framework.BlockingQueue; + +namespace OpenSim.Region.CoreModules.Framework.EventQueue +{ + public struct QueueItem + { + public int id; + public OSDMap body; + } + + public class EventQueueGetModule : IEventQueue, IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private Scene m_scene = null; + private IConfigSource m_gConfig; + bool enabledYN = false; + + private Dictionary m_ids = new Dictionary(); + + private Dictionary queues = new Dictionary(); + private Dictionary m_QueueUUIDAvatarMapping = new Dictionary(); + private Dictionary m_AvatarQueueUUIDMapping = new Dictionary(); + + #region IRegionModule methods + public void Initialise(Scene scene, IConfigSource config) + { + m_gConfig = config; + + IConfig startupConfig = m_gConfig.Configs["Startup"]; + + ReadConfigAndPopulate(scene, startupConfig, "Startup"); + + if (enabledYN) + { + m_scene = scene; + scene.RegisterModuleInterface(this); + + // Register fallback handler + // Why does EQG Fail on region crossings! + + //scene.CommsManager.HttpServer.AddLLSDHandler("/CAPS/EQG/", EventQueueFallBack); + + scene.EventManager.OnNewClient += OnNewClient; + + // TODO: Leaving these open, or closing them when we + // become a child is incorrect. It messes up TP in a big + // way. CAPS/EQ need to be active as long as the UDP + // circuit is there. + + scene.EventManager.OnClientClosed += ClientClosed; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnRegisterCaps += OnRegisterCaps; + } + else + { + m_gConfig = null; + } + + } + + private void ReadConfigAndPopulate(Scene scene, IConfig startupConfig, string p) + { + enabledYN = startupConfig.GetBoolean("EventQueue", true); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "EventQueueGetModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + #endregion + + /// + /// Always returns a valid queue + /// + /// + /// + private BlockingLLSDQueue TryGetQueue(UUID agentId) + { + lock (queues) + { + if (!queues.ContainsKey(agentId)) + { + m_log.DebugFormat( + "[EVENTQUEUE]: Adding new queue for agent {0} in region {1}", + agentId, m_scene.RegionInfo.RegionName); + + queues[agentId] = new BlockingLLSDQueue(); + } + + return queues[agentId]; + } + } + + /// + /// May return a null queue + /// + /// + /// + private BlockingLLSDQueue GetQueue(UUID agentId) + { + lock (queues) + { + if (queues.ContainsKey(agentId)) + { + return queues[agentId]; + } + else + return null; + } + } + + #region IEventQueue Members + + public bool Enqueue(OSD ev, UUID avatarID) + { + //m_log.DebugFormat("[EVENTQUEUE]: Enqueuing event for {0} in region {1}", avatarID, m_scene.RegionInfo.RegionName); + try + { + BlockingLLSDQueue queue = GetQueue(avatarID); + if (queue != null) + queue.Enqueue(ev); + } + catch(NullReferenceException e) + { + m_log.Error("[EVENTQUEUE] Caught exception: " + e); + return false; + } + + return true; + } + + #endregion + + private void OnNewClient(IClientAPI client) + { + //client.OnLogout += ClientClosed; + } + +// private void ClientClosed(IClientAPI client) +// { +// ClientClosed(client.AgentId); +// } + + private void ClientClosed(UUID AgentID) + { + m_log.DebugFormat("[EVENTQUEUE]: Closed client {0} in region {1}", AgentID, m_scene.RegionInfo.RegionName); + + int count = 0; + while (queues.ContainsKey(AgentID) && queues[AgentID].Count() > 0 && count++ < 5) + { + Thread.Sleep(1000); + } + + lock (queues) + { + queues.Remove(AgentID); + } + List removeitems = new List(); + lock (m_AvatarQueueUUIDMapping) + { + foreach (UUID ky in m_AvatarQueueUUIDMapping.Keys) + { + if (ky == AgentID) + { + removeitems.Add(ky); + } + } + + foreach (UUID ky in removeitems) + { + m_AvatarQueueUUIDMapping.Remove(ky); + m_scene.CommsManager.HttpServer.RemoveHTTPHandler("","/CAPS/EQG/" + ky.ToString() + "/"); + } + + } + UUID searchval = UUID.Zero; + + removeitems.Clear(); + + lock (m_QueueUUIDAvatarMapping) + { + foreach (UUID ky in m_QueueUUIDAvatarMapping.Keys) + { + searchval = m_QueueUUIDAvatarMapping[ky]; + + if (searchval == AgentID) + { + removeitems.Add(ky); + } + } + + foreach (UUID ky in removeitems) + m_QueueUUIDAvatarMapping.Remove(ky); + + } + } + + private void MakeChildAgent(ScenePresence avatar) + { + //m_log.DebugFormat("[EVENTQUEUE]: Make Child agent {0} in region {1}.", avatar.UUID, m_scene.RegionInfo.RegionName); + //lock (m_ids) + // { + //if (m_ids.ContainsKey(avatar.UUID)) + //{ + // close the event queue. + //m_ids[avatar.UUID] = -1; + //} + //} + } + + public void OnRegisterCaps(UUID agentID, Caps caps) + { + // Register an event queue for the client + + //m_log.DebugFormat( + // "[EVENTQUEUE]: OnRegisterCaps: agentID {0} caps {1} region {2}", + // agentID, caps, m_scene.RegionInfo.RegionName); + + // Let's instantiate a Queue for this agent right now + TryGetQueue(agentID); + + string capsBase = "/CAPS/EQG/"; + UUID EventQueueGetUUID = UUID.Zero; + + lock (m_AvatarQueueUUIDMapping) + { + // Reuse open queues. The client does! + if (m_AvatarQueueUUIDMapping.ContainsKey(agentID)) + { + m_log.DebugFormat("[EVENTQUEUE]: Found Existing UUID!"); + EventQueueGetUUID = m_AvatarQueueUUIDMapping[agentID]; + } + else + { + EventQueueGetUUID = UUID.Random(); + //m_log.DebugFormat("[EVENTQUEUE]: Using random UUID!"); + } + } + + lock (m_QueueUUIDAvatarMapping) + { + if (!m_QueueUUIDAvatarMapping.ContainsKey(EventQueueGetUUID)) + m_QueueUUIDAvatarMapping.Add(EventQueueGetUUID, agentID); + } + + lock (m_AvatarQueueUUIDMapping) + { + if (!m_AvatarQueueUUIDMapping.ContainsKey(agentID)) + m_AvatarQueueUUIDMapping.Add(agentID, EventQueueGetUUID); + } + + // Register this as a caps handler + caps.RegisterHandler("EventQueueGet", + new RestHTTPHandler("POST", capsBase + EventQueueGetUUID.ToString() + "/", + delegate(Hashtable m_dhttpMethod) + { + return ProcessQueue(m_dhttpMethod, agentID, caps); + })); + + // This will persist this beyond the expiry of the caps handlers + m_scene.CommsManager.HttpServer.AddHTTPHandler( + capsBase + EventQueueGetUUID.ToString() + "/", EventQueuePath2); + + Random rnd = new Random(System.Environment.TickCount); + lock (m_ids) + { + if (!m_ids.ContainsKey(agentID)) + m_ids.Add(agentID, rnd.Next(30000000)); + } + } + + public Hashtable ProcessQueue(Hashtable request, UUID agentID, Caps caps) + { + // TODO: this has to be redone to not busy-wait (and block the thread), + // TODO: as soon as we have a non-blocking way to handle HTTP-requests. + +// if (m_log.IsDebugEnabled) +// { +// String debug = "[EVENTQUEUE]: Got request for agent {0} in region {1} from thread {2}: [ "; +// foreach (object key in request.Keys) +// { +// debug += key.ToString() + "=" + request[key].ToString() + " "; +// } +// m_log.DebugFormat(debug + " ]", agentID, m_scene.RegionInfo.RegionName, System.Threading.Thread.CurrentThread.Name); +// } + + BlockingLLSDQueue queue = TryGetQueue(agentID); + OSD element = queue.Dequeue(15000); // 15s timeout + + Hashtable responsedata = new Hashtable(); + + int thisID = 0; + lock (m_ids) + thisID = m_ids[agentID]; + + if (element == null) + { + //m_log.ErrorFormat("[EVENTQUEUE]: Nothing to process in " + m_scene.RegionInfo.RegionName); + if (thisID == -1) // close-request + { + m_log.ErrorFormat("[EVENTQUEUE]: 404 in " + m_scene.RegionInfo.RegionName); + responsedata["int_response_code"] = 404; //501; //410; //404; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = "Closed EQG"; + return responsedata; + } + responsedata["int_response_code"] = 502; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = "Upstream error: "; + responsedata["error_status_text"] = "Upstream error:"; + responsedata["http_protocol_version"] = "HTTP/1.0"; + return responsedata; + } + + OSDArray array = new OSDArray(); + if (element == null) // didn't have an event in 15s + { + // Send it a fake event to keep the client polling! It doesn't like 502s like the proxys say! + array.Add(EventQueueHelper.KeepAliveEvent()); + m_log.DebugFormat("[EVENTQUEUE]: adding fake event for {0} in region {1}", agentID, m_scene.RegionInfo.RegionName); + } + else + { + array.Add(element); + while (queue.Count() > 0) + { + array.Add(queue.Dequeue(1)); + thisID++; + } + } + + OSDMap events = new OSDMap(); + events.Add("events", array); + + events.Add("id", new OSDInteger(thisID)); + lock (m_ids) + { + m_ids[agentID] = thisID + 1; + } + + responsedata["int_response_code"] = 200; + responsedata["content_type"] = "application/xml"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = OSDParser.SerializeLLSDXmlString(events); + //m_log.DebugFormat("[EVENTQUEUE]: sending response for {0} in region {1}: {2}", agentID, m_scene.RegionInfo.RegionName, responsedata["str_response_string"]); + + return responsedata; + } + + public Hashtable EventQueuePath2(Hashtable request) + { + string capuuid = (string)request["uri"]; //path.Replace("/CAPS/EQG/",""); + // pull off the last "/" in the path. + Hashtable responsedata = new Hashtable(); + capuuid = capuuid.Substring(0, capuuid.Length - 1); + capuuid = capuuid.Replace("/CAPS/EQG/", ""); + UUID AvatarID = UUID.Zero; + UUID capUUID = UUID.Zero; + + // parse the path and search for the avatar with it registered + if (UUID.TryParse(capuuid, out capUUID)) + { + lock (m_QueueUUIDAvatarMapping) + { + if (m_QueueUUIDAvatarMapping.ContainsKey(capUUID)) + { + AvatarID = m_QueueUUIDAvatarMapping[capUUID]; + } + } + if (AvatarID != UUID.Zero) + { + return ProcessQueue(request, AvatarID, m_scene.CapsModule.GetCapsHandlerForUser(AvatarID)); + } + else + { + responsedata["int_response_code"] = 404; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = "Not Found"; + responsedata["error_status_text"] = "Not Found"; + responsedata["http_protocol_version"] = "HTTP/1.0"; + return responsedata; + // return 404 + } + } + else + { + responsedata["int_response_code"] = 404; + responsedata["content_type"] = "text/plain"; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = "Not Found"; + responsedata["error_status_text"] = "Not Found"; + responsedata["http_protocol_version"] = "HTTP/1.0"; + return responsedata; + // return 404 + } + + } + + public OSD EventQueueFallBack(string path, OSD request, string endpoint) + { + // This is a fallback element to keep the client from loosing EventQueueGet + // Why does CAPS fail sometimes!? + m_log.Warn("[EVENTQUEUE]: In the Fallback handler! We lost the Queue in the rest handler!"); + string capuuid = path.Replace("/CAPS/EQG/",""); + capuuid = capuuid.Substring(0, capuuid.Length - 1); + +// UUID AvatarID = UUID.Zero; + UUID capUUID = UUID.Zero; + if (UUID.TryParse(capuuid, out capUUID)) + { +/* Don't remove this yet code cleaners! + * Still testing this! + * + lock (m_QueueUUIDAvatarMapping) + { + if (m_QueueUUIDAvatarMapping.ContainsKey(capUUID)) + { + AvatarID = m_QueueUUIDAvatarMapping[capUUID]; + } + } + + + if (AvatarID != UUID.Zero) + { + // Repair the CAP! + //OpenSim.Framework.Communications.Capabilities.Caps caps = m_scene.GetCapsHandlerForUser(AvatarID); + //string capsBase = "/CAPS/EQG/"; + //caps.RegisterHandler("EventQueueGet", + //new RestHTTPHandler("POST", capsBase + capUUID.ToString() + "/", + //delegate(Hashtable m_dhttpMethod) + //{ + // return ProcessQueue(m_dhttpMethod, AvatarID, caps); + //})); + // start new ID sequence. + Random rnd = new Random(System.Environment.TickCount); + lock (m_ids) + { + if (!m_ids.ContainsKey(AvatarID)) + m_ids.Add(AvatarID, rnd.Next(30000000)); + } + + + int thisID = 0; + lock (m_ids) + thisID = m_ids[AvatarID]; + + BlockingLLSDQueue queue = GetQueue(AvatarID); + OSDArray array = new OSDArray(); + LLSD element = queue.Dequeue(15000); // 15s timeout + if (element == null) + { + + array.Add(EventQueueHelper.KeepAliveEvent()); + } + else + { + array.Add(element); + while (queue.Count() > 0) + { + array.Add(queue.Dequeue(1)); + thisID++; + } + } + OSDMap events = new OSDMap(); + events.Add("events", array); + + events.Add("id", new LLSDInteger(thisID)); + + lock (m_ids) + { + m_ids[AvatarID] = thisID + 1; + } + + return events; + } + else + { + return new LLSD(); + } +* +*/ + } + else + { + //return new LLSD(); + } + + return new OSDString("shutdown404!"); + } + + public void DisableSimulator(ulong handle, UUID avatarID) + { + OSD item = EventQueueHelper.DisableSimulator(handle); + Enqueue(item, avatarID); + } + + public void EnableSimulator(ulong handle, IPEndPoint endPoint, UUID avatarID) + { + OSD item = EventQueueHelper.EnableSimulator(handle, endPoint); + Enqueue(item, avatarID); + } + + public void EstablishAgentCommunication(UUID avatarID, IPEndPoint endPoint, string capsPath) + { + OSD item = EventQueueHelper.EstablishAgentCommunication(avatarID, endPoint.ToString(), capsPath); + Enqueue(item, avatarID); + } + + public void TeleportFinishEvent(ulong regionHandle, byte simAccess, + IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL, + UUID avatarID) + { + OSD item = EventQueueHelper.TeleportFinishEvent(regionHandle, simAccess, regionExternalEndPoint, + locationID, flags, capsURL, avatarID); + Enqueue(item, avatarID); + } + + public void CrossRegion(ulong handle, Vector3 pos, Vector3 lookAt, + IPEndPoint newRegionExternalEndPoint, + string capsURL, UUID avatarID, UUID sessionID) + { + OSD item = EventQueueHelper.CrossRegion(handle, pos, lookAt, newRegionExternalEndPoint, + capsURL, avatarID, sessionID); + Enqueue(item, avatarID); + } + + public void ChatterboxInvitation(UUID sessionID, string sessionName, + UUID fromAgent, string message, UUID toAgent, string fromName, byte dialog, + uint timeStamp, bool offline, int parentEstateID, Vector3 position, + uint ttl, UUID transactionID, bool fromGroup, byte[] binaryBucket) + { + OSD item = EventQueueHelper.ChatterboxInvitation(sessionID, sessionName, fromAgent, message, toAgent, fromName, dialog, + timeStamp, offline, parentEstateID, position, ttl, transactionID, + fromGroup, binaryBucket); + Enqueue(item, toAgent); + m_log.InfoFormat("########### eq ChatterboxInvitation #############\n{0}", item); + + } + + public void ChatterBoxSessionAgentListUpdates(UUID sessionID, UUID fromAgent, UUID toAgent, bool canVoiceChat, + bool isModerator, bool textMute) + { + OSD item = EventQueueHelper.ChatterBoxSessionAgentListUpdates(sessionID, fromAgent, canVoiceChat, + isModerator, textMute); + Enqueue(item, toAgent); + m_log.InfoFormat("########### eq ChatterBoxSessionAgentListUpdates #############\n{0}", item); + } + + public void ParcelProperties(ParcelPropertiesPacket parcelPropertiesPacket, UUID avatarID) + { + OSD item = EventQueueHelper.ParcelProperties(parcelPropertiesPacket); + Enqueue(item, avatarID); + } + + public void GroupMembership(AgentGroupDataUpdatePacket groupUpdate, UUID avatarID) + { + OSD item = EventQueueHelper.GroupMembership(groupUpdate); + Enqueue(item, avatarID); + } + } +} diff --git a/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueHelper.cs b/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueHelper.cs new file mode 100644 index 0000000..80f6fce --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/EventQueue/EventQueueHelper.cs @@ -0,0 +1,459 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Net; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; + +namespace OpenSim.Region.CoreModules.Framework.EventQueue +{ + public class EventQueueHelper + { + private EventQueueHelper() {} // no construction possible, it's an utility class + + private static byte[] ulongToByteArray(ulong uLongValue) + { + // Reverse endianness of RegionHandle + return new byte[] + { + (byte)((uLongValue >> 56) % 256), + (byte)((uLongValue >> 48) % 256), + (byte)((uLongValue >> 40) % 256), + (byte)((uLongValue >> 32) % 256), + (byte)((uLongValue >> 24) % 256), + (byte)((uLongValue >> 16) % 256), + (byte)((uLongValue >> 8) % 256), + (byte)(uLongValue % 256) + }; + } + + private static byte[] uintToByteArray(uint uIntValue) + { + byte[] resultbytes = Utils.UIntToBytes(uIntValue); + if (BitConverter.IsLittleEndian) + Array.Reverse(resultbytes); + + return resultbytes; + } + + public static OSD buildEvent(string eventName, OSD eventBody) + { + OSDMap llsdEvent = new OSDMap(2); + llsdEvent.Add("message", new OSDString(eventName)); + llsdEvent.Add("body", eventBody); + + return llsdEvent; + } + + public static OSD EnableSimulator(ulong handle, IPEndPoint endPoint) + { + OSDMap llsdSimInfo = new OSDMap(3); + + llsdSimInfo.Add("Handle", new OSDBinary(ulongToByteArray(handle))); + llsdSimInfo.Add("IP", new OSDBinary(endPoint.Address.GetAddressBytes())); + llsdSimInfo.Add("Port", new OSDInteger(endPoint.Port)); + + OSDArray arr = new OSDArray(1); + arr.Add(llsdSimInfo); + + OSDMap llsdBody = new OSDMap(1); + llsdBody.Add("SimulatorInfo", arr); + + return buildEvent("EnableSimulator", llsdBody); + } + + public static OSD DisableSimulator(ulong handle) + { + //OSDMap llsdSimInfo = new OSDMap(1); + + //llsdSimInfo.Add("Handle", new OSDBinary(regionHandleToByteArray(handle))); + + //OSDArray arr = new OSDArray(1); + //arr.Add(llsdSimInfo); + + OSDMap llsdBody = new OSDMap(0); + //llsdBody.Add("SimulatorInfo", arr); + + return buildEvent("DisableSimulator", llsdBody); + } + + public static OSD CrossRegion(ulong handle, Vector3 pos, Vector3 lookAt, + IPEndPoint newRegionExternalEndPoint, + string capsURL, UUID agentID, UUID sessionID) + { + OSDArray lookAtArr = new OSDArray(3); + lookAtArr.Add(OSD.FromReal(lookAt.X)); + lookAtArr.Add(OSD.FromReal(lookAt.Y)); + lookAtArr.Add(OSD.FromReal(lookAt.Z)); + + OSDArray positionArr = new OSDArray(3); + positionArr.Add(OSD.FromReal(pos.X)); + positionArr.Add(OSD.FromReal(pos.Y)); + positionArr.Add(OSD.FromReal(pos.Z)); + + OSDMap infoMap = new OSDMap(2); + infoMap.Add("LookAt", lookAtArr); + infoMap.Add("Position", positionArr); + + OSDArray infoArr = new OSDArray(1); + infoArr.Add(infoMap); + + OSDMap agentDataMap = new OSDMap(2); + agentDataMap.Add("AgentID", OSD.FromUUID(agentID)); + agentDataMap.Add("SessionID", OSD.FromUUID(sessionID)); + + OSDArray agentDataArr = new OSDArray(1); + agentDataArr.Add(agentDataMap); + + OSDMap regionDataMap = new OSDMap(4); + regionDataMap.Add("RegionHandle", OSD.FromBinary(ulongToByteArray(handle))); + regionDataMap.Add("SeedCapability", OSD.FromString(capsURL)); + regionDataMap.Add("SimIP", OSD.FromBinary(newRegionExternalEndPoint.Address.GetAddressBytes())); + regionDataMap.Add("SimPort", OSD.FromInteger(newRegionExternalEndPoint.Port)); + + OSDArray regionDataArr = new OSDArray(1); + regionDataArr.Add(regionDataMap); + + OSDMap llsdBody = new OSDMap(3); + llsdBody.Add("Info", infoArr); + llsdBody.Add("AgentData", agentDataArr); + llsdBody.Add("RegionData", regionDataArr); + + return buildEvent("CrossedRegion", llsdBody); + } + + public static OSD TeleportFinishEvent( + ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL, UUID agentID) + { + OSDMap info = new OSDMap(); + info.Add("AgentID", OSD.FromUUID(agentID)); + info.Add("LocationID", OSD.FromInteger(4)); // TODO what is this? + info.Add("RegionHandle", OSD.FromBinary(ulongToByteArray(regionHandle))); + info.Add("SeedCapability", OSD.FromString(capsURL)); + info.Add("SimAccess", OSD.FromInteger(simAccess)); + info.Add("SimIP", OSD.FromBinary(regionExternalEndPoint.Address.GetAddressBytes())); + info.Add("SimPort", OSD.FromInteger(regionExternalEndPoint.Port)); + info.Add("TeleportFlags", OSD.FromBinary(1L << 4)); // AgentManager.TeleportFlags.ViaLocation + + OSDArray infoArr = new OSDArray(); + infoArr.Add(info); + + OSDMap body = new OSDMap(); + body.Add("Info", infoArr); + + return buildEvent("TeleportFinish", body); + } + + public static OSD ScriptRunningReplyEvent(UUID objectID, UUID itemID, bool running, bool mono) + { + OSDMap script = new OSDMap(); + script.Add("ObjectID", OSD.FromUUID(objectID)); + script.Add("ItemID", OSD.FromUUID(itemID)); + script.Add("Running", OSD.FromBoolean(running)); + script.Add("Mono", OSD.FromBoolean(mono)); + + OSDArray scriptArr = new OSDArray(); + scriptArr.Add(script); + + OSDMap body = new OSDMap(); + body.Add("Script", scriptArr); + + return buildEvent("ScriptRunningReply", body); + } + + public static OSD EstablishAgentCommunication(UUID agentID, string simIpAndPort, string seedcap) + { + OSDMap body = new OSDMap(3); + body.Add("agent-id", new OSDUUID(agentID)); + body.Add("sim-ip-and-port", new OSDString(simIpAndPort)); + body.Add("seed-capability", new OSDString(seedcap)); + + return buildEvent("EstablishAgentCommunication", body); + } + + public static OSD KeepAliveEvent() + { + return buildEvent("FAKEEVENT", new OSDMap()); + } + + public static OSD AgentParams(UUID agentID, bool checkEstate, int godLevel, bool limitedToEstate) + { + OSDMap body = new OSDMap(4); + + body.Add("agent_id", new OSDUUID(agentID)); + body.Add("check_estate", new OSDInteger(checkEstate ? 1 : 0)); + body.Add("god_level", new OSDInteger(godLevel)); + body.Add("limited_to_estate", new OSDInteger(limitedToEstate ? 1 : 0)); + + return body; + } + + public static OSD InstantMessageParams(UUID fromAgent, string message, UUID toAgent, + string fromName, byte dialog, uint timeStamp, bool offline, int parentEstateID, + Vector3 position, uint ttl, UUID transactionID, bool fromGroup, byte[] binaryBucket) + { + OSDMap messageParams = new OSDMap(15); + messageParams.Add("type", new OSDInteger((int)dialog)); + + OSDArray positionArray = new OSDArray(3); + positionArray.Add(OSD.FromReal(position.X)); + positionArray.Add(OSD.FromReal(position.Y)); + positionArray.Add(OSD.FromReal(position.Z)); + messageParams.Add("position", positionArray); + + messageParams.Add("region_id", new OSDUUID(UUID.Zero)); + messageParams.Add("to_id", new OSDUUID(toAgent)); + messageParams.Add("source", new OSDInteger(0)); + + OSDMap data = new OSDMap(1); + data.Add("binary_bucket", OSD.FromBinary(binaryBucket)); + messageParams.Add("data", data); + messageParams.Add("message", new OSDString(message)); + messageParams.Add("id", new OSDUUID(transactionID)); + messageParams.Add("from_name", new OSDString(fromName)); + messageParams.Add("timestamp", new OSDInteger((int)timeStamp)); + messageParams.Add("offline", new OSDInteger(offline ? 1 : 0)); + messageParams.Add("parent_estate_id", new OSDInteger(parentEstateID)); + messageParams.Add("ttl", new OSDInteger((int)ttl)); + messageParams.Add("from_id", new OSDUUID(fromAgent)); + messageParams.Add("from_group", new OSDInteger(fromGroup ? 1 : 0)); + + return messageParams; + } + + public static OSD InstantMessage(UUID fromAgent, string message, UUID toAgent, + string fromName, byte dialog, uint timeStamp, bool offline, int parentEstateID, + Vector3 position, uint ttl, UUID transactionID, bool fromGroup, byte[] binaryBucket, + bool checkEstate, int godLevel, bool limitedToEstate) + { + OSDMap im = new OSDMap(2); + im.Add("message_params", InstantMessageParams(fromAgent, message, toAgent, + fromName, dialog, timeStamp, offline, parentEstateID, + position, ttl, transactionID, fromGroup, binaryBucket)); + + im.Add("agent_params", AgentParams(fromAgent, checkEstate, godLevel, limitedToEstate)); + + return im; + } + + + public static OSD ChatterboxInvitation(UUID sessionID, string sessionName, + UUID fromAgent, string message, UUID toAgent, string fromName, byte dialog, + uint timeStamp, bool offline, int parentEstateID, Vector3 position, + uint ttl, UUID transactionID, bool fromGroup, byte[] binaryBucket) + { + OSDMap body = new OSDMap(5); + body.Add("session_id", new OSDUUID(sessionID)); + body.Add("from_name", new OSDString(fromName)); + body.Add("session_name", new OSDString(sessionName)); + body.Add("from_id", new OSDUUID(fromAgent)); + + body.Add("instantmessage", InstantMessage(fromAgent, message, toAgent, + fromName, dialog, timeStamp, offline, parentEstateID, position, + ttl, transactionID, fromGroup, binaryBucket, true, 0, true)); + + OSDMap chatterboxInvitation = new OSDMap(2); + chatterboxInvitation.Add("message", new OSDString("ChatterBoxInvitation")); + chatterboxInvitation.Add("body", body); + return chatterboxInvitation; + } + + public static OSD ChatterBoxSessionAgentListUpdates(UUID sessionID, + UUID agentID, bool canVoiceChat, bool isModerator, bool textMute) + { + OSDMap body = new OSDMap(); + OSDMap agentUpdates = new OSDMap(); + OSDMap infoDetail = new OSDMap(); + OSDMap mutes = new OSDMap(); + + mutes.Add("text", OSD.FromBoolean(textMute)); + infoDetail.Add("can_voice_chat", OSD.FromBoolean(canVoiceChat)); + infoDetail.Add("is_moderator", OSD.FromBoolean(isModerator)); + infoDetail.Add("mutes", mutes); + OSDMap info = new OSDMap(); + info.Add("info", infoDetail); + agentUpdates.Add(agentID.ToString(), info); + body.Add("agent_updates", agentUpdates); + body.Add("session_id", OSD.FromUUID(sessionID)); + body.Add("updates", new OSD()); + + OSDMap chatterBoxSessionAgentListUpdates = new OSDMap(); + chatterBoxSessionAgentListUpdates.Add("message", OSD.FromString("ChatterBoxSessionAgentListUpdates")); + chatterBoxSessionAgentListUpdates.Add("body", body); + + return chatterBoxSessionAgentListUpdates; + } + + public static OSD ParcelProperties(ParcelPropertiesPacket parcelPropertiesPacket) + { + OSDMap parcelProperties = new OSDMap(); + OSDMap body = new OSDMap(); + + OSDArray ageVerificationBlock = new OSDArray(); + OSDMap ageVerificationMap = new OSDMap(); + ageVerificationMap.Add("RegionDenyAgeUnverified", + OSD.FromBoolean(parcelPropertiesPacket.AgeVerificationBlock.RegionDenyAgeUnverified)); + ageVerificationBlock.Add(ageVerificationMap); + body.Add("AgeVerificationBlock", ageVerificationBlock); + + // LL sims send media info in this event queue message but it's not in the UDP + // packet we construct this event queue message from. This should be refactored in + // other areas of the code so it can all be send in the same message. Until then we will + // still send the media info via UDP + + //OSDArray mediaData = new OSDArray(); + //OSDMap mediaDataMap = new OSDMap(); + //mediaDataMap.Add("MediaDesc", OSD.FromString("")); + //mediaDataMap.Add("MediaHeight", OSD.FromInteger(0)); + //mediaDataMap.Add("MediaLoop", OSD.FromInteger(0)); + //mediaDataMap.Add("MediaType", OSD.FromString("type/type")); + //mediaDataMap.Add("MediaWidth", OSD.FromInteger(0)); + //mediaDataMap.Add("ObscureMedia", OSD.FromInteger(0)); + //mediaDataMap.Add("ObscureMusic", OSD.FromInteger(0)); + //mediaData.Add(mediaDataMap); + //body.Add("MediaData", mediaData); + + OSDArray parcelData = new OSDArray(); + OSDMap parcelDataMap = new OSDMap(); + OSDArray AABBMax = new OSDArray(3); + AABBMax.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMax.X)); + AABBMax.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMax.Y)); + AABBMax.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMax.Z)); + parcelDataMap.Add("AABBMax", AABBMax); + + OSDArray AABBMin = new OSDArray(3); + AABBMin.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMin.X)); + AABBMin.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMin.Y)); + AABBMin.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.AABBMin.Z)); + parcelDataMap.Add("AABBMin", AABBMin); + + parcelDataMap.Add("Area", OSD.FromInteger(parcelPropertiesPacket.ParcelData.Area)); + parcelDataMap.Add("AuctionID", OSD.FromBinary(uintToByteArray(parcelPropertiesPacket.ParcelData.AuctionID))); + parcelDataMap.Add("AuthBuyerID", OSD.FromUUID(parcelPropertiesPacket.ParcelData.AuthBuyerID)); + parcelDataMap.Add("Bitmap", OSD.FromBinary(parcelPropertiesPacket.ParcelData.Bitmap)); + parcelDataMap.Add("Category", OSD.FromInteger((int)parcelPropertiesPacket.ParcelData.Category)); + parcelDataMap.Add("ClaimDate", OSD.FromInteger(parcelPropertiesPacket.ParcelData.ClaimDate)); + parcelDataMap.Add("ClaimPrice", OSD.FromInteger(parcelPropertiesPacket.ParcelData.ClaimPrice)); + parcelDataMap.Add("Desc", OSD.FromString(Utils.BytesToString(parcelPropertiesPacket.ParcelData.Desc))); + parcelDataMap.Add("GroupID", OSD.FromUUID(parcelPropertiesPacket.ParcelData.GroupID)); + parcelDataMap.Add("GroupPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.GroupPrims)); + parcelDataMap.Add("IsGroupOwned", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.IsGroupOwned)); + parcelDataMap.Add("LandingType", OSD.FromInteger(parcelPropertiesPacket.ParcelData.LandingType)); + parcelDataMap.Add("LocalID", OSD.FromInteger(parcelPropertiesPacket.ParcelData.LocalID)); + parcelDataMap.Add("MaxPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.MaxPrims)); + parcelDataMap.Add("MediaAutoScale", OSD.FromInteger((int)parcelPropertiesPacket.ParcelData.MediaAutoScale)); + parcelDataMap.Add("MediaID", OSD.FromUUID(parcelPropertiesPacket.ParcelData.MediaID)); + parcelDataMap.Add("MediaURL", OSD.FromString(Utils.BytesToString(parcelPropertiesPacket.ParcelData.MediaURL))); + parcelDataMap.Add("MusicURL", OSD.FromString(Utils.BytesToString(parcelPropertiesPacket.ParcelData.MusicURL))); + parcelDataMap.Add("Name", OSD.FromString(Utils.BytesToString(parcelPropertiesPacket.ParcelData.Name))); + parcelDataMap.Add("OtherCleanTime", OSD.FromInteger(parcelPropertiesPacket.ParcelData.OtherCleanTime)); + parcelDataMap.Add("OtherCount", OSD.FromInteger(parcelPropertiesPacket.ParcelData.OtherCount)); + parcelDataMap.Add("OtherPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.OtherPrims)); + parcelDataMap.Add("OwnerID", OSD.FromUUID(parcelPropertiesPacket.ParcelData.OwnerID)); + parcelDataMap.Add("OwnerPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.OwnerPrims)); + parcelDataMap.Add("ParcelFlags", OSD.FromBinary(uintToByteArray(parcelPropertiesPacket.ParcelData.ParcelFlags))); + parcelDataMap.Add("ParcelPrimBonus", OSD.FromReal(parcelPropertiesPacket.ParcelData.ParcelPrimBonus)); + parcelDataMap.Add("PassHours", OSD.FromReal(parcelPropertiesPacket.ParcelData.PassHours)); + parcelDataMap.Add("PassPrice", OSD.FromInteger(parcelPropertiesPacket.ParcelData.PassPrice)); + parcelDataMap.Add("PublicCount", OSD.FromInteger(parcelPropertiesPacket.ParcelData.PublicCount)); + parcelDataMap.Add("RegionDenyAnonymous", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.RegionDenyAnonymous)); + parcelDataMap.Add("RegionDenyIdentified", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.RegionDenyIdentified)); + parcelDataMap.Add("RegionDenyTransacted", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.RegionDenyTransacted)); + + parcelDataMap.Add("RegionPushOverride", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.RegionPushOverride)); + parcelDataMap.Add("RentPrice", OSD.FromInteger(parcelPropertiesPacket.ParcelData.RentPrice)); + parcelDataMap.Add("RequestResult", OSD.FromInteger(parcelPropertiesPacket.ParcelData.RequestResult)); + parcelDataMap.Add("SalePrice", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SalePrice)); + parcelDataMap.Add("SelectedPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SelectedPrims)); + parcelDataMap.Add("SelfCount", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SelfCount)); + parcelDataMap.Add("SequenceID", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SequenceID)); + parcelDataMap.Add("SimWideMaxPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SimWideMaxPrims)); + parcelDataMap.Add("SimWideTotalPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.SimWideTotalPrims)); + parcelDataMap.Add("SnapSelection", OSD.FromBoolean(parcelPropertiesPacket.ParcelData.SnapSelection)); + parcelDataMap.Add("SnapshotID", OSD.FromUUID(parcelPropertiesPacket.ParcelData.SnapshotID)); + parcelDataMap.Add("Status", OSD.FromInteger((int)parcelPropertiesPacket.ParcelData.Status)); + parcelDataMap.Add("TotalPrims", OSD.FromInteger(parcelPropertiesPacket.ParcelData.TotalPrims)); + + OSDArray UserLocation = new OSDArray(3); + UserLocation.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLocation.X)); + UserLocation.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLocation.Y)); + UserLocation.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLocation.Z)); + parcelDataMap.Add("UserLocation", UserLocation); + + OSDArray UserLookAt = new OSDArray(3); + UserLookAt.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLookAt.X)); + UserLookAt.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLookAt.Y)); + UserLookAt.Add(OSD.FromReal(parcelPropertiesPacket.ParcelData.UserLookAt.Z)); + parcelDataMap.Add("UserLookAt", UserLookAt); + + parcelData.Add(parcelDataMap); + body.Add("ParcelData", parcelData); + parcelProperties.Add("body", body); + parcelProperties.Add("message", OSD.FromString("ParcelProperties")); + + return parcelProperties; + } + + public static OSD GroupMembership(AgentGroupDataUpdatePacket groupUpdatePacket) + { + OSDMap groupUpdate = new OSDMap(); + groupUpdate.Add("message", OSD.FromString("AgentGroupDataUpdate")); + + OSDMap body = new OSDMap(); + OSDArray agentData = new OSDArray(); + OSDMap agentDataMap = new OSDMap(); + agentDataMap.Add("AgentID", OSD.FromUUID(groupUpdatePacket.AgentData.AgentID)); + agentData.Add(agentDataMap); + body.Add("AgentData", agentData); + + OSDArray groupData = new OSDArray(); + + foreach (AgentGroupDataUpdatePacket.GroupDataBlock groupDataBlock in groupUpdatePacket.GroupData) + { + OSDMap groupDataMap = new OSDMap(); + groupDataMap.Add("ListInProfile", OSD.FromBoolean(false)); + groupDataMap.Add("GroupID", OSD.FromUUID(groupDataBlock.GroupID)); + groupDataMap.Add("GroupInsigniaID", OSD.FromUUID(groupDataBlock.GroupInsigniaID)); + groupDataMap.Add("Contribution", OSD.FromInteger(groupDataBlock.Contribution)); + groupDataMap.Add("GroupPowers", OSD.FromBinary(ulongToByteArray(groupDataBlock.GroupPowers))); + groupDataMap.Add("GroupName", OSD.FromString(Utils.BytesToString(groupDataBlock.GroupName))); + groupDataMap.Add("AcceptNotices", OSD.FromBoolean(groupDataBlock.AcceptNotices)); + + groupData.Add(groupDataMap); + + } + body.Add("GroupData", groupData); + groupUpdate.Add("body", body); + + return groupUpdate; + } + + } +} diff --git a/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Command.cs b/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Command.cs new file mode 100644 index 0000000..fe29e0c --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Command.cs @@ -0,0 +1,216 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Framework.InterfaceCommander +{ + /// + /// A single function call encapsulated in a class which enforces arguments when passing around as Object[]'s. + /// Used for console commands and script API generation + /// + public class Command : ICommand + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private List m_args = new List(); + + private Action m_command; + private string m_help; + private string m_name; + private CommandIntentions m_intentions; //A permission type system could implement this and know what a command intends on doing. + + public Command(string name, CommandIntentions intention, Action command, string help) + { + m_name = name; + m_command = command; + m_help = help; + m_intentions = intention; + } + + #region ICommand Members + + public void AddArgument(string name, string helptext, string type) + { + m_args.Add(new CommandArgument(name, helptext, type)); + } + + public string Name + { + get { return m_name; } + } + + public CommandIntentions Intentions + { + get { return m_intentions; } + } + + public string Help + { + get { return m_help; } + } + + public Dictionary Arguments + { + get + { + Dictionary tmp = new Dictionary(); + foreach (CommandArgument arg in m_args) + { + tmp.Add(arg.Name, arg.ArgumentType); + } + return tmp; + } + } + + public string ShortHelp() + { + string help = m_name; + + foreach (CommandArgument arg in m_args) + { + help += " <" + arg.Name + ">"; + } + + return help; + } + + public void ShowConsoleHelp() + { + Console.WriteLine("== " + Name + " =="); + Console.WriteLine(m_help); + Console.WriteLine("= Parameters ="); + foreach (CommandArgument arg in m_args) + { + Console.WriteLine("* " + arg.Name + " (" + arg.ArgumentType + ")"); + Console.WriteLine("\t" + arg.HelpText); + } + } + + public void Run(Object[] args) + { + Object[] cleanArgs = new Object[m_args.Count]; + + if (args.Length < cleanArgs.Length) + { + Console.WriteLine("ERROR: Missing " + (cleanArgs.Length - args.Length) + " argument(s)"); + ShowConsoleHelp(); + return; + } + if (args.Length > cleanArgs.Length) + { + Console.WriteLine("ERROR: Too many arguments for this command. Type ' help' for help."); + return; + } + + int i = 0; + foreach (Object arg in args) + { + if (string.IsNullOrEmpty(arg.ToString())) + { + Console.WriteLine("ERROR: Empty arguments are not allowed"); + return; + } + try + { + switch (m_args[i].ArgumentType) + { + case "String": + m_args[i].ArgumentValue = arg.ToString(); + break; + case "Integer": + m_args[i].ArgumentValue = Int32.Parse(arg.ToString()); + break; + case "Double": + m_args[i].ArgumentValue = Double.Parse(arg.ToString()); + break; + case "Boolean": + m_args[i].ArgumentValue = Boolean.Parse(arg.ToString()); + break; + default: + Console.WriteLine("ERROR: Unknown desired type for argument " + m_args[i].Name + " on command " + m_name); + break; + } + } + catch (FormatException) + { + Console.WriteLine("ERROR: Argument number " + (i + 1) + + " (" + m_args[i].Name + ") must be a valid " + + m_args[i].ArgumentType.ToLower() + "."); + return; + } + cleanArgs[i] = m_args[i].ArgumentValue; + + i++; + } + + m_command.Invoke(cleanArgs); + } + + #endregion + } + + /// + /// A single command argument, contains name, type and at runtime, value. + /// + public class CommandArgument + { + private string m_help; + private string m_name; + private string m_type; + private Object m_val; + + public CommandArgument(string name, string help, string type) + { + m_name = name; + m_help = help; + m_type = type; + } + + public string Name + { + get { return m_name; } + } + + public string HelpText + { + get { return m_help; } + } + + public string ArgumentType + { + get { return m_type; } + } + + public Object ArgumentValue + { + get { return m_val; } + set { m_val = value; } + } + } +} diff --git a/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Commander.cs b/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Commander.cs new file mode 100644 index 0000000..cd905ab --- /dev/null +++ b/OpenSim/Region/CoreModules/Framework/InterfaceCommander/Commander.cs @@ -0,0 +1,182 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.Framework.InterfaceCommander +{ + /// + /// A class to enable modules to register console and script commands, which enforces typing and valid input. + /// + public class Commander : ICommander + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Used in runtime class generation + /// + private string m_generatedApiClassName; + + public string Name + { + get { return m_name; } + } + private string m_name; + + public string Help + { + get + { + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("=== " + m_name + " ==="); + + foreach (ICommand com in m_commands.Values) + { + sb.AppendLine("* " + Name + " " + com.Name + " - " + com.Help); + } + + return sb.ToString(); + } + } + + /// + /// Constructor + /// + /// + public Commander(string name) + { + m_name = name; + m_generatedApiClassName = m_name[0].ToString().ToUpper(); + + if (m_name.Length > 1) + m_generatedApiClassName += m_name.Substring(1); + } + + public Dictionary Commands + { + get { return m_commands; } + } + private Dictionary m_commands = new Dictionary(); + + #region ICommander Members + + public void RegisterCommand(string commandName, ICommand command) + { + m_commands[commandName] = command; + } + + /// + /// Generates a runtime C# class which can be compiled and inserted via reflection to enable modules to register new script commands + /// + /// Returns C# source code to create a binding + public string GenerateRuntimeAPI() + { + string classSrc = "\n\tpublic class " + m_generatedApiClassName + " {\n"; + foreach (ICommand com in m_commands.Values) + { + classSrc += "\tpublic void " + EscapeRuntimeAPICommand(com.Name) + "( "; + foreach (KeyValuePair arg in com.Arguments) + { + classSrc += arg.Value + " " + Util.Md5Hash(arg.Key) + ","; + } + classSrc = classSrc.Remove(classSrc.Length - 1); // Delete the last comma + classSrc += " )\n\t{\n"; + classSrc += "\t\tObject[] args = new Object[" + com.Arguments.Count.ToString() + "];\n"; + int i = 0; + foreach (KeyValuePair arg in com.Arguments) + { + classSrc += "\t\targs[" + i.ToString() + "] = " + Util.Md5Hash(arg.Key) + " " + ";\n"; + i++; + } + classSrc += "\t\tGetCommander(\"" + m_name + "\").Run(\"" + com.Name + "\", args);\n"; + classSrc += "\t}\n"; + } + classSrc += "}\n"; + + return classSrc; + } + + /// + /// Runs a specified function with attached arguments + /// *** DO NOT CALL DIRECTLY. *** + /// Call ProcessConsoleCommand instead if handling human input. + /// + /// The function name to call + /// The function parameters + public void Run(string function, object[] args) + { + m_commands[function].Run(args); + } + + public void ProcessConsoleCommand(string function, string[] args) + { + if (m_commands.ContainsKey(function)) + { + if (args.Length > 0 && args[0] == "help") + { + m_commands[function].ShowConsoleHelp(); + } + else + { + m_commands[function].Run(args); + } + } + else + { + if (function == "api") + { + m_log.Info(GenerateRuntimeAPI()); + } + else + { + if (function != "help") + Console.WriteLine("ERROR: Invalid command - No such command exists"); + + Console.Write(Help); + } + } + } + + #endregion + + private string EscapeRuntimeAPICommand(string command) + { + command = command.Replace('-', '_'); + StringBuilder tmp = new StringBuilder(command); + tmp[0] = tmp[0].ToString().ToUpper().ToCharArray()[0]; + + return tmp.ToString(); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneAssetService.cs b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneAssetService.cs new file mode 100644 index 0000000..13efe6b --- /dev/null +++ b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneAssetService.cs @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2008, Contributors. All rights reserved. + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Organizations nor the names of Individual + * 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 System.Collections.Generic; +using System.Net; +using System.Reflection; + +using log4net; +using Nini.Config; + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Grid.AssetServer; +using OpenSim.Data; + +namespace OpenSim.Region.CoreModules.Hypergrid +{ + public class HGStandaloneAssetService : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static bool initialized = false; + private static bool enabled = false; + + Scene m_scene; + //AssetService m_assetService; + + #region IRegionModule interface + + public void Initialise(Scene scene, IConfigSource config) + { + if (!initialized) + { + initialized = true; + m_scene = scene; + + // This module is only on for standalones in hypergrid mode + enabled = !config.Configs["Startup"].GetBoolean("gridmode", true) && config.Configs["Startup"].GetBoolean("hypergrid", false); + } + } + + public void PostInitialise() + { + if (enabled) + { + m_log.Info("[HGStandaloneAssetService]: Starting..."); + + //m_assetService = new AssetService(m_scene); + new AssetService(m_scene); + } + } + + public void Close() + { + } + + public string Name + { + get { return "HGStandaloneAssetService"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + } + + public class AssetService + { + private IUserService m_userService; + private bool m_doLookup = false; + + public bool DoLookup + { + get { return m_doLookup; } + set { m_doLookup = value; } + } + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public AssetService(Scene m_scene) + { + AddHttpHandlers(m_scene); + m_userService = m_scene.CommsManager.UserService; + } + + protected void AddHttpHandlers(Scene m_scene) + { + IAssetDataPlugin m_assetProvider = ((AssetServerBase)m_scene.AssetCache.AssetServer).AssetProviderPlugin; + + BaseHttpServer httpServer = m_scene.CommsManager.HttpServer; + httpServer.AddStreamHandler(new GetAssetStreamHandler(m_assetProvider)); + httpServer.AddStreamHandler(new PostAssetStreamHandler(m_assetProvider)); + + } + + ///// + ///// Check that the source of an inventory request is one that we trust. + ///// + ///// + ///// + //public bool CheckTrustSource(IPEndPoint peer) + //{ + // if (m_doLookup) + // { + // m_log.InfoFormat("[GRID AGENT INVENTORY]: Checking trusted source {0}", peer); + // UriBuilder ub = new UriBuilder(m_userserver_url); + // IPAddress[] uaddrs = Dns.GetHostAddresses(ub.Host); + // foreach (IPAddress uaddr in uaddrs) + // { + // if (uaddr.Equals(peer.Address)) + // { + // return true; + // } + // } + + // m_log.WarnFormat( + // "[GRID AGENT INVENTORY]: Rejecting request since source {0} was not in the list of trusted sources", + // peer); + + // return false; + // } + // else + // { + // return true; + // } + //} + + /// + /// Check that the source of an inventory request for a particular agent is a current session belonging to + /// that agent. + /// + /// + /// + /// + public bool CheckAuthSession(string session_id, string avatar_id) + { + if (m_doLookup) + { + m_log.InfoFormat("[HGStandaloneInvService]: checking authed session {0} {1}", session_id, avatar_id); + UUID userID = UUID.Zero; + UUID sessionID = UUID.Zero; + UUID.TryParse(avatar_id, out userID); + UUID.TryParse(session_id, out sessionID); + if (userID.Equals(UUID.Zero) || sessionID.Equals(UUID.Zero)) + { + m_log.Info("[HGStandaloneInvService]: Invalid user or session id " + avatar_id + "; " + session_id); + return false; + } + UserProfileData userProfile = m_userService.GetUserProfile(userID); + if (userProfile != null && userProfile.CurrentAgent != null && + userProfile.CurrentAgent.SessionID == sessionID) + { + m_log.Info("[HGStandaloneInvService]: user is logged in and session is valid. Authorizing access."); + return true; + } + + m_log.Warn("[HGStandaloneInvService]: unknown user or session_id, request rejected"); + return false; + } + else + { + return true; + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs new file mode 100644 index 0000000..dfc859e --- /dev/null +++ b/OpenSim/Region/CoreModules/Hypergrid/HGStandaloneInventoryService.cs @@ -0,0 +1,314 @@ +/** + * Copyright (c) 2008, Contributors. All rights reserved. + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Organizations nor the names of Individual + * 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 System.Collections.Generic; +using System.Net; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Hypergrid +{ + public class HGStandaloneInventoryService : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static bool initialized = false; + private static bool enabled = false; + + Scene m_scene; + //InventoryService m_inventoryService; + + #region IRegionModule interface + + public void Initialise(Scene scene, IConfigSource config) + { + if (!initialized) + { + initialized = true; + m_scene = scene; + + // This module is only on for standalones + enabled = !config.Configs["Startup"].GetBoolean("gridmode", true) && config.Configs["Startup"].GetBoolean("hypergrid", false); + } + } + + public void PostInitialise() + { + if (enabled) + { + m_log.Info("[HGStandaloneInvService]: Starting..."); + //m_inventoryService = new InventoryService(m_scene); + new InventoryService(m_scene); + } + } + + public void Close() + { + } + + public string Name + { + get { return "HGStandaloneInventoryService"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + } + + public class InventoryService + { + private static readonly ILog m_log + = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private InventoryServiceBase m_inventoryService; + private IUserService m_userService; + private bool m_doLookup = false; + + public bool DoLookup + { + get { return m_doLookup; } + set { m_doLookup = value; } + } + + public InventoryService(Scene m_scene) + { + m_inventoryService = (InventoryServiceBase)m_scene.CommsManager.SecureInventoryService; + m_userService = m_scene.CommsManager.UserService; + AddHttpHandlers(m_scene); + } + + protected void AddHttpHandlers(Scene m_scene) + { + BaseHttpServer httpServer = m_scene.CommsManager.HttpServer; + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/GetInventory/", GetUserInventory, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/NewFolder/", m_inventoryService.AddFolder, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/UpdateFolder/", m_inventoryService.UpdateFolder, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/MoveFolder/", m_inventoryService.MoveFolder, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/PurgeFolder/", m_inventoryService.PurgeFolder, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/NewItem/", m_inventoryService.AddItem, CheckAuthSession)); + + httpServer.AddStreamHandler( + new RestDeserialiseSecureHandler( + "POST", "/DeleteItem/", m_inventoryService.DeleteItem, CheckAuthSession)); + + //// WARNING: Root folders no longer just delivers the root and immediate child folders (e.g + //// system folders such as Objects, Textures), but it now returns the entire inventory skeleton. + //// It would have been better to rename this request, but complexities in the BaseHttpServer + //// (e.g. any http request not found is automatically treated as an xmlrpc request) make it easier + //// to do this for now. + //m_scene.AddStreamHandler( + // new RestDeserialiseTrustedHandler> + // ("POST", "/RootFolders/", GetInventorySkeleton, CheckTrustSource)); + + //// for persistent active gestures + //m_scene.AddStreamHandler( + // new RestDeserialiseTrustedHandler> + // ("POST", "/ActiveGestures/", GetActiveGestures, CheckTrustSource)); + } + + + ///// + ///// Check that the source of an inventory request is one that we trust. + ///// + ///// + ///// + //public bool CheckTrustSource(IPEndPoint peer) + //{ + // if (m_doLookup) + // { + // m_log.InfoFormat("[GRID AGENT INVENTORY]: Checking trusted source {0}", peer); + // UriBuilder ub = new UriBuilder(m_userserver_url); + // IPAddress[] uaddrs = Dns.GetHostAddresses(ub.Host); + // foreach (IPAddress uaddr in uaddrs) + // { + // if (uaddr.Equals(peer.Address)) + // { + // return true; + // } + // } + + // m_log.WarnFormat( + // "[GRID AGENT INVENTORY]: Rejecting request since source {0} was not in the list of trusted sources", + // peer); + + // return false; + // } + // else + // { + // return true; + // } + //} + + /// + /// Check that the source of an inventory request for a particular agent is a current session belonging to + /// that agent. + /// + /// + /// + /// + public bool CheckAuthSession(string session_id, string avatar_id) + { + if (m_doLookup) + { + m_log.InfoFormat("[HGStandaloneInvService]: checking authed session {0} {1}", session_id, avatar_id); + UUID userID = UUID.Zero; + UUID sessionID = UUID.Zero; + UUID.TryParse(avatar_id, out userID); + UUID.TryParse(session_id, out sessionID); + if (userID.Equals(UUID.Zero) || sessionID.Equals(UUID.Zero)) + { + m_log.Info("[HGStandaloneInvService]: Invalid user or session id " + avatar_id + "; " + session_id); + return false; + } + UserProfileData userProfile = m_userService.GetUserProfile(userID); + if (userProfile != null && userProfile.CurrentAgent != null && + userProfile.CurrentAgent.SessionID == sessionID) + { + m_log.Info("[HGStandaloneInvService]: user is logged in and session is valid. Authorizing access."); + return true; + } + + m_log.Warn("[HGStandaloneInvService]: unknown user or session_id, request rejected"); + return false; + } + else + { + return true; + } + } + + + /// + /// Return a user's entire inventory + /// + /// + /// The user's inventory. If an inventory cannot be found then an empty collection is returned. + public InventoryCollection GetUserInventory(Guid rawUserID) + { + UUID userID = new UUID(rawUserID); + + m_log.Info("[HGStandaloneInvService]: Processing request for inventory of " + userID); + + // Uncomment me to simulate a slow responding inventory server + //Thread.Sleep(16000); + + InventoryCollection invCollection = new InventoryCollection(); + + List allFolders = ((InventoryServiceBase)m_inventoryService).GetInventorySkeleton(userID); + + if (null == allFolders) + { + m_log.WarnFormat("[HGStandaloneInvService]: No inventory found for user {0}", rawUserID); + + return invCollection; + } + + List allItems = new List(); + + foreach (InventoryFolderBase folder in allFolders) + { + List items = ((InventoryServiceBase)m_inventoryService).RequestFolderItems(folder.ID); + + if (items != null) + { + allItems.InsertRange(0, items); + } + } + + invCollection.UserID = userID; + invCollection.Folders = allFolders; + invCollection.Items = allItems; + + // foreach (InventoryFolderBase folder in invCollection.Folders) + // { + // m_log.DebugFormat("[GRID AGENT INVENTORY]: Sending back folder {0} {1}", folder.Name, folder.ID); + // } + // + // foreach (InventoryItemBase item in invCollection.Items) + // { + // m_log.DebugFormat("[GRID AGENT INVENTORY]: Sending back item {0} {1}, folder {2}", item.Name, item.ID, item.Folder); + // } + + m_log.InfoFormat( + "[HGStandaloneInvService]: Sending back inventory response to user {0} containing {1} folders and {2} items", + invCollection.UserID, invCollection.Folders.Count, invCollection.Items.Count); + + return invCollection; + } + + /// + /// Guid to UUID wrapper for same name IInventoryServices method + /// + /// + /// + public List GetInventorySkeleton(Guid rawUserID) + { + UUID userID = new UUID(rawUserID); + return ((InventoryServiceBase)m_inventoryService).GetInventorySkeleton(userID); + } + + public List GetActiveGestures(Guid rawUserID) + { + UUID userID = new UUID(rawUserID); + + m_log.InfoFormat("[HGStandaloneInvService]: fetching active gestures for user {0}", userID); + + return ((InventoryServiceBase)m_inventoryService).GetActiveGestures(userID); + } + } +} diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs new file mode 100644 index 0000000..5540cc3 --- /dev/null +++ b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2008, Contributors. All rights reserved. + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Organizations nor the names of Individual + * 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 System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Net; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using OpenMetaverse.StructuredData; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; + +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Types; +using OpenSim.Region.CoreModules.World.WorldMap; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +using OSD = OpenMetaverse.StructuredData.OSD; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; + +namespace OpenSim.Region.CoreModules.Hypergrid +{ + public class HGWorldMapModule : WorldMapModule, IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + #region IRegionModule Members + + public override void Initialise(Scene scene, IConfigSource config) + { + IConfig startupConfig = config.Configs["Startup"]; + if (startupConfig.GetString("WorldMapModule", "WorldMap") == "HGWorldMap") + m_Enabled = true; + + if (!m_Enabled) + return; + m_log.Info("[HGMap] Initializing..."); + m_scene = scene; + } + + + public override string Name + { + get { return "HGWorldMap"; } + } + + + #endregion + + /// + /// Requests map blocks in area of minX, maxX, minY, MaxY in world cordinates + /// + /// + /// + /// + /// + public override void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) + { + // + // WARNING!!! COPY & PASTE FROM SUPERCLASS + // The only difference is at the very end + // + + m_log.Info("[HGMap]: Request map blocks " + minX + "-" + maxX + " " + minY + "-" + maxY); + + //m_scene.ForEachScenePresence(delegate (ScenePresence sp) { + // if (!sp.IsChildAgent && sp.UUID == remoteClient.AgentId) + // { + // Console.WriteLine("XXX Root agent"); + // DoRequestMapBlocks(remoteClient, minX, minY, maxX, maxY, flag); + // } + //}; + + List mapBlocks; + if ((flag & 0x10000) != 0) // user clicked on the map a tile that isn't visible + { + List response = new List(); + + // this should return one mapblock at most. But make sure: Look whether the one we requested is in there + mapBlocks = m_scene.SceneGridService.RequestNeighbourMapBlocks(minX, minY, maxX, maxY); + if (mapBlocks != null) + { + foreach (MapBlockData block in mapBlocks) + { + if (block.X == minX && block.Y == minY) + { + // found it => add it to response + response.Add(block); + break; + } + } + } + response = mapBlocks; + if (response.Count == 0) + { + // response still empty => couldn't find the map-tile the user clicked on => tell the client + MapBlockData block = new MapBlockData(); + block.X = (ushort)minX; + block.Y = (ushort)minY; + block.Access = 254; // == not there + response.Add(block); + } + remoteClient.SendMapBlock(response, 0); + } + else + { + // normal mapblock request. Use the provided values + mapBlocks = m_scene.SceneGridService.RequestNeighbourMapBlocks(minX - 4, minY - 4, maxX + 4, maxY + 4); + + // Different from super + FillInMap(mapBlocks, minX, minY, maxX, maxY); + // + + remoteClient.SendMapBlock(mapBlocks, flag); + } + } + + + private void FillInMap(List mapBlocks, int minX, int minY, int maxX, int maxY) + { + for (int x = minX; x <= maxX; x++) + for (int y = minY; y <= maxY; y++) + { + MapBlockData mblock = mapBlocks.Find(delegate(MapBlockData mb) { return ((mb.X == x) && (mb.Y == y)); }); + if (mblock == null) + { + mblock = new MapBlockData(); + mblock.X = (ushort)x; + mblock.Y = (ushort)y; + mblock.Name = ""; + mblock.Access = 254; // not here??? + mblock.MapImageId = UUID.Zero; + mapBlocks.Add(mblock); + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/InterGrid/OpenGridProtocolModule.cs b/OpenSim/Region/CoreModules/InterGrid/OpenGridProtocolModule.cs new file mode 100644 index 0000000..2e1675b --- /dev/null +++ b/OpenSim/Region/CoreModules/InterGrid/OpenGridProtocolModule.cs @@ -0,0 +1,1273 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Reflection; +using System.Threading; +using System.Web; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; +using OpenMetaverse.Packets; + +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OSD = OpenMetaverse.StructuredData.OSD; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; + +namespace OpenSim.Region.CoreModules.InterGrid +{ + public struct OGPState + { + public string first_name; + public string last_name; + public UUID agent_id; + public UUID local_agent_id; + public UUID region_id; + public uint circuit_code; + public UUID secure_session_id; + public UUID session_id; + public bool agent_access; + public string sim_access; + public uint god_level; + public bool god_overide; + public bool identified; + public bool transacted; + public bool age_verified; + public bool allow_redirect; + public int limited_to_estate; + public string inventory_host; + public bool src_can_see_mainland; + public int src_estate_id; + public int src_version; + public int src_parent_estate_id; + public bool visible_to_parent; + public string teleported_into_region; + } + + public class OpenGridProtocolModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private List m_scene = new List(); + + private Dictionary CapsLoginID = new Dictionary(); + private Dictionary m_OGPState = new Dictionary(); + private Dictionary m_loginToRegionState = new Dictionary(); + + + private string LastNameSuffix = "_EXTERNAL"; + private string FirstNamePrefix = ""; + private string httpsCN = ""; + private bool httpSSL = false; + private uint httpsslport = 0; + private bool GridMode = false; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + bool enabled = false; + IConfig cfg = null; + IConfig httpcfg = null; + IConfig startupcfg = null; + try + { + cfg = config.Configs["OpenGridProtocol"]; + } catch (NullReferenceException) + { + enabled = false; + } + + try + { + httpcfg = config.Configs["Network"]; + } + catch (NullReferenceException) + { + + } + try + { + startupcfg = config.Configs["Startup"]; + } + catch (NullReferenceException) + { + + } + + if (startupcfg != null) + { + GridMode = enabled = startupcfg.GetBoolean("gridmode", false); + } + + if (cfg != null) + { + enabled = cfg.GetBoolean("ogp_enabled", false); + LastNameSuffix = cfg.GetString("ogp_lastname_suffix", "_EXTERNAL"); + FirstNamePrefix = cfg.GetString("ogp_firstname_prefix", ""); + if (enabled) + { + m_log.Warn("[OGP]: Open Grid Protocol is on, Listening for Clients on /agent/"); + lock (m_scene) + { + if (m_scene.Count == 0) + { + scene.CommsManager.HttpServer.AddLLSDHandler("/agent/", ProcessAgentDomainMessage); + scene.CommsManager.HttpServer.AddLLSDHandler("/", ProcessRegionDomainSeed); + try + { + ServicePointManager.ServerCertificateValidationCallback += customXertificateValidation; + } + catch (NotImplementedException) + { + try + { +#pragma warning disable 0612, 0618 + // Mono does not implement the ServicePointManager.ServerCertificateValidationCallback yet! Don't remove this! + ServicePointManager.CertificatePolicy = new MonoCert(); +#pragma warning restore 0612, 0618 + } + catch (Exception) + { + m_log.Error("[OGP]: Certificate validation handler change not supported. You may get ssl certificate validation errors teleporting from your region to some SSL regions."); + } + } + + } + // can't pick the region 'agent' because it would conflict with our agent domain handler + // a zero length region name would conflict with are base region seed cap + if (!SceneListDuplicateCheck(scene.RegionInfo.RegionName) && scene.RegionInfo.RegionName.ToLower() != "agent" && scene.RegionInfo.RegionName.Length > 0) + { + scene.CommsManager.HttpServer.AddLLSDHandler( + "/" + HttpUtility.UrlPathEncode(scene.RegionInfo.RegionName.ToLower()), + ProcessRegionDomainSeed); + } + + if (!m_scene.Contains(scene)) + m_scene.Add(scene); + } + } + } + lock (m_scene) + { + if (m_scene.Count == 1) + { + if (httpcfg != null) + { + httpSSL = httpcfg.GetBoolean("http_listener_ssl", false); + httpsCN = httpcfg.GetString("http_listener_cn", scene.RegionInfo.ExternalHostName); + if (httpsCN.Length == 0) + httpsCN = scene.RegionInfo.ExternalHostName; + httpsslport = (uint)httpcfg.GetInt("http_listener_sslport",((int)scene.RegionInfo.HttpPort + 1)); + } + } + } + } + + public void PostInitialise() + { + } + + public void Close() + { + //scene.EventManager.OnAvatarEnteringNewParcel -= AvatarEnteringParcel; + } + + public string Name + { + get { return "OpenGridProtocolModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + public OSD ProcessRegionDomainSeed(string path, OSD request, string endpoint) + { + string[] pathSegments = path.Split('/'); + + if (pathSegments.Length <= 1) + { + return GenerateNoHandlerMessage(); + + } + + return GenerateRezAvatarRequestMessage(pathSegments[1]); + + + + //m_log.InfoFormat("[OGP]: path {0}, segments {1} segment[1] {2} Last segment {3}", + // path, pathSegments.Length, pathSegments[1], pathSegments[pathSegments.Length - 1]); + //return new OSDMap(); + + } + + public OSD ProcessAgentDomainMessage(string path, OSD request, string endpoint) + { + // /agent/* + + string[] pathSegments = path.Split('/'); + if (pathSegments.Length <= 1) + { + return GenerateNoHandlerMessage(); + + } + if (pathSegments[0].Length == 0 && pathSegments[1].Length == 0) + { + return GenerateRezAvatarRequestMessage(""); + } + m_log.InfoFormat("[OGP]: path {0}, segments {1} segment[1] {2} Last segment {3}", + path, pathSegments.Length, pathSegments[1], pathSegments[pathSegments.Length - 1]); + + switch (pathSegments[pathSegments.Length - 1]) + { + case "rez_avatar": + return RezAvatarMethod(path, request); + //break; + case "derez_avatar": + return DerezAvatarMethod(path, request); + //break; + + } + if (path.Length < 2) + { + return GenerateNoHandlerMessage(); + } + + switch (pathSegments[pathSegments.Length - 2] + "/" + pathSegments[pathSegments.Length - 1]) + { + case "rez_avatar/rez": + return RezAvatarMethod(path, request); + //break; + case "rez_avatar/request": + return RequestRezAvatarMethod(path, request); + case "rez_avatar/place": + return RequestRezAvatarMethod(path, request); + case "rez_avatar/derez": + return DerezAvatarMethod(path, request); + //break; + default: + return GenerateNoHandlerMessage(); + } + //return null; + } + + private OSD GenerateRezAvatarRequestMessage(string regionname) + { + Scene region = null; + bool usedroot = false; + + if (regionname.Length == 0) + { + region = GetRootScene(); + usedroot = true; + } + else + { + region = GetScene(HttpUtility.UrlDecode(regionname).ToLower()); + } + + // this shouldn't happen since we don't listen for a region that is down.. but + // it might if the region was taken down or is in the middle of restarting + + if (region == null) + { + region = GetRootScene(); + usedroot = true; + } + + UUID statekeeper = UUID.Random(); + + + + + RegionInfo reg = region.RegionInfo; + + OSDMap responseMap = new OSDMap(); + string rezHttpProtocol = "http://"; + //string regionCapsHttpProtocol = "http://"; + string httpaddr = reg.ExternalHostName; + string urlport = reg.HttpPort.ToString(); + string requestpath = "/agent/" + statekeeper + "/rez_avatar/request"; + + if (!usedroot) + { + lock (m_loginToRegionState) + { + if (!m_loginToRegionState.ContainsKey(requestpath)) + { + m_loginToRegionState.Add(requestpath, region.RegionInfo.RegionName.ToLower()); + } + } + } + + if (httpSSL) + { + rezHttpProtocol = "https://"; + //regionCapsHttpProtocol = "https://"; + urlport = httpsslport.ToString(); + + if (httpsCN.Length > 0) + httpaddr = httpsCN; + } + + responseMap["connect"] = OSD.FromBoolean(true); + OSDMap capabilitiesMap = new OSDMap(); + capabilitiesMap["rez_avatar/request"] = OSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + requestpath); + responseMap["capabilities"] = capabilitiesMap; + + return responseMap; + } + + // Using OpenSim.Framework.Communications.Capabilities.Caps here one time.. + // so the long name is probably better then a using statement + public void OnRegisterCaps(UUID agentID, OpenSim.Framework.Communications.Capabilities.Caps caps) + { + /* If we ever want to register our own caps here.... + * + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("CAPNAME", + new RestStreamHandler("POST", capsBase + CAPSPOSTFIX!, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return METHODHANDLER(request, path, param, + agentID, caps); + })); + + * + */ + } + + public OSD RequestRezAvatarMethod(string path, OSD request) + { + //System.Console.WriteLine("[REQUESTREZAVATAR]: " + request.ToString()); + + OSDMap requestMap = (OSDMap)request; + + + Scene homeScene = null; + + lock (m_loginToRegionState) + { + if (m_loginToRegionState.ContainsKey(path)) + { + homeScene = GetScene(m_loginToRegionState[path]); + m_loginToRegionState.Remove(path); + + if (homeScene == null) + homeScene = GetRootScene(); + } + else + { + homeScene = GetRootScene(); + } + } + + // Homescene is still null, we must have no regions that are up + if (homeScene == null) + return GenerateNoHandlerMessage(); + + RegionInfo reg = homeScene.RegionInfo; + ulong regionhandle = GetOSCompatibleRegionHandle(reg); + //string RegionURI = reg.ServerURI; + //int RegionPort = (int)reg.HttpPort; + + UUID RemoteAgentID = requestMap["agent_id"].AsUUID(); + + // will be used in the future. The client always connects with the aditi agentid currently + UUID LocalAgentID = RemoteAgentID; + + string FirstName = requestMap["first_name"].AsString(); + string LastName = requestMap["last_name"].AsString(); + + FirstName = FirstNamePrefix + FirstName; + LastName = LastName + LastNameSuffix; + + OGPState userState = GetOGPState(LocalAgentID); + + userState.first_name = requestMap["first_name"].AsString(); + userState.last_name = requestMap["last_name"].AsString(); + userState.age_verified = requestMap["age_verified"].AsBoolean(); + userState.transacted = requestMap["transacted"].AsBoolean(); + userState.agent_access = requestMap["agent_access"].AsBoolean(); + userState.allow_redirect = requestMap["allow_redirect"].AsBoolean(); + userState.identified = requestMap["identified"].AsBoolean(); + userState.god_level = (uint)requestMap["god_level"].AsInteger(); + userState.sim_access = requestMap["sim_access"].AsString(); + userState.agent_id = RemoteAgentID; + userState.limited_to_estate = requestMap["limited_to_estate"].AsInteger(); + userState.src_can_see_mainland = requestMap["src_can_see_mainland"].AsBoolean(); + userState.src_estate_id = requestMap["src_estate_id"].AsInteger(); + userState.local_agent_id = LocalAgentID; + userState.teleported_into_region = reg.RegionName.ToLower(); + + UpdateOGPState(LocalAgentID, userState); + + OSDMap responseMap = new OSDMap(); + + if (RemoteAgentID == UUID.Zero) + { + responseMap["connect"] = OSD.FromBoolean(false); + responseMap["message"] = OSD.FromString("No agent ID was specified in rez_avatar/request"); + m_log.Error("[OGP]: rez_avatar/request failed because no avatar UUID was provided in the request body"); + return responseMap; + } + + responseMap["sim_host"] = OSD.FromString(reg.ExternalHostName); + + // DEPRECIATED + responseMap["sim_ip"] = OSD.FromString(Util.GetHostFromDNS(reg.ExternalHostName).ToString()); + + responseMap["connect"] = OSD.FromBoolean(true); + responseMap["sim_port"] = OSD.FromInteger(reg.InternalEndPoint.Port); + responseMap["region_x"] = OSD.FromInteger(reg.RegionLocX * (uint)Constants.RegionSize); // LLX + responseMap["region_y"] = OSD.FromInteger(reg.RegionLocY * (uint)Constants.RegionSize); // LLY + responseMap["region_id"] = OSD.FromUUID(reg.originRegionID); + responseMap["sim_access"] = OSD.FromString((reg.RegionSettings.Maturity == 1) ? "Mature" : "PG"); + + // Generate a dummy agent for the user so we can get back a CAPS path + AgentCircuitData agentData = new AgentCircuitData(); + agentData.AgentID = LocalAgentID; + agentData.BaseFolder = UUID.Zero; + agentData.CapsPath = CapsUtil.GetRandomCapsObjectPath(); + agentData.child = false; + agentData.circuitcode = (uint)(Util.RandomClass.Next()); + agentData.firstname = FirstName; + agentData.lastname = LastName; + agentData.SecureSessionID = UUID.Random(); + agentData.SessionID = UUID.Random(); + agentData.startpos = new Vector3(128f, 128f, 100f); + + // Pre-Fill our region cache with information on the agent. + UserAgentData useragent = new UserAgentData(); + useragent.AgentIP = "unknown"; + useragent.AgentOnline = true; + useragent.AgentPort = (uint)0; + useragent.Handle = regionhandle; + useragent.InitialRegion = reg.originRegionID; + useragent.LoginTime = Util.UnixTimeSinceEpoch(); + useragent.LogoutTime = 0; + useragent.Position = agentData.startpos; + useragent.Region = reg.originRegionID; + useragent.SecureSessionID = agentData.SecureSessionID; + useragent.SessionID = agentData.SessionID; + + UserProfileData userProfile = new UserProfileData(); + userProfile.AboutText = "OGP User"; + userProfile.CanDoMask = (uint)0; + userProfile.Created = Util.UnixTimeSinceEpoch(); + userProfile.CurrentAgent = useragent; + userProfile.CustomType = "OGP"; + userProfile.FirstLifeAboutText = "I'm testing OpenGrid Protocol"; + userProfile.FirstLifeImage = UUID.Zero; + userProfile.FirstName = agentData.firstname; + userProfile.GodLevel = 0; + userProfile.HomeLocation = agentData.startpos; + userProfile.HomeLocationX = agentData.startpos.X; + userProfile.HomeLocationY = agentData.startpos.Y; + userProfile.HomeLocationZ = agentData.startpos.Z; + userProfile.HomeLookAt = Vector3.Zero; + userProfile.HomeLookAtX = userProfile.HomeLookAt.X; + userProfile.HomeLookAtY = userProfile.HomeLookAt.Y; + userProfile.HomeLookAtZ = userProfile.HomeLookAt.Z; + userProfile.HomeRegion = reg.RegionHandle; + userProfile.HomeRegionID = reg.originRegionID; + userProfile.HomeRegionX = reg.RegionLocX; + userProfile.HomeRegionY = reg.RegionLocY; + userProfile.ID = agentData.AgentID; + userProfile.Image = UUID.Zero; + userProfile.LastLogin = Util.UnixTimeSinceEpoch(); + userProfile.Partner = UUID.Zero; + userProfile.PasswordHash = "$1$"; + userProfile.PasswordSalt = ""; + userProfile.RootInventoryFolderID = UUID.Zero; + userProfile.SurName = agentData.lastname; + userProfile.UserAssetURI = homeScene.CommsManager.NetworkServersInfo.AssetURL; + userProfile.UserFlags = 0; + userProfile.UserInventoryURI = homeScene.CommsManager.NetworkServersInfo.InventoryURL; + userProfile.WantDoMask = 0; + userProfile.WebLoginKey = UUID.Random(); + + // Do caps registration + // get seed capagentData.firstname = FirstName;agentData.lastname = LastName; + if (homeScene.CommsManager.UserService.GetUserProfile(agentData.AgentID) == null && !GridMode) + { + homeScene.CommsManager.UserAdminService.AddUser( + agentData.firstname, agentData.lastname, CreateRandomStr(7), "", + homeScene.RegionInfo.RegionLocX, homeScene.RegionInfo.RegionLocY, agentData.AgentID); + + UserProfileData userProfile2 = homeScene.CommsManager.UserService.GetUserProfile(agentData.AgentID); + if (userProfile2 != null) + { + userProfile = userProfile2; + userProfile.AboutText = "OGP USER"; + userProfile.FirstLifeAboutText = "OGP USER"; + homeScene.CommsManager.UserService.UpdateUserProfile(userProfile); + } + } + + // Stick our data in the cache so the region will know something about us + homeScene.CommsManager.UserProfileCacheService.PreloadUserCache(agentData.AgentID, userProfile); + + // Call 'new user' event handler + homeScene.NewUserConnection(agentData); + + //string raCap = string.Empty; + + UUID AvatarRezCapUUID = LocalAgentID; + string rezAvatarPath = "/agent/" + AvatarRezCapUUID + "/rez_avatar/rez"; + string derezAvatarPath = "/agent/" + AvatarRezCapUUID + "/rez_avatar/derez"; + // Get a reference to the user's cap so we can pull out the Caps Object Path + OpenSim.Framework.Communications.Capabilities.Caps userCap + = homeScene.CapsModule.GetCapsHandlerForUser(agentData.AgentID); + + string rezHttpProtocol = "http://"; + string regionCapsHttpProtocol = "http://"; + string httpaddr = reg.ExternalHostName; + string urlport = reg.HttpPort.ToString(); + + if (httpSSL) + { + rezHttpProtocol = "https://"; + regionCapsHttpProtocol = "https://"; + urlport = httpsslport.ToString(); + + if (httpsCN.Length > 0) + httpaddr = httpsCN; + } + + // DEPRECIATED + responseMap["seed_capability"] + = OSD.FromString( + regionCapsHttpProtocol + httpaddr + ":" + reg.HttpPort + CapsUtil.GetCapsSeedPath(userCap.CapsObjectPath)); + + // REPLACEMENT + responseMap["region_seed_capability"] + = OSD.FromString( + regionCapsHttpProtocol + httpaddr + ":" + reg.HttpPort + CapsUtil.GetCapsSeedPath(userCap.CapsObjectPath)); + + responseMap["rez_avatar"] = OSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + rezAvatarPath); + responseMap["rez_avatar/rez"] = OSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + rezAvatarPath); + responseMap["rez_avatar/derez"] = OSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + derezAvatarPath); + + // Add the user to the list of CAPS that are outstanding. + // well allow the caps hosts in this dictionary + lock (CapsLoginID) + { + if (CapsLoginID.ContainsKey(rezAvatarPath)) + { + CapsLoginID[rezAvatarPath] = agentData; + + // This is a joke, if you didn't notice... It's so unlikely to happen, that I'll print this message if it does occur! + m_log.Error("[OGP]: Holy anomoly batman! Caps path already existed! All the UUID Duplication worries were founded!"); + } + else + { + CapsLoginID.Add(rezAvatarPath, agentData); + } + } + + //System.Console.WriteLine("Response:" + responseMap.ToString()); + return responseMap; + } + + public OSD RezAvatarMethod(string path, OSD request) + { + m_log.WarnFormat("[REZAVATAR]: {0}", request.ToString()); + + OSDMap responseMap = new OSDMap(); + + AgentCircuitData userData = null; + + // Only people we've issued a cap can go further + if (TryGetAgentCircuitData(path,out userData)) + { + OSDMap requestMap = (OSDMap)request; + + // take these values to start. There's a few more + UUID SecureSessionID=requestMap["secure_session_id"].AsUUID(); + UUID SessionID = requestMap["session_id"].AsUUID(); + int circuitcode = requestMap["circuit_code"].AsInteger(); + OSDArray Parameter = new OSDArray(); + if (requestMap.ContainsKey("parameter")) + { + Parameter = (OSDArray)requestMap["parameter"]; + } + + //int version = 1; + int estateID = 1; + int parentEstateID = 1; + UUID regionID = UUID.Zero; + bool visibleToParent = true; + + for (int i = 0; i < Parameter.Count; i++) + { + OSDMap item = (OSDMap)Parameter[i]; +// if (item.ContainsKey("version")) +// { +// version = item["version"].AsInteger(); +// } + if (item.ContainsKey("estate_id")) + { + estateID = item["estate_id"].AsInteger(); + } + if (item.ContainsKey("parent_estate_id")) + { + parentEstateID = item["parent_estate_id"].AsInteger(); + + } + if (item.ContainsKey("region_id")) + { + regionID = item["region_id"].AsUUID(); + + } + if (item.ContainsKey("visible_to_parent")) + { + visibleToParent = item["visible_to_parent"].AsBoolean(); + } + } + //Update our Circuit data with the real values + userData.SecureSessionID = SecureSessionID; + userData.SessionID = SessionID; + + OGPState userState = GetOGPState(userData.AgentID); + + // Locate a home scene suitable for the user. + Scene homeScene = null; + + homeScene = GetScene(userState.teleported_into_region); + + if (homeScene == null) + homeScene = GetRootScene(); + + if (homeScene != null) + { + // Get a referenceokay - to their Cap object so we can pull out the capobjectroot + OpenSim.Framework.Communications.Capabilities.Caps userCap + = homeScene.CapsModule.GetCapsHandlerForUser(userData.AgentID); + + //Update the circuit data in the region so this user is authorized + homeScene.UpdateCircuitData(userData); + homeScene.ChangeCircuitCode(userData.circuitcode,(uint)circuitcode); + + // Load state + + + // Keep state changes + userState.first_name = requestMap["first_name"].AsString(); + userState.secure_session_id = requestMap["secure_session_id"].AsUUID(); + userState.age_verified = requestMap["age_verified"].AsBoolean(); + userState.region_id = homeScene.RegionInfo.originRegionID; // replace 0000000 with our regionid + userState.transacted = requestMap["transacted"].AsBoolean(); + userState.agent_access = requestMap["agent_access"].AsBoolean(); + userState.inventory_host = requestMap["inventory_host"].AsString(); + userState.identified = requestMap["identified"].AsBoolean(); + userState.session_id = requestMap["session_id"].AsUUID(); + userState.god_level = (uint)requestMap["god_level"].AsInteger(); + userState.last_name = requestMap["last_name"].AsString(); + userState.god_overide = requestMap["god_override"].AsBoolean(); + userState.circuit_code = (uint)requestMap["circuit_code"].AsInteger(); + userState.limited_to_estate = requestMap["limited_to_estate"].AsInteger(); + userState.src_estate_id = estateID; + userState.region_id = regionID; + userState.src_parent_estate_id = parentEstateID; + userState.visible_to_parent = visibleToParent; + + // Save state changes + UpdateOGPState(userData.AgentID, userState); + + // Get the region information for the home region. + RegionInfo reg = homeScene.RegionInfo; + + // Dummy positional and look at info.. we don't have it. + OSDArray PositionArray = new OSDArray(); + PositionArray.Add(OSD.FromInteger(128)); + PositionArray.Add(OSD.FromInteger(128)); + PositionArray.Add(OSD.FromInteger(40)); + + OSDArray LookAtArray = new OSDArray(); + LookAtArray.Add(OSD.FromInteger(1)); + LookAtArray.Add(OSD.FromInteger(1)); + LookAtArray.Add(OSD.FromInteger(1)); + + // Our region's X and Y position in OpenSimulator space. + uint fooX = reg.RegionLocX; + uint fooY = reg.RegionLocY; + m_log.InfoFormat("[OGP]: region x({0}) region y({1})", fooX, fooY); + m_log.InfoFormat("[OGP]: region http {0} {1}", reg.ServerURI, reg.HttpPort); + m_log.InfoFormat("[OGO]: region UUID {0} ", reg.RegionID); + + // Convert the X and Y position to LL space + responseMap["region_x"] = OSD.FromInteger(fooX * (uint)Constants.RegionSize); // convert it to LL X + responseMap["region_y"] = OSD.FromInteger(fooY * (uint)Constants.RegionSize); // convert it to LL Y + + // Give em a new seed capability + responseMap["seed_capability"] = OSD.FromString("http://" + reg.ExternalHostName + ":" + reg.HttpPort + "/CAPS/" + userCap.CapsObjectPath + "0000/"); + responseMap["region"] = OSD.FromUUID(reg.originRegionID); + responseMap["look_at"] = LookAtArray; + + responseMap["sim_port"] = OSD.FromInteger(reg.InternalEndPoint.Port); + responseMap["sim_host"] = OSD.FromString(reg.ExternalHostName);// + ":" + reg.InternalEndPoint.Port.ToString()); + + // DEPRECIATED + responseMap["sim_ip"] = OSD.FromString(Util.GetHostFromDNS(reg.ExternalHostName).ToString()); + + responseMap["session_id"] = OSD.FromUUID(SessionID); + responseMap["secure_session_id"] = OSD.FromUUID(SecureSessionID); + responseMap["circuit_code"] = OSD.FromInteger(circuitcode); + + responseMap["position"] = PositionArray; + + responseMap["region_id"] = OSD.FromUUID(reg.originRegionID); + + responseMap["sim_access"] = OSD.FromString("Mature"); + + responseMap["connect"] = OSD.FromBoolean(true); + + + + m_log.InfoFormat("[OGP]: host: {0}, IP {1}", responseMap["sim_host"].ToString(), responseMap["sim_ip"].ToString()); + } + } + + return responseMap; + } + + public OSD DerezAvatarMethod(string path, OSD request) + { + m_log.ErrorFormat("DerezPath: {0}, Request: {1}", path, request.ToString()); + + //LLSD llsdResponse = null; + OSDMap responseMap = new OSDMap(); + + string[] PathArray = path.Split('/'); + m_log.InfoFormat("[OGP]: prefix {0}, uuid {1}, suffix {2}", PathArray[1], PathArray[2], PathArray[3]); + string uuidString = PathArray[2]; + m_log.InfoFormat("[OGP]: Request to Derez avatar with UUID {0}", uuidString); + UUID userUUID = UUID.Zero; + if (UUID.TryParse(uuidString, out userUUID)) + { + UUID RemoteID = (UUID)uuidString; + UUID LocalID = RemoteID; + // FIXME: TODO: Routine to map RemoteUUIDs to LocalUUIds + // would be done already.. but the client connects with the Aditi UUID + // regardless over the UDP stack + + OGPState userState = GetOGPState(LocalID); + if (userState.agent_id != UUID.Zero) + { + //OSDMap outboundRequestMap = new OSDMap(); + OSDMap inboundRequestMap = (OSDMap)request; + string rezAvatarString = inboundRequestMap["rez_avatar"].AsString(); + if (rezAvatarString.Length == 0) + { + rezAvatarString = inboundRequestMap["rez_avatar/rez"].AsString(); + } + OSDArray LookAtArray = new OSDArray(); + LookAtArray.Add(OSD.FromInteger(1)); + LookAtArray.Add(OSD.FromInteger(1)); + LookAtArray.Add(OSD.FromInteger(1)); + + OSDArray PositionArray = new OSDArray(); + PositionArray.Add(OSD.FromInteger(128)); + PositionArray.Add(OSD.FromInteger(128)); + PositionArray.Add(OSD.FromInteger(40)); + + OSDArray lookArray = new OSDArray(); + lookArray.Add(OSD.FromInteger(128)); + lookArray.Add(OSD.FromInteger(128)); + lookArray.Add(OSD.FromInteger(40)); + + responseMap["connect"] = OSD.FromBoolean(true);// it's okay to give this user up + responseMap["look_at"] = LookAtArray; + + m_log.WarnFormat("[OGP]: Invoking rez_avatar on host:{0} for avatar: {1} {2}", rezAvatarString, userState.first_name, userState.last_name); + + OSDMap rezResponseMap = invokeRezAvatarCap(responseMap, rezAvatarString,userState); + + // If invoking it returned an error, parse and end + if (rezResponseMap.ContainsKey("connect")) + { + if (rezResponseMap["connect"].AsBoolean() == false) + { + return responseMap; + } + } + + string rezRespSeedCap = ""; + + // DEPRECIATED + if (rezResponseMap.ContainsKey("seed_capability")) + rezRespSeedCap = rezResponseMap["seed_capability"].AsString(); + + // REPLACEMENT + if (rezResponseMap.ContainsKey("region_seed_capability")) + rezRespSeedCap = rezResponseMap["region_seed_capability"].AsString(); + + // REPLACEMENT + if (rezResponseMap.ContainsKey("rez_avatar/rez")) + rezRespSeedCap = rezResponseMap["rez_avatar/rez"].AsString(); + + // DEPRECIATED + string rezRespSim_ip = rezResponseMap["sim_ip"].AsString(); + + string rezRespSim_host = rezResponseMap["sim_host"].AsString(); + + int rrPort = rezResponseMap["sim_port"].AsInteger(); + int rrX = rezResponseMap["region_x"].AsInteger(); + int rrY = rezResponseMap["region_y"].AsInteger(); + m_log.ErrorFormat("X:{0}, Y:{1}", rrX, rrY); + UUID rrRID = rezResponseMap["region_id"].AsUUID(); + OSDArray RezResponsePositionArray = null; + string rrAccess = rezResponseMap["sim_access"].AsString(); + if (rezResponseMap.ContainsKey("position")) + { + RezResponsePositionArray = (OSDArray)rezResponseMap["position"]; + } + // DEPRECIATED + responseMap["seed_capability"] = OSD.FromString(rezRespSeedCap); + + // REPLACEMENT r3 + responseMap["region_seed_capability"] = OSD.FromString(rezRespSeedCap); + + // DEPRECIATED + responseMap["sim_ip"] = OSD.FromString(Util.GetHostFromDNS(rezRespSim_ip).ToString()); + + responseMap["sim_host"] = OSD.FromString(rezRespSim_host); + responseMap["sim_port"] = OSD.FromInteger(rrPort); + responseMap["region_x"] = OSD.FromInteger(rrX ); + responseMap["region_y"] = OSD.FromInteger(rrY ); + responseMap["region_id"] = OSD.FromUUID(rrRID); + responseMap["sim_access"] = OSD.FromString(rrAccess); + + if (RezResponsePositionArray != null) + { + responseMap["position"] = RezResponsePositionArray; + } + responseMap["look_at"] = lookArray; + responseMap["connect"] = OSD.FromBoolean(true); + + ShutdownConnection(LocalID,this); + // PLEASE STOP CHANGING THIS TO an M_LOG, M_LOG DOESN'T WORK ON MULTILINE .TOSTRINGS + System.Console.WriteLine("RESPONSEDEREZ: " + responseMap.ToString()); + return responseMap; + } + else + { + return GenerateNoStateMessage(LocalID); + } + } + else + { + return GenerateNoHandlerMessage(); + } + + //return responseMap; + } + + private OSDMap invokeRezAvatarCap(OSDMap responseMap, string CapAddress, OGPState userState) + { + Scene reg = GetRootScene(); + + WebRequest DeRezRequest = WebRequest.Create(CapAddress); + DeRezRequest.Method = "POST"; + DeRezRequest.ContentType = "application/xml+llsd"; + + OSDMap RAMap = new OSDMap(); + OSDMap AgentParms = new OSDMap(); + OSDMap RegionParms = new OSDMap(); + + OSDArray Parameter = new OSDArray(2); + + OSDMap version = new OSDMap(); + version["version"] = OSD.FromInteger(userState.src_version); + Parameter.Add(version); + + OSDMap SrcData = new OSDMap(); + SrcData["estate_id"] = OSD.FromInteger(reg.RegionInfo.EstateSettings.EstateID); + SrcData["parent_estate_id"] = OSD.FromInteger((reg.RegionInfo.EstateSettings.ParentEstateID == 100 ? 1 : reg.RegionInfo.EstateSettings.ParentEstateID)); + SrcData["region_id"] = OSD.FromUUID(reg.RegionInfo.originRegionID); + SrcData["visible_to_parent"] = OSD.FromBoolean(userState.visible_to_parent); + Parameter.Add(SrcData); + + AgentParms["first_name"] = OSD.FromString(userState.first_name); + AgentParms["last_name"] = OSD.FromString(userState.last_name); + AgentParms["agent_id"] = OSD.FromUUID(userState.agent_id); + RegionParms["region_id"] = OSD.FromUUID(userState.region_id); + AgentParms["circuit_code"] = OSD.FromInteger(userState.circuit_code); + AgentParms["secure_session_id"] = OSD.FromUUID(userState.secure_session_id); + AgentParms["session_id"] = OSD.FromUUID(userState.session_id); + AgentParms["agent_access"] = OSD.FromBoolean(userState.agent_access); + AgentParms["god_level"] = OSD.FromInteger(userState.god_level); + AgentParms["god_overide"] = OSD.FromBoolean(userState.god_overide); + AgentParms["identified"] = OSD.FromBoolean(userState.identified); + AgentParms["transacted"] = OSD.FromBoolean(userState.transacted); + AgentParms["age_verified"] = OSD.FromBoolean(userState.age_verified); + AgentParms["limited_to_estate"] = OSD.FromInteger(userState.limited_to_estate); + AgentParms["inventory_host"] = OSD.FromString(userState.inventory_host); + + // version 1 + RAMap = AgentParms; + + // Planned for version 2 + // RAMap["agent_params"] = AgentParms; + + RAMap["region_params"] = RegionParms; + RAMap["parameter"] = Parameter; + + string RAMapString = RAMap.ToString(); + m_log.InfoFormat("[OGP] RAMap string {0}", RAMapString); + OSD LLSDofRAMap = RAMap; // RENAME if this works + + m_log.InfoFormat("[OGP]: LLSD of map as string was {0}", LLSDofRAMap.ToString()); + //m_log.InfoFormat("[OGP]: LLSD+XML: {0}", LLSDParser.SerializeXmlString(LLSDofRAMap)); + byte[] buffer = OSDParser.SerializeLLSDXmlBytes(LLSDofRAMap); + + //string bufferDump = System.Text.Encoding.ASCII.GetString(buffer); + //m_log.InfoFormat("[OGP]: buffer form is {0}",bufferDump); + //m_log.InfoFormat("[OGP]: LLSD of map was {0}",buffer.Length); + + Stream os = null; + try + { // send the Post + DeRezRequest.ContentLength = buffer.Length; //Count bytes to send + os = DeRezRequest.GetRequestStream(); + os.Write(buffer, 0, buffer.Length); //Send it + os.Close(); + m_log.InfoFormat("[OGP]: Derez Avatar Posted Rez Avatar request to remote sim {0}", CapAddress); + } + catch (WebException ex) + { + m_log.InfoFormat("[OGP] Bad send on de_rez_avatar {0}", ex.Message); + responseMap["connect"] = OSD.FromBoolean(false); + + return responseMap; + } + + m_log.Info("[OGP] waiting for a reply after rez avatar send"); + string rez_avatar_reply = null; + { // get the response + try + { + WebResponse webResponse = DeRezRequest.GetResponse(); + if (webResponse == null) + { + m_log.Info("[OGP:] Null reply on rez_avatar post"); + } + + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + rez_avatar_reply = sr.ReadToEnd().Trim(); + m_log.InfoFormat("[OGP]: rez_avatar reply was {0} ", rez_avatar_reply); + + } + catch (WebException ex) + { + m_log.InfoFormat("[OGP]: exception on read after send of rez avatar {0}", ex.Message); + responseMap["connect"] = OSD.FromBoolean(false); + + return responseMap; + } + OSD rezResponse = null; + try + { + rezResponse = OSDParser.DeserializeLLSDXml(rez_avatar_reply); + + responseMap = (OSDMap)rezResponse; + } + catch (Exception ex) + { + m_log.InfoFormat("[OGP]: exception on parse of rez reply {0}", ex.Message); + responseMap["connect"] = OSD.FromBoolean(false); + + return responseMap; + } + } + return responseMap; + } + + public OSD GenerateNoHandlerMessage() + { + OSDMap map = new OSDMap(); + map["reason"] = OSD.FromString("LLSDRequest"); + map["message"] = OSD.FromString("No handler registered for LLSD Requests"); + map["login"] = OSD.FromString("false"); + map["connect"] = OSD.FromString("false"); + return map; + } + public OSD GenerateNoStateMessage(UUID passedAvatar) + { + OSDMap map = new OSDMap(); + map["reason"] = OSD.FromString("derez failed"); + map["message"] = OSD.FromString("Unable to locate OGP state for avatar " + passedAvatar.ToString()); + map["login"] = OSD.FromString("false"); + map["connect"] = OSD.FromString("false"); + return map; + } + private bool TryGetAgentCircuitData(string path, out AgentCircuitData userdata) + { + userdata = null; + lock (CapsLoginID) + { + if (CapsLoginID.ContainsKey(path)) + { + userdata = CapsLoginID[path]; + DiscardUsedCap(path); + return true; + } + } + return false; + } + + private void DiscardUsedCap(string path) + { + CapsLoginID.Remove(path); + } + + private Scene GetRootScene() + { + Scene ReturnScene = null; + lock (m_scene) + { + if (m_scene.Count > 0) + { + ReturnScene = m_scene[0]; + } + } + + return ReturnScene; + } + + private Scene GetScene(string scenename) + { + Scene ReturnScene = null; + lock (m_scene) + { + foreach (Scene s in m_scene) + { + if (s.RegionInfo.RegionName.ToLower() == scenename) + { + ReturnScene = s; + break; + } + } + } + + return ReturnScene; + } + + private ulong GetOSCompatibleRegionHandle(RegionInfo reg) + { + return Util.UIntsToLong(reg.RegionLocX, reg.RegionLocY); + } + + private OGPState InitializeNewState() + { + OGPState returnState = new OGPState(); + returnState.first_name = ""; + returnState.last_name = ""; + returnState.agent_id = UUID.Zero; + returnState.local_agent_id = UUID.Zero; + returnState.region_id = UUID.Zero; + returnState.circuit_code = 0; + returnState.secure_session_id = UUID.Zero; + returnState.session_id = UUID.Zero; + returnState.agent_access = true; + returnState.god_level = 0; + returnState.god_overide = false; + returnState.identified = false; + returnState.transacted = false; + returnState.age_verified = false; + returnState.limited_to_estate = 1; + returnState.inventory_host = "http://inv4.mysql.aditi.lindenlab.com"; + returnState.allow_redirect = true; + returnState.sim_access = ""; + returnState.src_can_see_mainland = true; + returnState.src_estate_id = 1; + returnState.src_version = 1; + returnState.src_parent_estate_id = 1; + returnState.visible_to_parent = true; + returnState.teleported_into_region = ""; + + return returnState; + } + + private OGPState GetOGPState(UUID agentId) + { + lock (m_OGPState) + { + if (m_OGPState.ContainsKey(agentId)) + { + return m_OGPState[agentId]; + } + else + { + return InitializeNewState(); + } + } + } + + public void DeleteOGPState(UUID agentId) + { + lock (m_OGPState) + { + if (m_OGPState.ContainsKey(agentId)) + m_OGPState.Remove(agentId); + } + } + + private void UpdateOGPState(UUID agentId, OGPState state) + { + lock (m_OGPState) + { + if (m_OGPState.ContainsKey(agentId)) + { + m_OGPState[agentId] = state; + } + else + { + m_OGPState.Add(agentId,state); + } + } + } + private bool SceneListDuplicateCheck(string str) + { + // no lock, called from locked space! + bool found = false; + + foreach (Scene s in m_scene) + { + if (s.RegionInfo.RegionName == str) + { + found = true; + break; + } + } + + return found; + } + + public void ShutdownConnection(UUID avatarId, OpenGridProtocolModule mod) + { + Scene homeScene = GetRootScene(); + ScenePresence avatar = null; + if (homeScene.TryGetAvatar(avatarId,out avatar)) + { + KillAUser ku = new KillAUser(avatar,mod); + Thread ta = new Thread(ku.ShutdownNoLogout); + ta.IsBackground = true; + ta.Name = "ShutdownThread"; + ta.Start(); + } + } + + private string CreateRandomStr(int len) + { + Random rnd = new Random(System.Environment.TickCount); + string returnstring = ""; + string chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + + for (int i = 0; i < len; i++) + { + returnstring += chars.Substring(rnd.Next(chars.Length), 1); + } + return returnstring; + } + // Temporary hack to allow teleporting to and from Vaak + private static bool customXertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error) + { + //if (cert.Subject == "E=root@lindenlab.com, CN=*.vaak.lindenlab.com, O=\"Linden Lab, Inc.\", L=San Francisco, S=California, C=US") + //{ + return true; + //} + + //return false; + } + } + + public class KillAUser + { + private ScenePresence avToBeKilled = null; + private OpenGridProtocolModule m_mod = null; + + public KillAUser(ScenePresence avatar, OpenGridProtocolModule mod) + { + avToBeKilled = avatar; + m_mod = mod; + } + + public void ShutdownNoLogout() + { + UUID avUUID = UUID.Zero; + + if (avToBeKilled != null) + { + avUUID = avToBeKilled.UUID; + avToBeKilled.MakeChildAgent(); + + avToBeKilled.ControllingClient.SendLogoutPacketWhenClosing = false; + + Thread.Sleep(30000); + + // test for child agent because they might have come back + if (avToBeKilled.IsChildAgent) + { + m_mod.DeleteOGPState(avUUID); + avToBeKilled.ControllingClient.Close(true); + } + } + } + + } + + public class MonoCert : ICertificatePolicy + { + #region ICertificatePolicy Members + + public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) + { + return true; + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs new file mode 100644 index 0000000..e6a12a4 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/DynamicTexture/DynamicTextureModule.cs @@ -0,0 +1,332 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Scripting.DynamicTexture +{ + public class DynamicTextureModule : IRegionModule, IDynamicTextureManager + { + private Dictionary RegisteredScenes = new Dictionary(); + + private Dictionary RenderPlugins = + new Dictionary(); + + private Dictionary Updaters = new Dictionary(); + + #region IDynamicTextureManager Members + + public void RegisterRender(string handleType, IDynamicTextureRender render) + { + if (!RenderPlugins.ContainsKey(handleType)) + { + RenderPlugins.Add(handleType, render); + } + } + + /// + /// Called by code which actually renders the dynamic texture to supply texture data. + /// + /// + /// + public void ReturnData(UUID id, byte[] data) + { + if (Updaters.ContainsKey(id)) + { + DynamicTextureUpdater updater = Updaters[id]; + if (RegisteredScenes.ContainsKey(updater.SimUUID)) + { + Scene scene = RegisteredScenes[updater.SimUUID]; + updater.DataReceived(data, scene); + } + } + } + + public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url, + string extraParams, int updateTimer) + { + return AddDynamicTextureURL(simID, primID, contentType, url, extraParams, updateTimer, false, 255); + } + + public UUID AddDynamicTextureURL(UUID simID, UUID primID, string contentType, string url, + string extraParams, int updateTimer, bool SetBlending, byte AlphaValue) + { + if (RenderPlugins.ContainsKey(contentType)) + { + //Console.WriteLine("dynamic texture being created: " + url + " of type " + contentType); + + DynamicTextureUpdater updater = new DynamicTextureUpdater(); + updater.SimUUID = simID; + updater.PrimID = primID; + updater.ContentType = contentType; + updater.Url = url; + updater.UpdateTimer = updateTimer; + updater.UpdaterID = UUID.Random(); + updater.Params = extraParams; + updater.BlendWithOldTexture = SetBlending; + updater.FrontAlpha = AlphaValue; + + if (!Updaters.ContainsKey(updater.UpdaterID)) + { + Updaters.Add(updater.UpdaterID, updater); + } + + RenderPlugins[contentType].AsyncConvertUrl(updater.UpdaterID, url, extraParams); + return updater.UpdaterID; + } + return UUID.Zero; + } + + public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data, + string extraParams, int updateTimer) + { + return AddDynamicTextureData(simID, primID, contentType, data, extraParams, updateTimer, false, 255); + } + + public UUID AddDynamicTextureData(UUID simID, UUID primID, string contentType, string data, + string extraParams, int updateTimer, bool SetBlending, byte AlphaValue) + { + if (RenderPlugins.ContainsKey(contentType)) + { + DynamicTextureUpdater updater = new DynamicTextureUpdater(); + updater.SimUUID = simID; + updater.PrimID = primID; + updater.ContentType = contentType; + updater.BodyData = data; + updater.UpdateTimer = updateTimer; + updater.UpdaterID = UUID.Random(); + updater.Params = extraParams; + updater.BlendWithOldTexture = SetBlending; + updater.FrontAlpha = AlphaValue; + + if (!Updaters.ContainsKey(updater.UpdaterID)) + { + Updaters.Add(updater.UpdaterID, updater); + } + + RenderPlugins[contentType].AsyncConvertData(updater.UpdaterID, data, extraParams); + return updater.UpdaterID; + } + return UUID.Zero; + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (!RegisteredScenes.ContainsKey(scene.RegionInfo.RegionID)) + { + RegisteredScenes.Add(scene.RegionInfo.RegionID, scene); + scene.RegisterModuleInterface(this); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "DynamicTextureModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + #region Nested type: DynamicTextureUpdater + + public class DynamicTextureUpdater + { + public bool BlendWithOldTexture = false; + public string BodyData; + public string ContentType; + public byte FrontAlpha = 255; + public UUID LastAssetID; + public string Params; + public UUID PrimID; + public bool SetNewFrontAlpha = false; + public UUID SimUUID; + public UUID UpdaterID; + public int UpdateTimer; + public string Url; + + public DynamicTextureUpdater() + { + LastAssetID = UUID.Zero; + UpdateTimer = 0; + BodyData = null; + } + + /// + /// Called once new texture data has been received for this updater. + /// + public void DataReceived(byte[] data, Scene scene) + { + SceneObjectPart part = scene.GetSceneObjectPart(PrimID); + byte[] assetData; + AssetBase oldAsset = null; + + if (BlendWithOldTexture) + { + UUID lastTextureID = part.Shape.Textures.DefaultTexture.TextureID; + oldAsset = scene.AssetCache.GetAsset(lastTextureID, true); + if (oldAsset != null) + { + assetData = BlendTextures(data, oldAsset.Data, SetNewFrontAlpha, FrontAlpha); + } + else + { + assetData = new byte[data.Length]; + Array.Copy(data, assetData, data.Length); + } + } + else + { + assetData = new byte[data.Length]; + Array.Copy(data, assetData, data.Length); + } + + // Create a new asset for user + AssetBase asset = new AssetBase(); + asset.Metadata.FullID = UUID.Random(); + asset.Data = assetData; + asset.Metadata.Name = "DynamicImage" + Util.RandomClass.Next(1, 10000); + asset.Metadata.Type = 0; + asset.Metadata.Description = "dynamic image"; + asset.Metadata.Local = false; + asset.Metadata.Temporary = true; + scene.AssetCache.AddAsset(asset); + + LastAssetID = asset.Metadata.FullID; + + IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface(); + if (cacheLayerDecode != null) + { + cacheLayerDecode.syncdecode(asset.Metadata.FullID, asset.Data); + } + cacheLayerDecode = null; + + // mostly keep the values from before + Primitive.TextureEntry tmptex = part.Shape.Textures; + + // remove the old asset from the cache + UUID oldID = tmptex.DefaultTexture.TextureID; + scene.AssetCache.ExpireAsset(oldID); + + tmptex.DefaultTexture.TextureID = asset.Metadata.FullID; + // I'm pretty sure we always want to force this to true + tmptex.DefaultTexture.Fullbright = true; + + part.Shape.Textures = tmptex; + part.ScheduleFullUpdate(); + } + + private byte[] BlendTextures(byte[] frontImage, byte[] backImage, bool setNewAlpha, byte newAlpha) + { + ManagedImage managedImage; + Image image; + + if (OpenJPEG.DecodeToImage(frontImage, out managedImage, out image)) + { + Bitmap image1 = new Bitmap(image); + + if (OpenJPEG.DecodeToImage(backImage, out managedImage, out image)) + { + Bitmap image2 = new Bitmap(image); + + if (setNewAlpha) + SetAlpha(ref image1, newAlpha); + + Bitmap joint = MergeBitMaps(image1, image2); + + byte[] result = new byte[0]; + + try + { + result = OpenJPEG.EncodeFromImage(joint, true); + } + catch (Exception) + { + Console.WriteLine( + "[DYNAMICTEXTUREMODULE]: OpenJpeg Encode Failed. Empty byte data returned!"); + } + + return result; + } + } + + return null; + } + + public Bitmap MergeBitMaps(Bitmap front, Bitmap back) + { + Bitmap joint; + Graphics jG; + + joint = new Bitmap(back.Width, back.Height, PixelFormat.Format32bppArgb); + jG = Graphics.FromImage(joint); + + jG.DrawImage(back, 0, 0, back.Width, back.Height); + jG.DrawImage(front, 0, 0, back.Width, back.Height); + + return joint; + } + + private void SetAlpha(ref Bitmap b, byte alpha) + { + for (int w = 0; w < b.Width; w++) + { + for (int h = 0; h < b.Height; h++) + { + b.SetPixel(w, h, Color.FromArgb(alpha, b.GetPixel(w, h))); + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs new file mode 100644 index 0000000..c23ff1e --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/EMailModules/EmailModule.cs @@ -0,0 +1,288 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using log4net; +using Nini.Config; +using DotNetOpenMail; +using DotNetOpenMail.SmtpAuth; + +namespace OpenSim.Region.CoreModules.Scripting.EmailModules +{ + public class EmailModule : IEmailModule + { + // + // Log + // + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // + // Module vars + // + private IConfigSource m_Config; + private string m_HostName = string.Empty; + //private string m_RegionName = string.Empty; + private string SMTP_SERVER_HOSTNAME = string.Empty; + private int SMTP_SERVER_PORT = 25; + private string SMTP_SERVER_LOGIN = string.Empty; + private string SMTP_SERVER_PASSWORD = string.Empty; + + // Scenes by Region Handle + private Dictionary m_Scenes = + new Dictionary(); + + private bool m_Enabled = false; + + public void Initialise(Scene scene, IConfigSource config) + { + m_Config = config; + IConfig SMTPConfig; + + //FIXME: RegionName is correct?? + //m_RegionName = scene.RegionInfo.RegionName; + + IConfig startupConfig = m_Config.Configs["Startup"]; + + m_Enabled = (startupConfig.GetString("emailmodule", "DefaultEmailModule") == "DefaultEmailModule"); + + //Load SMTP SERVER config + try + { + if ((SMTPConfig = m_Config.Configs["SMTP"]) == null) + { + m_log.InfoFormat("[SMTP] SMTP server not configured"); + m_Enabled = false; + return; + } + + if (!SMTPConfig.GetBoolean("enabled", false)) + { + m_log.InfoFormat("[SMTP] module disabled in configuration"); + m_Enabled = false; + return; + } + + m_HostName = SMTPConfig.GetString("host_domain_header_from", m_HostName); + SMTP_SERVER_HOSTNAME = SMTPConfig.GetString("SMTP_SERVER_HOSTNAME",SMTP_SERVER_HOSTNAME); + SMTP_SERVER_PORT = SMTPConfig.GetInt("SMTP_SERVER_PORT", SMTP_SERVER_PORT); + SMTP_SERVER_LOGIN = SMTPConfig.GetString("SMTP_SERVER_LOGIN", SMTP_SERVER_LOGIN); + SMTP_SERVER_PASSWORD = SMTPConfig.GetString("SMTP_SERVER_PASSWORD", SMTP_SERVER_PASSWORD); + } + catch (Exception e) + { + m_log.Error("[EMAIL] DefaultEmailModule not configured: "+ e.Message); + m_Enabled = false; + return; + } + + // It's a go! + if (m_Enabled) + { + lock (m_Scenes) + { + // Claim the interface slot + scene.RegisterModuleInterface(this); + + // Add to scene list + if (m_Scenes.ContainsKey(scene.RegionInfo.RegionHandle)) + { + m_Scenes[scene.RegionInfo.RegionHandle] = scene; + } + else + { + m_Scenes.Add(scene.RegionInfo.RegionHandle, scene); + } + } + + m_log.Info("[EMAIL] Activated DefaultEmailModule"); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "DefaultEmailModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + /// + /// + /// + /// + private void DelayInSeconds(int seconds) + { + TimeSpan DiffDelay = new TimeSpan(0, 0, seconds); + DateTime EndDelay = DateTime.Now.Add(DiffDelay); + while (DateTime.Now < EndDelay) + { + ;//Do nothing!! + } + } + + private SceneObjectPart findPrim(UUID objectID, out string ObjectRegionName) + { + lock (m_Scenes) + { + foreach (Scene s in m_Scenes.Values) + { + SceneObjectPart part = s.GetSceneObjectPart(objectID); + if (part != null) + { + ObjectRegionName = s.RegionInfo.RegionName; + return part; + } + } + } + ObjectRegionName = string.Empty; + return null; + } + + private void resolveNamePositionRegionName(UUID objectID, out string ObjectName, out string ObjectAbsolutePosition, out string ObjectRegionName) + { + string m_ObjectRegionName; + SceneObjectPart part = findPrim(objectID, out m_ObjectRegionName); + if (part != null) + { + ObjectAbsolutePosition = part.AbsolutePosition.ToString(); + ObjectName = part.Name; + ObjectRegionName = m_ObjectRegionName; + return; + } + ObjectAbsolutePosition = part.AbsolutePosition.ToString(); + ObjectName = part.Name; + ObjectRegionName = m_ObjectRegionName; + return; + } + + /// + /// SendMail function utilized by llEMail + /// + /// + /// + /// + /// + public void SendEmail(UUID objectID, string address, string subject, string body) + { + //Check if address is empty + if (address == string.Empty) + return; + + //FIXED:Check the email is correct form in REGEX + string EMailpatternStrict = @"^(([^<>()[\]\\.,;:\s@\""]+" + + @"(\.[^<>()[\]\\.,;:\s@\""]+)*)|(\"".+\""))@" + + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" + + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+" + + @"[a-zA-Z]{2,}))$"; + Regex EMailreStrict = new Regex(EMailpatternStrict); + bool isEMailStrictMatch = EMailreStrict.IsMatch(address); + if (!isEMailStrictMatch) + { + m_log.Error("[EMAIL] REGEX Problem in EMail Address: "+address); + return; + } + //FIXME:Check if subject + body = 4096 Byte + if ((subject.Length + body.Length) > 1024) + { + m_log.Error("[EMAIL] subject + body > 1024 Byte"); + return; + } + + try + { + string LastObjectName = string.Empty; + string LastObjectPosition = string.Empty; + string LastObjectRegionName = string.Empty; + //DONE: Message as Second Life style + //20 second delay - AntiSpam System - for now only 10 seconds + DelayInSeconds(10); + //Creation EmailMessage + EmailMessage emailMessage = new EmailMessage(); + //From + emailMessage.FromAddress = new EmailAddress(objectID.ToString()+"@"+m_HostName); + //To - Only One + emailMessage.AddToAddress(new EmailAddress(address)); + //Subject + emailMessage.Subject = subject; + //TEXT Body + resolveNamePositionRegionName(objectID, out LastObjectName, out LastObjectPosition, out LastObjectRegionName); + emailMessage.TextPart = new TextAttachment("Object-Name: " + LastObjectName + + "\r\nRegion: " + LastObjectRegionName + "\r\nLocal-Position: " + + LastObjectPosition+"\r\n\r\n\r\n" + body); + //HTML Body + emailMessage.HtmlPart = new HtmlAttachment("

" + + "
Object-Name: " + LastObjectName + + "
Region: " + LastObjectRegionName + + "
Local-Position: " + LastObjectPosition + "


" + +body+"\r\n

"); + + //Set SMTP SERVER config + SmtpServer smtpServer=new SmtpServer(SMTP_SERVER_HOSTNAME,SMTP_SERVER_PORT); + //Authentication + smtpServer.SmtpAuthToken=new SmtpAuthToken(SMTP_SERVER_LOGIN, SMTP_SERVER_PASSWORD); + //Send Email Message + emailMessage.Send(smtpServer); + //Log + m_log.Info("[EMAIL] EMail sent to: " + address + " from object: " + objectID.ToString()); + } + catch (Exception e) + { + m_log.Error("[EMAIL] DefaultEmailModule Exception: "+e.Message); + return; + } + } + + /// + /// + /// + /// + /// + /// + /// + public Email GetNextEmail(UUID objectID, string sender, string subject) + { + return null; + } + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs new file mode 100644 index 0000000..9f3bd09 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -0,0 +1,437 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using System.Collections; + +/***************************************************** + * + * ScriptsHttpRequests + * + * Implements the llHttpRequest and http_response + * callback. + * + * Some stuff was already in LSLLongCmdHandler, and then + * there was this file with a stub class in it. So, + * I am moving some of the objects and functions out of + * LSLLongCmdHandler, such as the HttpRequestClass, the + * start and stop methods, and setting up pending and + * completed queues. These are processed in the + * LSLLongCmdHandler polling loop. Similiar to the + * XMLRPCModule, since that seems to work. + * + * //TODO + * + * This probably needs some throttling mechanism but + * it's wide open right now. This applies to both + * number of requests and data volume. + * + * Linden puts all kinds of header fields in the requests. + * Not doing any of that: + * User-Agent + * X-SecondLife-Shard + * X-SecondLife-Object-Name + * X-SecondLife-Object-Key + * X-SecondLife-Region + * X-SecondLife-Local-Position + * X-SecondLife-Local-Velocity + * X-SecondLife-Local-Rotation + * X-SecondLife-Owner-Name + * X-SecondLife-Owner-Key + * + * HTTPS support + * + * Configurable timeout? + * Configurable max response size? + * Configurable + * + * **************************************************/ + +namespace OpenSim.Region.CoreModules.Scripting.HttpRequest +{ + public class HttpRequestModule : IRegionModule, IHttpRequestModule + { + private object HttpListLock = new object(); + private int httpTimeout = 30000; + private string m_name = "HttpScriptRequests"; + + private string m_proxyurl = ""; + private string m_proxyexcepts = ""; + + // + private Dictionary m_pendingRequests; + private Scene m_scene; + // private Queue rpcQueue = new Queue(); + + public HttpRequestModule() + { + } + + #region IHttpRequestModule Members + + public UUID MakeHttpRequest(string url, string parameters, string body) + { + return UUID.Zero; + } + + public UUID StartHttpRequest(uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body) + { + UUID reqID = UUID.Random(); + HttpRequestClass htc = new HttpRequestClass(); + + // Partial implementation: support for parameter flags needed + // see http://wiki.secondlife.com/wiki/LlHTTPRequest + // + // Parameters are expected in {key, value, ... , key, value} + if (parameters != null) + { + string[] parms = parameters.ToArray(); + for (int i = 0; i < parms.Length; i += 2) + { + switch (Int32.Parse(parms[i])) + { + case (int)HttpRequestConstants.HTTP_METHOD: + + htc.HttpMethod = parms[i + 1]; + break; + + case (int)HttpRequestConstants.HTTP_MIMETYPE: + + htc.HttpMIMEType = parms[i + 1]; + break; + + case (int)HttpRequestConstants.HTTP_BODY_MAXLENGTH: + + // TODO implement me + break; + + case (int)HttpRequestConstants.HTTP_VERIFY_CERT: + + // TODO implement me + break; + } + } + } + + htc.LocalID = localID; + htc.ItemID = itemID; + htc.Url = url; + htc.ReqID = reqID; + htc.HttpTimeout = httpTimeout; + htc.OutboundBody = body; + htc.ResponseHeaders = headers; + htc.proxyurl = m_proxyurl; + htc.proxyexcepts = m_proxyexcepts; + + lock (HttpListLock) + { + m_pendingRequests.Add(reqID, htc); + } + + htc.Process(); + + return reqID; + } + + public void StopHttpRequest(uint m_localID, UUID m_itemID) + { + if (m_pendingRequests != null) + { + lock (HttpListLock) + { + HttpRequestClass tmpReq; + if (m_pendingRequests.TryGetValue(m_itemID, out tmpReq)) + { + tmpReq.Stop(); + m_pendingRequests.Remove(m_itemID); + } + } + } + } + + /* + * TODO + * Not sure how important ordering is is here - the next first + * one completed in the list is returned, based soley on its list + * position, not the order in which the request was started or + * finsihed. I thought about setting up a queue for this, but + * it will need some refactoring and this works 'enough' right now + */ + + public IServiceRequest GetNextCompletedRequest() + { + lock (HttpListLock) + { + foreach (UUID luid in m_pendingRequests.Keys) + { + HttpRequestClass tmpReq; + + if (m_pendingRequests.TryGetValue(luid, out tmpReq)) + { + if (tmpReq.Finished) + { + return tmpReq; + } + } + } + } + return null; + } + + public void RemoveCompletedRequest(UUID id) + { + lock (HttpListLock) + { + HttpRequestClass tmpReq; + if (m_pendingRequests.TryGetValue(id, out tmpReq)) + { + tmpReq.Stop(); + tmpReq = null; + m_pendingRequests.Remove(id); + } + } + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + + m_scene.RegisterModuleInterface(this); + + m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); + m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + + m_pendingRequests = new Dictionary(); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + } + + public class HttpRequestClass: IServiceRequest + { + // Constants for parameters + // public const int HTTP_BODY_MAXLENGTH = 2; + // public const int HTTP_METHOD = 0; + // public const int HTTP_MIMETYPE = 1; + // public const int HTTP_VERIFY_CERT = 3; + private bool _finished; + public bool Finished + { + get { return _finished; } + } + // public int HttpBodyMaxLen = 2048; // not implemented + + // Parameter members and default values + public string HttpMethod = "GET"; + public string HttpMIMEType = "text/plain;charset=utf-8"; + public int HttpTimeout; + // public bool HttpVerifyCert = true; // not implemented + private Thread httpThread; + + // Request info + private UUID _itemID; + public UUID ItemID + { + get { return _itemID; } + set { _itemID = value; } + } + private uint _localID; + public uint LocalID + { + get { return _localID; } + set { _localID = value; } + } + public DateTime Next; + public string proxyurl; + public string proxyexcepts; + public string OutboundBody; + private UUID _reqID; + public UUID ReqID + { + get { return _reqID; } + set { _reqID = value; } + } + public HttpWebRequest Request; + public string ResponseBody; + public List ResponseMetadata; + public Dictionary ResponseHeaders; + public int Status; + public string Url; + + public void Process() + { + httpThread = new Thread(SendRequest); + httpThread.Name = "HttpRequestThread"; + httpThread.Priority = ThreadPriority.BelowNormal; + httpThread.IsBackground = true; + _finished = false; + httpThread.Start(); + ThreadTracker.Add(httpThread); + } + + /* + * TODO: More work on the response codes. Right now + * returning 200 for success or 499 for exception + */ + + public void SendRequest() + { + HttpWebResponse response = null; + StringBuilder sb = new StringBuilder(); + byte[] buf = new byte[8192]; + string tempString = null; + int count = 0; + + try + { + Request = (HttpWebRequest) WebRequest.Create(Url); + Request.Method = HttpMethod; + Request.ContentType = HttpMIMEType; + + if (proxyurl != null && proxyurl.Length > 0) + { + if (proxyexcepts != null && proxyexcepts.Length > 0) + { + string[] elist = proxyexcepts.Split(';'); + Request.Proxy = new WebProxy(proxyurl, true, elist); + } + else + { + Request.Proxy = new WebProxy(proxyurl, true); + } + } + + foreach (KeyValuePair entry in ResponseHeaders) + Request.Headers[entry.Key] = entry.Value; + + // Encode outbound data + if (OutboundBody.Length > 0) + { + byte[] data = Encoding.UTF8.GetBytes(OutboundBody); + + Request.ContentLength = data.Length; + Stream bstream = Request.GetRequestStream(); + bstream.Write(data, 0, data.Length); + bstream.Close(); + } + + Request.Timeout = HttpTimeout; + // execute the request + response = (HttpWebResponse) Request.GetResponse(); + + Stream resStream = response.GetResponseStream(); + + do + { + // fill the buffer with data + count = resStream.Read(buf, 0, buf.Length); + + // make sure we read some data + if (count != 0) + { + // translate from bytes to ASCII text + tempString = Encoding.UTF8.GetString(buf, 0, count); + + // continue building the string + sb.Append(tempString); + } + } while (count > 0); // any more data to read? + + ResponseBody = sb.ToString(); + } + catch (Exception e) + { + if (e is WebException && ((WebException)e).Status == WebExceptionStatus.ProtocolError) + { + HttpWebResponse webRsp = (HttpWebResponse)((WebException)e).Response; + Status = (int)webRsp.StatusCode; + ResponseBody = webRsp.StatusDescription; + } + else + { + Status = (int)OSHttpStatusCode.ClientErrorJoker; + ResponseBody = e.Message; + } + + _finished = true; + return; + } + finally + { + if (response != null) + response.Close(); + } + + Status = (int)OSHttpStatusCode.SuccessOk; + _finished = true; + } + + public void Stop() + { + try + { + httpThread.Abort(); + } + catch (Exception) + { + } + } + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs new file mode 100644 index 0000000..afcaff1 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/LoadImageURL/LoadImageURLModule.cs @@ -0,0 +1,229 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Drawing; +using System.IO; +using System.Net; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL +{ + public class LoadImageURLModule : IRegionModule, IDynamicTextureRender + { + private string m_name = "LoadImageURL"; + private Scene m_scene; + private IDynamicTextureManager m_textureManager; + + private string m_proxyurl = ""; + private string m_proxyexcepts = ""; + + #region IDynamicTextureRender Members + + public string GetName() + { + return m_name; + } + + public string GetContentType() + { + return ("image"); + } + + public bool SupportsAsynchronous() + { + return true; + } + + public byte[] ConvertUrl(string url, string extraParams) + { + return null; + } + + public byte[] ConvertStream(Stream data, string extraParams) + { + return null; + } + + public bool AsyncConvertUrl(UUID id, string url, string extraParams) + { + MakeHttpRequest(url, id); + return true; + } + + public bool AsyncConvertData(UUID id, string bodyData, string extraParams) + { + return false; + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (m_scene == null) + { + m_scene = scene; + } + + m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); + m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + } + + public void PostInitialise() + { + m_textureManager = m_scene.RequestModuleInterface(); + if (m_textureManager != null) + { + m_textureManager.RegisterRender(GetContentType(), this); + } + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + private void MakeHttpRequest(string url, UUID requestID) + { + WebRequest request = HttpWebRequest.Create(url); + + if (m_proxyurl != null && m_proxyurl.Length > 0) + { + if (m_proxyexcepts != null && m_proxyexcepts.Length > 0) + { + string[] elist = m_proxyexcepts.Split(';'); + request.Proxy = new WebProxy(m_proxyurl, true, elist); + } + else + { + request.Proxy = new WebProxy(m_proxyurl, true); + } + } + + RequestState state = new RequestState((HttpWebRequest) request, requestID); + // IAsyncResult result = request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); + request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state); + + TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1)); + state.TimeOfRequest = (int) t.TotalSeconds; + } + + private void HttpRequestReturn(IAsyncResult result) + { + RequestState state = (RequestState) result.AsyncState; + WebRequest request = (WebRequest) state.Request; + try + { + HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); + if (response.StatusCode == HttpStatusCode.OK) + { + Bitmap image = new Bitmap(response.GetResponseStream()); + Size newsize; + + // TODO: make this a bit less hard coded + if ((image.Height < 64) && (image.Width < 64)) + { + newsize = new Size(32, 32); + } + else if ((image.Height < 128) && (image.Width < 128)) + { + newsize = new Size(64, 64); + } + else if ((image.Height < 256) && (image.Width < 256)) + { + newsize = new Size(128, 128); + } + else if ((image.Height < 512 && image.Width < 512)) + { + newsize = new Size(256, 256); + } + else if ((image.Height < 1024 && image.Width < 1024)) + { + newsize = new Size(512, 512); + } + else + { + newsize = new Size(1024, 1024); + } + + Bitmap resize = new Bitmap(image, newsize); + byte[] imageJ2000 = new byte[0]; + + try + { + imageJ2000 = OpenJPEG.EncodeFromImage(resize, true); + } + catch (Exception) + { + Console.WriteLine( + "[LOADIMAGEURLMODULE]: OpenJpeg Encode Failed. Empty byte data returned!"); + } + + m_textureManager.ReturnData(state.RequestID, imageJ2000); + } + } + catch (WebException) + { + + } + } + + #region Nested type: RequestState + + public class RequestState + { + public HttpWebRequest Request = null; + public UUID RequestID = UUID.Zero; + public int TimeOfRequest = 0; + + public RequestState(HttpWebRequest request, UUID requestID) + { + Request = request; + RequestID = requestID; + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs b/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs new file mode 100644 index 0000000..0c709b5 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/VectorRender/VectorRenderModule.cs @@ -0,0 +1,515 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Net; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +//using Cairo; + +namespace OpenSim.Region.CoreModules.Scripting.VectorRender +{ + public class VectorRenderModule : IRegionModule, IDynamicTextureRender + { + private string m_name = "VectorRenderModule"; + private Scene m_scene; + private IDynamicTextureManager m_textureManager; + + public VectorRenderModule() + { + } + + #region IDynamicTextureRender Members + + public string GetContentType() + { + return ("vector"); + } + + public string GetName() + { + return m_name; + } + + public bool SupportsAsynchronous() + { + return true; + } + + public byte[] ConvertUrl(string url, string extraParams) + { + return null; + } + + public byte[] ConvertStream(Stream data, string extraParams) + { + return null; + } + + public bool AsyncConvertUrl(UUID id, string url, string extraParams) + { + return false; + } + + public bool AsyncConvertData(UUID id, string bodyData, string extraParams) + { + Draw(bodyData, id, extraParams); + return true; + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + if (m_scene == null) + { + m_scene = scene; + } + } + + public void PostInitialise() + { + m_textureManager = m_scene.RequestModuleInterface(); + if (m_textureManager != null) + { + m_textureManager.RegisterRender(GetContentType(), this); + } + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + private void Draw(string data, UUID id, string extraParams) + { + // We need to cater for old scripts that didnt use extraParams neatly, they use either an integer size which represents both width and height, or setalpha + // we will now support multiple comma seperated params in the form width:256,height:512,alpha:255 + int width = 256; + int height = 256; + int alpha = 255; // 0 is transparent + + char[] paramDelimiter = { ',' }; + char[] nvpDelimiter = { ':' }; + + extraParams = extraParams.Trim(); + extraParams = extraParams.ToLower(); + + string[] nvps = extraParams.Split(paramDelimiter); + + int temp = -1; + foreach (string pair in nvps) + { + string[] nvp = pair.Split(nvpDelimiter); + string name = ""; + string value = ""; + + if (nvp[0] != null) + { + name = nvp[0].Trim(); + } + + if (nvp.Length == 2) + { + value = nvp[1].Trim(); + } + + switch (name) + { + case "width": + temp = parseIntParam(value); + if (temp != -1) + { + if (temp < 1) + { + width = 1; + } + else if (temp > 2048) + { + width = 2048; + } + else + { + width = temp; + } + } + break; + case "height": + temp = parseIntParam(value); + if (temp != -1) + { + if (temp < 1) + { + height = 1; + } + else if (temp > 2048) + { + height = 2048; + } + else + { + height = temp; + } + } + break; + case "alpha": + temp = parseIntParam(value); + if (temp != -1) + { + if (temp < 0) + { + alpha = 0; + } + else if (temp > 255) + { + alpha = 255; + } + else + { + alpha = temp; + } + } + break; + case "": + // blank string has been passed do nothing just use defaults + break; + default: // this is all for backwards compat, all a bit ugly hopfully can be removed in future + // could be either set alpha or just an int + if (name == "setalpha") + { + alpha = 0; // set the texture to have transparent background (maintains backwards compat) + } + else + { + // this function used to accept an int on its own that represented both + // width and height, this is to maintain backwards compat, could be removed + // but would break existing scripts + temp = parseIntParam(name); + if (temp != -1) + { + if (temp > 1024) + temp = 1024; + + if (temp < 128) + temp = 128; + + width = temp; + height = temp; + } + } + break; + } + + } + + Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + Graphics graph = Graphics.FromImage(bitmap); + + // this is really just to save people filling the + // background white in their scripts, only do when fully opaque + if (alpha == 255) + { + graph.FillRectangle(new SolidBrush(Color.White), 0, 0, width, height); + } + + for (int w = 0; w < bitmap.Width; w++) + { + for (int h = 0; h < bitmap.Height; h++) + { + bitmap.SetPixel(w, h, Color.FromArgb(alpha, bitmap.GetPixel(w, h))); + } + } + + + GDIDraw(data, graph); + + byte[] imageJ2000 = new byte[0]; + + try + { + imageJ2000 = OpenJPEG.EncodeFromImage(bitmap, true); + } + catch (Exception) + { + Console.WriteLine( + "[VECTORRENDERMODULE]: OpenJpeg Encode Failed. Empty byte data returned!"); + } + m_textureManager.ReturnData(id, imageJ2000); + } + + private int parseIntParam(string strInt) + { + int parsed; + try + { + parsed = Convert.ToInt32(strInt); + } + catch (Exception) + { + //Ckrinke: Add a WriteLine to remove the warning about 'e' defined but not used + // Console.WriteLine("Problem with Draw. Please verify parameters." + e.ToString()); + parsed = -1; + } + + return parsed; + + } + + +/* + private void CairoDraw(string data, System.Drawing.Graphics graph) + { + using (Win32Surface draw = new Win32Surface(graph.GetHdc())) + { + Context contex = new Context(draw); + + contex.Antialias = Antialias.None; //fastest method but low quality + contex.LineWidth = 7; + char[] lineDelimiter = { ';' }; + char[] partsDelimiter = { ',' }; + string[] lines = data.Split(lineDelimiter); + + foreach (string line in lines) + { + string nextLine = line.Trim(); + + if (nextLine.StartsWith("MoveTO")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, ref x, ref y); + contex.MoveTo(x, y); + } + else if (nextLine.StartsWith("LineTo")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, ref x, ref y); + contex.LineTo(x, y); + contex.Stroke(); + } + } + } + graph.ReleaseHdc(); + } +*/ + + private void GDIDraw(string data, Graphics graph) + { + Point startPoint = new Point(0, 0); + Point endPoint = new Point(0, 0); + Pen drawPen = new Pen(Color.Black, 7); + string fontName = "Arial"; + float fontSize = 14; + Font myFont = new Font(fontName, fontSize); + SolidBrush myBrush = new SolidBrush(Color.Black); + char[] lineDelimiter = {';'}; + char[] partsDelimiter = {','}; + string[] lines = data.Split(lineDelimiter); + + foreach (string line in lines) + { + string nextLine = line.Trim(); + //replace with switch, or even better, do some proper parsing + if (nextLine.StartsWith("MoveTo")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 6, ref x, ref y); + startPoint.X = (int) x; + startPoint.Y = (int) y; + } + else if (nextLine.StartsWith("LineTo")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 6, ref x, ref y); + endPoint.X = (int) x; + endPoint.Y = (int) y; + graph.DrawLine(drawPen, startPoint, endPoint); + startPoint.X = endPoint.X; + startPoint.Y = endPoint.Y; + } + else if (nextLine.StartsWith("Text")) + { + nextLine = nextLine.Remove(0, 4); + nextLine = nextLine.Trim(); + graph.DrawString(nextLine, myFont, myBrush, startPoint); + } + else if (nextLine.StartsWith("Image")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 5, ref x, ref y); + endPoint.X = (int) x; + endPoint.Y = (int) y; + Image image = ImageHttpRequest(nextLine); + graph.DrawImage(image, (float) startPoint.X, (float) startPoint.Y, x, y); + startPoint.X += endPoint.X; + startPoint.Y += endPoint.Y; + } + else if (nextLine.StartsWith("Rectangle")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 9, ref x, ref y); + endPoint.X = (int) x; + endPoint.Y = (int) y; + graph.DrawRectangle(drawPen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y); + startPoint.X += endPoint.X; + startPoint.Y += endPoint.Y; + } + else if (nextLine.StartsWith("FillRectangle")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 13, ref x, ref y); + endPoint.X = (int) x; + endPoint.Y = (int) y; + graph.FillRectangle(myBrush, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y); + startPoint.X += endPoint.X; + startPoint.Y += endPoint.Y; + } + else if (nextLine.StartsWith("Ellipse")) + { + float x = 0; + float y = 0; + GetParams(partsDelimiter, ref nextLine, 7, ref x, ref y); + endPoint.X = (int) x; + endPoint.Y = (int) y; + graph.DrawEllipse(drawPen, startPoint.X, startPoint.Y, endPoint.X, endPoint.Y); + startPoint.X += endPoint.X; + startPoint.Y += endPoint.Y; + } + else if (nextLine.StartsWith("FontSize")) + { + nextLine = nextLine.Remove(0, 8); + nextLine = nextLine.Trim(); + fontSize = Convert.ToSingle(nextLine, CultureInfo.InvariantCulture); + myFont = new Font(fontName, fontSize); + } + else if (nextLine.StartsWith("FontName")) + { + nextLine = nextLine.Remove(0, 8); + fontName = nextLine.Trim(); + myFont = new Font(fontName, fontSize); + } + else if (nextLine.StartsWith("PenSize")) + { + nextLine = nextLine.Remove(0, 7); + nextLine = nextLine.Trim(); + float size = Convert.ToSingle(nextLine, CultureInfo.InvariantCulture); + drawPen.Width = size; + } + else if (nextLine.StartsWith("PenColour")) + { + nextLine = nextLine.Remove(0, 9); + nextLine = nextLine.Trim(); + int hex = 0; + + Color newColour; + if (Int32.TryParse(nextLine, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hex)) + { + newColour = Color.FromArgb(hex); + } + else + { + // this doesn't fail, it just returns black if nothing is found + newColour = Color.FromName(nextLine); + } + + myBrush.Color = newColour; + drawPen.Color = newColour; + } + } + } + + private static void GetParams(char[] partsDelimiter, ref string line, int startLength, ref float x, ref float y) + { + line = line.Remove(0, startLength); + string[] parts = line.Split(partsDelimiter); + if (parts.Length == 2) + { + string xVal = parts[0].Trim(); + string yVal = parts[1].Trim(); + x = Convert.ToSingle(xVal, CultureInfo.InvariantCulture); + y = Convert.ToSingle(yVal, CultureInfo.InvariantCulture); + } + else if (parts.Length > 2) + { + string xVal = parts[0].Trim(); + string yVal = parts[1].Trim(); + x = Convert.ToSingle(xVal, CultureInfo.InvariantCulture); + y = Convert.ToSingle(yVal, CultureInfo.InvariantCulture); + + line = ""; + for (int i = 2; i < parts.Length; i++) + { + line = line + parts[i].Trim(); + line = line + " "; + } + } + } + + private Bitmap ImageHttpRequest(string url) + { + WebRequest request = HttpWebRequest.Create(url); +//Ckrinke: Comment out for now as 'str' is unused. Bring it back into play later when it is used. +//Ckrinke Stream str = null; + HttpWebResponse response = (HttpWebResponse) (request).GetResponse(); + if (response.StatusCode == HttpStatusCode.OK) + { + Bitmap image = new Bitmap(response.GetResponseStream()); + return image; + } + + return null; + } + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs new file mode 100644 index 0000000..c363940 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/WorldComm/WorldCommModule.cs @@ -0,0 +1,726 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +// using log4net; +// using System.Reflection; + + +/***************************************************** + * + * WorldCommModule + * + * + * Holding place for world comms - basically llListen + * function implementation. + * + * lLListen(integer channel, string name, key id, string msg) + * The name, id, and msg arguments specify the filtering + * criteria. You can pass the empty string + * (or NULL_KEY for id) for these to set a completely + * open filter; this causes the listen() event handler to be + * invoked for all chat on the channel. To listen only + * for chat spoken by a specific object or avatar, + * specify the name and/or id arguments. To listen + * only for a specific command, specify the + * (case-sensitive) msg argument. If msg is not empty, + * listener will only hear strings which are exactly equal + * to msg. You can also use all the arguments to establish + * the most restrictive filtering criteria. + * + * It might be useful for each listener to maintain a message + * digest, with a list of recent messages by UUID. This can + * be used to prevent in-world repeater loops. However, the + * linden functions do not have this capability, so for now + * thats the way it works. + * Instead it blocks messages originating from the same prim. + * (not Object!) + * + * For LSL compliance, note the following: + * (Tested again 1.21.1 on May 2, 2008) + * 1. 'id' has to be parsed into a UUID. None-UUID keys are + * to be replaced by the ZeroID key. (Well, TryParse does + * that for us. + * 2. Setting up an listen event from the same script, with the + * same filter settings (including step 1), returns the same + * handle as the original filter. + * 3. (TODO) handles should be script-local. Starting from 1. + * Might be actually easier to map the global handle into + * script-local handle in the ScriptEngine. Not sure if its + * worth the effort tho. + * + * **************************************************/ + +namespace OpenSim.Region.CoreModules.Scripting.WorldComm +{ + public class WorldCommModule : IRegionModule, IWorldComm + { + // private static readonly ILog m_log = + // LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private ListenerManager m_listenerManager; + private Queue m_pending; + private Queue m_pendingQ; + private Scene m_scene; + private int m_whisperdistance = 10; + private int m_saydistance = 30; + private int m_shoutdistance = 100; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + // wrap this in a try block so that defaults will work if + // the config file doesn't specify otherwise. + int maxlisteners = 1000; + int maxhandles = 64; + try + { + m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); + m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); + m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); + maxlisteners = config.Configs["Chat"].GetInt("max_listens_per_region", maxlisteners); + maxhandles = config.Configs["Chat"].GetInt("max_listens_per_script", maxhandles); + } + catch (Exception) + { + } + if (maxlisteners < 1) maxlisteners = int.MaxValue; + if (maxhandles < 1) maxhandles = int.MaxValue; + + m_scene = scene; + m_scene.RegisterModuleInterface(this); + m_listenerManager = new ListenerManager(maxlisteners, maxhandles); + m_scene.EventManager.OnChatFromClient += DeliverClientMessage; + m_scene.EventManager.OnChatBroadcast += DeliverClientMessage; + m_pendingQ = new Queue(); + m_pending = Queue.Synchronized(m_pendingQ); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "WorldCommModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region IWorldComm Members + + /// + /// Create a listen event callback with the specified filters. + /// The parameters localID,itemID are needed to uniquely identify + /// the script during 'peek' time. Parameter hostID is needed to + /// determine the position of the script. + /// + /// localID of the script engine + /// UUID of the script engine + /// UUID of the SceneObjectPart + /// channel to listen on + /// name to filter on + /// key to filter on (user given, could be totally faked) + /// msg to filter on + /// number of the scripts handle + public int Listen(uint localID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg) + { + return m_listenerManager.AddListener(localID, itemID, hostID, channel, name, id, msg); + } + + /// + /// Sets the listen event with handle as active (active = TRUE) or inactive (active = FALSE). + /// The handle used is returned from Listen() + /// + /// UUID of the script engine + /// handle returned by Listen() + /// temp. activate or deactivate the Listen() + public void ListenControl(UUID itemID, int handle, int active) + { + if (active == 1) + m_listenerManager.Activate(itemID, handle); + else if (active == 0) + m_listenerManager.Dectivate(itemID, handle); + } + + /// + /// Removes the listen event callback with handle + /// + /// UUID of the script engine + /// handle returned by Listen() + public void ListenRemove(UUID itemID, int handle) + { + m_listenerManager.Remove(itemID, handle); + } + + /// + /// Removes all listen event callbacks for the given itemID + /// (script engine) + /// + /// UUID of the script engine + public void DeleteListener(UUID itemID) + { + m_listenerManager.DeleteListener(itemID); + } + + + protected static Vector3 CenterOfRegion = new Vector3(128, 128, 20); + + public void DeliverMessage(ChatTypeEnum type, int channel, string name, UUID id, string msg) + { + Vector3 position; + SceneObjectPart source; + ScenePresence avatar; + + if ((source = m_scene.GetSceneObjectPart(id)) != null) + position = source.AbsolutePosition; + else if ((avatar = m_scene.GetScenePresence(id)) != null) + position = avatar.AbsolutePosition; + else if (ChatTypeEnum.Region == type) + position = CenterOfRegion; + else + return; + + DeliverMessage(type, channel, name, id, msg, position); + } + + /// + /// This method scans over the objects which registered an interest in listen callbacks. + /// For everyone it finds, it checks if it fits the given filter. If it does, then + /// enqueue the message for delivery to the objects listen event handler. + /// The enqueued ListenerInfo no longer has filter values, but the actually trigged values. + /// Objects that do an llSay have their messages delivered here and for nearby avatars, + /// the OnChatFromClient event is used. + /// + /// type of delvery (whisper,say,shout or regionwide) + /// channel to sent on + /// name of sender (object or avatar) + /// key of sender (object or avatar) + /// msg to sent + public void DeliverMessage(ChatTypeEnum type, int channel, string name, UUID id, string msg, Vector3 position) + { + // m_log.DebugFormat("[WorldComm] got[2] type {0}, channel {1}, name {2}, id {3}, msg {4}", + // type, channel, name, id, msg); + + // Determine which listen event filters match the given set of arguments, this results + // in a limited set of listeners, each belonging a host. If the host is in range, add them + // to the pending queue. + foreach (ListenerInfo li in m_listenerManager.GetListeners(UUID.Zero, channel, name, id, msg)) + { + // Dont process if this message is from yourself! + if (li.GetHostID().Equals(id)) + continue; + + SceneObjectPart sPart = m_scene.GetSceneObjectPart(li.GetHostID()); + if (sPart == null) + continue; + + double dis = Util.GetDistanceTo(sPart.AbsolutePosition, position); + switch (type) + { + case ChatTypeEnum.Whisper: + if (dis < m_whisperdistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; + + case ChatTypeEnum.Say: + if (dis < m_saydistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; + + case ChatTypeEnum.Shout: + if (dis < m_shoutdistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; + + case ChatTypeEnum.Region: + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + break; + } + } + } + + /// + /// Are there any listen events ready to be dispatched? + /// + /// boolean indication + public bool HasMessages() + { + return (m_pending.Count > 0); + } + + /// + /// Pop the first availlable listen event from the queue + /// + /// ListenerInfo with filter filled in + public IWorldCommListenerInfo GetNextMessage() + { + ListenerInfo li = null; + + lock (m_pending.SyncRoot) + { + li = (ListenerInfo) m_pending.Dequeue(); + } + + return li; + } + + #endregion + + /******************************************************************** + * + * Listener Stuff + * + * *****************************************************************/ + + private void DeliverClientMessage(Object sender, OSChatMessage e) + { + if (null != e.Sender) + DeliverMessage(e.Type, e.Channel, e.Sender.Name, e.Sender.AgentId, e.Message, e.Position); + else + DeliverMessage(e.Type, e.Channel, e.From, UUID.Zero, e.Message, e.Position); + } + + public Object[] GetSerializationData(UUID itemID) + { + return m_listenerManager.GetSerializationData(itemID); + } + + public void CreateFromData(uint localID, UUID itemID, UUID hostID, + Object[] data) + { + m_listenerManager.AddFromData(localID, itemID, hostID, data); + } + } + + public class ListenerManager + { + private Dictionary> m_listeners = new Dictionary>(); + private int m_maxlisteners; + private int m_maxhandles; + private int m_curlisteners; + + public ListenerManager(int maxlisteners, int maxhandles) + { + m_maxlisteners = maxlisteners; + m_maxhandles = maxhandles; + m_curlisteners = 0; + } + + public int AddListener(uint localID, UUID itemID, UUID hostID, int channel, string name, UUID id, string msg) + { + // do we already have a match on this particular filter event? + List coll = GetListeners(itemID, channel, name, id, msg); + + if (coll.Count > 0) + { + // special case, called with same filter settings, return same handle + // (2008-05-02, tested on 1.21.1 server, still holds) + return coll[0].GetHandle(); + } + + if (m_curlisteners < m_maxlisteners) + { + int newHandle = GetNewHandle(itemID); + + if (newHandle > 0) + { + ListenerInfo li = new ListenerInfo(newHandle, localID, itemID, hostID, channel, name, id, msg); + + lock (m_listeners) + { + List listeners; + if (!m_listeners.TryGetValue(channel,out listeners)) + { + listeners = new List(); + m_listeners.Add(channel, listeners); + } + listeners.Add(li); + m_curlisteners++; + } + + return newHandle; + } + } + return -1; + } + + public void Remove(UUID itemID, int handle) + { + lock (m_listeners) + { + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle().Equals(handle)) + { + lis.Value.Remove(li); + if (lis.Value.Count == 0) + { + m_listeners.Remove(lis.Key); + m_curlisteners--; + } + // there should be only one, so we bail out early + return; + } + } + } + } + } + + public void DeleteListener(UUID itemID) + { + List emptyChannels = new List(); + List removedListeners = new List(); + + lock (m_listeners) + { + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID)) + { + // store them first, else the enumerated bails on us + removedListeners.Add(li); + } + } + foreach (ListenerInfo li in removedListeners) + { + lis.Value.Remove(li); + m_curlisteners--; + } + removedListeners.Clear(); + if (lis.Value.Count == 0) + { + // again, store first, remove later + emptyChannels.Add(lis.Key); + } + } + foreach (int channel in emptyChannels) + { + m_listeners.Remove(channel); + } + } + } + + public void Activate(UUID itemID, int handle) + { + lock (m_listeners) + { + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle() == handle) + { + li.Activate(); + // only one, bail out + return; + } + } + } + } + } + + public void Dectivate(UUID itemID, int handle) + { + lock (m_listeners) + { + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle() == handle) + { + li.Deactivate(); + // only one, bail out + return; + } + } + } + } + } + + // non-locked access, since its always called in the context of the lock + private int GetNewHandle(UUID itemID) + { + List handles = new List(); + + // build a list of used keys for this specific itemID... + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID)) + handles.Add(li.GetHandle()); + } + } + + // Note: 0 is NOT a valid handle for llListen() to return + for (int i = 1; i <= m_maxhandles; i++) + { + if (!handles.Contains(i)) + return i; + } + + return -1; + } + + // Theres probably a more clever and efficient way to + // do this, maybe with regex. + // PM2008: Ha, one could even be smart and define a specialized Enumerator. + public List GetListeners(UUID itemID, int channel, string name, UUID id, string msg) + { + List collection = new List(); + + lock (m_listeners) + { + List listeners; + if (!m_listeners.TryGetValue(channel,out listeners)) + { + return collection; + } + + foreach (ListenerInfo li in listeners) + { + if (!li.IsActive()) + { + continue; + } + if (!itemID.Equals(UUID.Zero) && !li.GetItemID().Equals(itemID)) + { + continue; + } + if (li.GetName().Length > 0 && !li.GetName().Equals(name)) + { + continue; + } + if (!li.GetID().Equals(UUID.Zero) && !li.GetID().Equals(id)) + { + continue; + } + if (li.GetMessage().Length > 0 && !li.GetMessage().Equals(msg)) + { + continue; + } + collection.Add(li); + } + } + return collection; + } + + public Object[] GetSerializationData(UUID itemID) + { + List data = new List(); + + foreach (List list in m_listeners.Values) + { + foreach (ListenerInfo l in list) + { + if (l.GetItemID() == itemID) + data.AddRange(l.GetSerializationData()); + } + } + return (Object[])data.ToArray(); + } + + public void AddFromData(uint localID, UUID itemID, UUID hostID, + Object[] data) + { + int idx = 0; + Object[] item = new Object[6]; + + while (idx < data.Length) + { + Array.Copy(data, idx, item, 0, 6); + + ListenerInfo info = + ListenerInfo.FromData(localID, itemID, hostID, item); + + if (!m_listeners.ContainsKey((int)item[2])) + m_listeners.Add((int)item[2], new List()); + m_listeners[(int)item[2]].Add(info); + + idx+=6; + } + } + } + + public class ListenerInfo: IWorldCommListenerInfo + { + private bool m_active; // Listener is active or not + private int m_handle; // Assigned handle of this listener + private uint m_localID; // Local ID from script engine + private UUID m_itemID; // ID of the host script engine + private UUID m_hostID; // ID of the host/scene part + private int m_channel; // Channel + private UUID m_id; // ID to filter messages from + private string m_name; // Object name to filter messages from + private string m_message; // The message + + public ListenerInfo(int handle, uint localID, UUID ItemID, UUID hostID, int channel, string name, UUID id, string message) + { + Initialise(handle, localID, ItemID, hostID, channel, name, id, message); + } + + public ListenerInfo(ListenerInfo li, string name, UUID id, string message) + { + Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, li.m_channel, name, id, message); + } + + private void Initialise(int handle, uint localID, UUID ItemID, UUID hostID, int channel, string name, + UUID id, string message) + { + m_active = true; + m_handle = handle; + m_localID = localID; + m_itemID = ItemID; + m_hostID = hostID; + m_channel = channel; + m_name = name; + m_id = id; + m_message = message; + } + + public Object[] GetSerializationData() + { + Object[] data = new Object[6]; + + data[0] = m_active; + data[1] = m_handle; + data[2] = m_channel; + data[3] = m_name; + data[4] = m_id; + data[5] = m_message; + + return data; + } + + public static ListenerInfo FromData(uint localID, UUID ItemID, UUID hostID, Object[] data) + { + ListenerInfo linfo = new ListenerInfo((int)data[1], localID, + ItemID, hostID, (int)data[2], (string)data[3], + (UUID)data[4], (string)data[5]); + linfo.m_active=(bool)data[0]; + + return linfo; + } + + public UUID GetItemID() + { + return m_itemID; + } + + public UUID GetHostID() + { + return m_hostID; + } + + public int GetChannel() + { + return m_channel; + } + + public uint GetLocalID() + { + return m_localID; + } + + public int GetHandle() + { + return m_handle; + } + + public string GetMessage() + { + return m_message; + } + + public string GetName() + { + return m_name; + } + + public bool IsActive() + { + return m_active; + } + + public void Deactivate() + { + m_active = false; + } + + public void Activate() + { + m_active = true; + } + + public UUID GetID() + { + return m_id; + } + } +} diff --git a/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs new file mode 100644 index 0000000..942c130 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs @@ -0,0 +1,726 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +/***************************************************** + * + * XMLRPCModule + * + * Module for accepting incoming communications from + * external XMLRPC client and calling a remote data + * procedure for a registered data channel/prim. + * + * + * 1. On module load, open a listener port + * 2. Attach an XMLRPC handler + * 3. When a request is received: + * 3.1 Parse into components: channel key, int, string + * 3.2 Look up registered channel listeners + * 3.3 Call the channel (prim) remote data method + * 3.4 Capture the response (llRemoteDataReply) + * 3.5 Return response to client caller + * 3.6 If no response from llRemoteDataReply within + * RemoteReplyScriptTimeout, generate script timeout fault + * + * Prims in script must: + * 1. Open a remote data channel + * 1.1 Generate a channel ID + * 1.2 Register primid,channelid pair with module + * 2. Implement the remote data procedure handler + * + * llOpenRemoteDataChannel + * llRemoteDataReply + * remote_data(integer type, key channel, key messageid, string sender, integer ival, string sval) + * llCloseRemoteDataChannel + * + * **************************************************/ + +namespace OpenSim.Region.CoreModules.Scripting.XMLRPC +{ + public class XMLRPCModule : IRegionModule, IXMLRPC + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_name = "XMLRPCModule"; + + // + private Dictionary m_openChannels; + private Dictionary m_pendingSRDResponses; + private int m_remoteDataPort = 0; + + private Dictionary m_rpcPending; + private Dictionary m_rpcPendingResponses; + private List m_scenes = new List(); + private int RemoteReplyScriptTimeout = 9000; + private int RemoteReplyScriptWait = 300; + private object XMLRPCListLock = new object(); + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + // We need to create these early because the scripts might be calling + // But since this gets called for every region, we need to make sure they + // get called only one time (or we lose any open channels) + if (null == m_openChannels) + { + m_openChannels = new Dictionary(); + m_rpcPending = new Dictionary(); + m_rpcPendingResponses = new Dictionary(); + m_pendingSRDResponses = new Dictionary(); + + try + { + m_remoteDataPort = config.Configs["Network"].GetInt("remoteDataPort", m_remoteDataPort); + } + catch (Exception) + { + } + } + + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + + scene.RegisterModuleInterface(this); + } + } + + public void PostInitialise() + { + if (IsEnabled()) + { + // Start http server + // Attach xmlrpc handlers + m_log.Info("[REMOTE_DATA]: " + + "Starting XMLRPC Server on port " + m_remoteDataPort + " for llRemoteData commands."); + BaseHttpServer httpServer = new BaseHttpServer((uint) m_remoteDataPort); + httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData); + httpServer.Start(); + } + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + #region IXMLRPC Members + + public bool IsEnabled() + { + return (m_remoteDataPort > 0); + } + + /********************************************** + * OpenXMLRPCChannel + * + * Generate a UUID channel key and add it and + * the prim id to dictionary + * + * A custom channel key can be proposed. + * Otherwise, passing UUID.Zero will generate + * and return a random channel + * + * First check if there is a channel assigned for + * this itemID. If there is, then someone called + * llOpenRemoteDataChannel twice. Just return the + * original channel. Other option is to delete the + * current channel and assign a new one. + * + * ********************************************/ + + public UUID OpenXMLRPCChannel(uint localID, UUID itemID, UUID channelID) + { + UUID newChannel = UUID.Zero; + + // This should no longer happen, but the check is reasonable anyway + if (null == m_openChannels) + { + m_log.Warn("[RemoteDataReply] Attempt to open channel before initialization is complete"); + return newChannel; + } + + //Is a dupe? + foreach (RPCChannelInfo ci in m_openChannels.Values) + { + if (ci.GetItemID().Equals(itemID)) + { + // return the original channel ID for this item + newChannel = ci.GetChannelID(); + break; + } + } + + if (newChannel == UUID.Zero) + { + newChannel = (channelID == UUID.Zero) ? UUID.Random() : channelID; + RPCChannelInfo rpcChanInfo = new RPCChannelInfo(localID, itemID, newChannel); + lock (XMLRPCListLock) + { + m_openChannels.Add(newChannel, rpcChanInfo); + } + } + + return newChannel; + } + + // Delete channels based on itemID + // for when a script is deleted + public void DeleteChannels(UUID itemID) + { + if (m_openChannels != null) + { + ArrayList tmp = new ArrayList(); + + lock (XMLRPCListLock) + { + foreach (RPCChannelInfo li in m_openChannels.Values) + { + if (li.GetItemID().Equals(itemID)) + { + tmp.Add(itemID); + } + } + + IEnumerator tmpEnumerator = tmp.GetEnumerator(); + while (tmpEnumerator.MoveNext()) + m_openChannels.Remove((UUID) tmpEnumerator.Current); + } + } + } + + /********************************************** + * Remote Data Reply + * + * Response to RPC message + * + *********************************************/ + + public void RemoteDataReply(string channel, string message_id, string sdata, int idata) + { + UUID message_key = new UUID(message_id); + UUID channel_key = new UUID(channel); + + RPCRequestInfo rpcInfo = null; + + if (message_key == UUID.Zero) + { + foreach (RPCRequestInfo oneRpcInfo in m_rpcPendingResponses.Values) + if (oneRpcInfo.GetChannelKey() == channel_key) + rpcInfo = oneRpcInfo; + } + else + { + m_rpcPendingResponses.TryGetValue(message_key, out rpcInfo); + } + + if (rpcInfo != null) + { + rpcInfo.SetStrRetval(sdata); + rpcInfo.SetIntRetval(idata); + rpcInfo.SetProcessed(true); + m_rpcPendingResponses.Remove(message_key); + } + else + { + m_log.Warn("[RemoteDataReply]: Channel or message_id not found"); + } + } + + /********************************************** + * CloseXMLRPCChannel + * + * Remove channel from dictionary + * + *********************************************/ + + public void CloseXMLRPCChannel(UUID channelKey) + { + if (m_openChannels.ContainsKey(channelKey)) + m_openChannels.Remove(channelKey); + } + + + public bool hasRequests() + { + lock (XMLRPCListLock) + { + if (m_rpcPending != null) + return (m_rpcPending.Count > 0); + else + return false; + } + } + + public IXmlRpcRequestInfo GetNextCompletedRequest() + { + if (m_rpcPending != null) + { + lock (XMLRPCListLock) + { + foreach (UUID luid in m_rpcPending.Keys) + { + RPCRequestInfo tmpReq; + + if (m_rpcPending.TryGetValue(luid, out tmpReq)) + { + if (!tmpReq.IsProcessed()) return tmpReq; + } + } + } + } + return null; + } + + public void RemoveCompletedRequest(UUID id) + { + lock (XMLRPCListLock) + { + RPCRequestInfo tmp; + if (m_rpcPending.TryGetValue(id, out tmp)) + { + m_rpcPending.Remove(id); + m_rpcPendingResponses.Add(id, tmp); + } + else + { + Console.WriteLine("UNABLE TO REMOVE COMPLETED REQUEST"); + } + } + } + + public UUID SendRemoteData(uint localID, UUID itemID, string channel, string dest, int idata, string sdata) + { + SendRemoteDataRequest req = new SendRemoteDataRequest( + localID, itemID, channel, dest, idata, sdata + ); + m_pendingSRDResponses.Add(req.GetReqID(), req); + req.Process(); + return req.ReqID; + } + + public IServiceRequest GetNextCompletedSRDRequest() + { + if (m_pendingSRDResponses != null) + { + lock (XMLRPCListLock) + { + foreach (UUID luid in m_pendingSRDResponses.Keys) + { + SendRemoteDataRequest tmpReq; + + if (m_pendingSRDResponses.TryGetValue(luid, out tmpReq)) + { + if (tmpReq.Finished) + return tmpReq; + } + } + } + } + return null; + } + + public void RemoveCompletedSRDRequest(UUID id) + { + lock (XMLRPCListLock) + { + SendRemoteDataRequest tmpReq; + if (m_pendingSRDResponses.TryGetValue(id, out tmpReq)) + { + m_pendingSRDResponses.Remove(id); + } + } + } + + public void CancelSRDRequests(UUID itemID) + { + if (m_pendingSRDResponses != null) + { + lock (XMLRPCListLock) + { + foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values) + { + if (li.ItemID.Equals(itemID)) + m_pendingSRDResponses.Remove(li.GetReqID()); + } + } + } + } + + #endregion + + public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request) + { + XmlRpcResponse response = new XmlRpcResponse(); + + Hashtable requestData = (Hashtable) request.Params[0]; + bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") && + requestData.Contains("StringValue")); + + if (GoodXML) + { + UUID channel = new UUID((string) requestData["Channel"]); + RPCChannelInfo rpcChanInfo; + if (m_openChannels.TryGetValue(channel, out rpcChanInfo)) + { + string intVal = Convert.ToInt32(requestData["IntValue"]).ToString(); + string strVal = (string) requestData["StringValue"]; + + RPCRequestInfo rpcInfo; + + lock (XMLRPCListLock) + { + rpcInfo = + new RPCRequestInfo(rpcChanInfo.GetLocalID(), rpcChanInfo.GetItemID(), channel, strVal, + intVal); + m_rpcPending.Add(rpcInfo.GetMessageID(), rpcInfo); + } + + int timeoutCtr = 0; + + while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout)) + { + Thread.Sleep(RemoteReplyScriptWait); + timeoutCtr += RemoteReplyScriptWait; + } + if (rpcInfo.IsProcessed()) + { + Hashtable param = new Hashtable(); + param["StringValue"] = rpcInfo.GetStrRetval(); + param["IntValue"] = rpcInfo.GetIntRetval(); + + ArrayList parameters = new ArrayList(); + parameters.Add(param); + + response.Value = parameters; + rpcInfo = null; + } + else + { + response.SetFault(-1, "Script timeout"); + rpcInfo = null; + } + } + else + { + response.SetFault(-1, "Invalid channel"); + } + } + + return response; + } + } + + public class RPCRequestInfo: IXmlRpcRequestInfo + { + private UUID m_ChannelKey; + private string m_IntVal; + private UUID m_ItemID; + private uint m_localID; + private UUID m_MessageID; + private bool m_processed; + private int m_respInt; + private string m_respStr; + private string m_StrVal; + + public RPCRequestInfo(uint localID, UUID itemID, UUID channelKey, string strVal, string intVal) + { + m_localID = localID; + m_StrVal = strVal; + m_IntVal = intVal; + m_ItemID = itemID; + m_ChannelKey = channelKey; + m_MessageID = UUID.Random(); + m_processed = false; + m_respStr = String.Empty; + m_respInt = 0; + } + + public bool IsProcessed() + { + return m_processed; + } + + public UUID GetChannelKey() + { + return m_ChannelKey; + } + + public void SetProcessed(bool processed) + { + m_processed = processed; + } + + public void SetStrRetval(string resp) + { + m_respStr = resp; + } + + public string GetStrRetval() + { + return m_respStr; + } + + public void SetIntRetval(int resp) + { + m_respInt = resp; + } + + public int GetIntRetval() + { + return m_respInt; + } + + public uint GetLocalID() + { + return m_localID; + } + + public UUID GetItemID() + { + return m_ItemID; + } + + public string GetStrVal() + { + return m_StrVal; + } + + public int GetIntValue() + { + return int.Parse(m_IntVal); + } + + public UUID GetMessageID() + { + return m_MessageID; + } + } + + public class RPCChannelInfo + { + private UUID m_ChannelKey; + private UUID m_itemID; + private uint m_localID; + + public RPCChannelInfo(uint localID, UUID itemID, UUID channelID) + { + m_ChannelKey = channelID; + m_localID = localID; + m_itemID = itemID; + } + + public UUID GetItemID() + { + return m_itemID; + } + + public UUID GetChannelID() + { + return m_ChannelKey; + } + + public uint GetLocalID() + { + return m_localID; + } + } + + public class SendRemoteDataRequest: IServiceRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public string Channel; + public string DestURL; + private bool _finished; + public bool Finished + { + get { return _finished; } + set { _finished = value; } + } + private Thread httpThread; + public int Idata; + private UUID _itemID; + public UUID ItemID + { + get { return _itemID; } + set { _itemID = value; } + } + private uint _localID; + public uint LocalID + { + get { return _localID; } + set { _localID = value; } + } + private UUID _reqID; + public UUID ReqID + { + get { return _reqID; } + set { _reqID = value; } + } + public XmlRpcRequest Request; + public int ResponseIdata; + public string ResponseSdata; + public string Sdata; + + public SendRemoteDataRequest(uint localID, UUID itemID, string channel, string dest, int idata, string sdata) + { + this.Channel = channel; + DestURL = dest; + this.Idata = idata; + this.Sdata = sdata; + ItemID = itemID; + LocalID = localID; + + ReqID = UUID.Random(); + } + + public void Process() + { + httpThread = new Thread(SendRequest); + httpThread.Name = "HttpRequestThread"; + httpThread.Priority = ThreadPriority.BelowNormal; + httpThread.IsBackground = true; + _finished = false; + httpThread.Start(); + ThreadTracker.Add(httpThread); + } + + /* + * TODO: More work on the response codes. Right now + * returning 200 for success or 499 for exception + */ + + public void SendRequest() + { + Hashtable param = new Hashtable(); + + // Check if channel is an UUID + // if not, use as method name + UUID parseUID; + string mName = "llRemoteData"; + if ((Channel != null) && (Channel != "")) + if (!UUID.TryParse(Channel, out parseUID)) + mName = Channel; + else + param["Channel"] = Channel; + + param["StringValue"] = Sdata; + param["IntValue"] = Convert.ToString(Idata); + + ArrayList parameters = new ArrayList(); + parameters.Add(param); + XmlRpcRequest req = new XmlRpcRequest(mName, parameters); + try + { + XmlRpcResponse resp = req.Send(DestURL, 30000); + if (resp != null) + { + Hashtable respParms; + if (resp.Value.GetType().Equals(typeof(System.Collections.Hashtable))) + { + respParms = (Hashtable) resp.Value; + } + else + { + ArrayList respData = (ArrayList) resp.Value; + respParms = (Hashtable) respData[0]; + } + if (respParms != null) + { + if (respParms.Contains("StringValue")) + { + Sdata = (string) respParms["StringValue"]; + } + if (respParms.Contains("IntValue")) + { + Idata = Convert.ToInt32((string) respParms["IntValue"]); + } + if (respParms.Contains("faultString")) + { + Sdata = (string) respParms["faultString"]; + } + if (respParms.Contains("faultCode")) + { + Idata = Convert.ToInt32(respParms["faultCode"]); + } + } + } + } + catch (Exception we) + { + Sdata = we.Message; + m_log.Warn("[SendRemoteDataRequest]: Request failed"); + m_log.Warn(we.StackTrace); + } + + _finished = true; + } + + public void Stop() + { + try + { + httpThread.Abort(); + } + catch (Exception) + { + } + } + + public UUID GetReqID() + { + return ReqID; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveConstants.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveConstants.cs new file mode 100644 index 0000000..179d1a2 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveConstants.cs @@ -0,0 +1,128 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Constants for the archiving module + /// + public class ArchiveConstants + { + /// + /// The location of the archive control file + /// + public static readonly string CONTROL_FILE_PATH = "archive.xml"; + + /// + /// Path for the assets held in an archive + /// + public static readonly string ASSETS_PATH = "assets/"; + + /// + /// Path for the assets metadata file + /// + //public static readonly string ASSETS_METADATA_PATH = "assets.xml"; + + /// + /// Path for the prims file + /// + public static readonly string OBJECTS_PATH = "objects/"; + + /// + /// Path for terrains. Technically these may be assets, but I think it's quite nice to split them out. + /// + public static readonly string TERRAINS_PATH = "terrains/"; + + /// + /// Path for region settings. + /// + public static readonly string SETTINGS_PATH = "settings/"; + + /// + /// The character the separates the uuid from extension information in an archived asset filename + /// + public static readonly string ASSET_EXTENSION_SEPARATOR = "_"; + + /// + /// Extensions used for asset types in the archive + /// + public static readonly IDictionary ASSET_TYPE_TO_EXTENSION = new Dictionary(); + public static readonly IDictionary EXTENSION_TO_ASSET_TYPE = new Dictionary(); + + static ArchiveConstants() + { + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Animation] = ASSET_EXTENSION_SEPARATOR + "animation.bvh"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Bodypart] = ASSET_EXTENSION_SEPARATOR + "bodypart.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.CallingCard] = ASSET_EXTENSION_SEPARATOR + "callingcard.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Clothing] = ASSET_EXTENSION_SEPARATOR + "clothing.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Folder] = ASSET_EXTENSION_SEPARATOR + "folder.txt"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Gesture] = ASSET_EXTENSION_SEPARATOR + "gesture.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.ImageJPEG] = ASSET_EXTENSION_SEPARATOR + "image.jpg"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.ImageTGA] = ASSET_EXTENSION_SEPARATOR + "image.tga"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Landmark] = ASSET_EXTENSION_SEPARATOR + "landmark.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.LostAndFoundFolder] = ASSET_EXTENSION_SEPARATOR + "lostandfoundfolder.txt"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.LSLBytecode] = ASSET_EXTENSION_SEPARATOR + "bytecode.lso"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.LSLText] = ASSET_EXTENSION_SEPARATOR + "script.lsl"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Notecard] = ASSET_EXTENSION_SEPARATOR + "notecard.txt"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Object] = ASSET_EXTENSION_SEPARATOR + "object.xml"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.RootFolder] = ASSET_EXTENSION_SEPARATOR + "rootfolder.txt"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Simstate] = ASSET_EXTENSION_SEPARATOR + "simstate.bin"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.SnapshotFolder] = ASSET_EXTENSION_SEPARATOR + "snapshotfolder.txt"; // Not sure if we'll ever see this + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Sound] = ASSET_EXTENSION_SEPARATOR + "sound.ogg"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.SoundWAV] = ASSET_EXTENSION_SEPARATOR + "sound.wav"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Texture] = ASSET_EXTENSION_SEPARATOR + "texture.jp2"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.TextureTGA] = ASSET_EXTENSION_SEPARATOR + "texture.tga"; + ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.TrashFolder] = ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"; // Not sure if we'll ever see this + + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "animation.bvh"] = (sbyte)AssetType.Animation; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bodypart.txt"] = (sbyte)AssetType.Bodypart; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "callingcard.txt"] = (sbyte)AssetType.CallingCard; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "clothing.txt"] = (sbyte)AssetType.Clothing; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "folder.txt"] = (sbyte)AssetType.Folder; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "gesture.txt"] = (sbyte)AssetType.Gesture; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "image.jpg"] = (sbyte)AssetType.ImageJPEG; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "image.tga"] = (sbyte)AssetType.ImageTGA; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "landmark.txt"] = (sbyte)AssetType.Landmark; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "lostandfoundfolder.txt"] = (sbyte)AssetType.LostAndFoundFolder; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bytecode.lso"] = (sbyte)AssetType.LSLBytecode; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "script.lsl"] = (sbyte)AssetType.LSLText; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "notecard.txt"] = (sbyte)AssetType.Notecard; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "object.xml"] = (sbyte)AssetType.Object; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "rootfolder.txt"] = (sbyte)AssetType.RootFolder; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "simstate.bin"] = (sbyte)AssetType.Simstate; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "snapshotfolder.txt"] = (sbyte)AssetType.SnapshotFolder; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "sound.ogg"] = (sbyte)AssetType.Sound; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "sound.wav"] = (sbyte)AssetType.SoundWAV; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.jp2"] = (sbyte)AssetType.Texture; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.tga"] = (sbyte)AssetType.TextureTGA; + EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"] = (sbyte)AssetType.TrashFolder; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs new file mode 100644 index 0000000..3218abc --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveReadRequest.cs @@ -0,0 +1,460 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Xml; +using System.Net; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Terrain; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Handles an individual archive read request + /// + public class ArchiveReadRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static System.Text.ASCIIEncoding m_asciiEncoding = new System.Text.ASCIIEncoding(); + + private Scene m_scene; + private Stream m_loadStream; + private string m_errorMessage; + + /// + /// Used to cache lookups for valid uuids. + /// + private IDictionary m_validUserUuids = new Dictionary(); + + public ArchiveReadRequest(Scene scene, string loadPath) + { + m_scene = scene; + m_loadStream = new GZipStream(GetStream(loadPath), CompressionMode.Decompress); + m_errorMessage = String.Empty; + } + + public ArchiveReadRequest(Scene scene, Stream loadStream) + { + m_scene = scene; + m_loadStream = loadStream; + } + + /// + /// Dearchive the region embodied in this request. + /// + public void DearchiveRegion() + { + // The same code can handle dearchiving 0.1 and 0.2 OpenSim Archive versions + DearchiveRegion0DotStar(); + } + + private void DearchiveRegion0DotStar() + { + int successfulAssetRestores = 0; + int failedAssetRestores = 0; + List serialisedSceneObjects = new List(); + + try + { + TarArchiveReader archive = new TarArchiveReader(m_loadStream); + + //AssetsDearchiver dearchiver = new AssetsDearchiver(m_scene.AssetCache); + + string filePath = "ERROR"; + + byte[] data; + TarArchiveReader.TarEntryType entryType; + + while ((data = archive.ReadEntry(out filePath, out entryType)) != null) + { + //m_log.DebugFormat( + // "[ARCHIVER]: Successfully read {0} ({1} bytes)}", filePath, data.Length); + if (TarArchiveReader.TarEntryType.TYPE_DIRECTORY == entryType) + { + m_log.WarnFormat("[ARCHIVER]: Ignoring directory entry {0}", + filePath); + } + else if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + { + serialisedSceneObjects.Add(m_asciiEncoding.GetString(data)); + } +// else if (filePath.Equals(ArchiveConstants.ASSETS_METADATA_PATH)) +// { +// string xml = m_asciiEncoding.GetString(data); +// dearchiver.AddAssetMetadata(xml); +// } + else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + if (LoadAsset(filePath, data)) + successfulAssetRestores++; + else + failedAssetRestores++; + } + else if (filePath.StartsWith(ArchiveConstants.TERRAINS_PATH)) + { + LoadTerrain(filePath, data); + } + else if (filePath.StartsWith(ArchiveConstants.SETTINGS_PATH)) + { + LoadRegionSettings(filePath, data); + } + } + + //m_log.Debug("[ARCHIVER]: Reached end of archive"); + + archive.Close(); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ARCHIVER]: Error loading oar file. Exception was: {0}", e); + m_errorMessage += e.ToString(); + m_scene.EventManager.TriggerOarFileLoaded(m_errorMessage); + return; + } + + m_log.InfoFormat("[ARCHIVER]: Restored {0} assets", successfulAssetRestores); + + if (failedAssetRestores > 0) + { + m_log.ErrorFormat("[ARCHIVER]: Failed to load {0} assets", failedAssetRestores); + m_errorMessage += String.Format("Failed to load {0} assets", failedAssetRestores); + } + + m_log.Info("[ARCHIVER]: Clearing all existing scene objects"); + m_scene.DeleteAllSceneObjects(); + + // Reload serialized prims + m_log.InfoFormat("[ARCHIVER]: Loading {0} scene objects. Please wait.", serialisedSceneObjects.Count); + + IRegionSerialiserModule serialiser = m_scene.RequestModuleInterface(); + ICollection sceneObjects = new List(); + + foreach (string serialisedSceneObject in serialisedSceneObjects) + { + SceneObjectGroup sceneObject = serialiser.DeserializeGroupFromXml2(serialisedSceneObject); + + // For now, give all incoming scene objects new uuids. This will allow scenes to be cloned + // on the same region server and multiple examples a single object archive to be imported + // to the same scene (when this is possible). + sceneObject.ResetIDs(); + + // Try to retain the original creator/owner/lastowner if their uuid is present on this grid + // otherwise, use the master avatar uuid instead + UUID masterAvatarId = m_scene.RegionInfo.MasterAvatarAssignedUUID; + + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + masterAvatarId = m_scene.RegionInfo.EstateSettings.EstateOwner; + + foreach (SceneObjectPart part in sceneObject.Children.Values) + { + if (!resolveUserUuid(part.CreatorID)) + part.CreatorID = masterAvatarId; + + if (!resolveUserUuid(part.OwnerID)) + part.OwnerID = masterAvatarId; + + if (!resolveUserUuid(part.LastOwnerID)) + part.LastOwnerID = masterAvatarId; + + // And zap any troublesome sit target information + part.SitTargetOrientation = new Quaternion(0, 0, 0, 1); + part.SitTargetPosition = new Vector3(0, 0, 0); + + // Fix ownership/creator of inventory items + // Not doing so results in inventory items + // being no copy/no mod for everyone + TaskInventoryDictionary inv = part.TaskInventory; + foreach (KeyValuePair kvp in inv) + { + if (!resolveUserUuid(kvp.Value.OwnerID)) + { + kvp.Value.OwnerID = masterAvatarId; + } + if (!resolveUserUuid(kvp.Value.CreatorID)) + { + kvp.Value.CreatorID = masterAvatarId; + } + } + } + + if (m_scene.AddRestoredSceneObject(sceneObject, true, false)) + { + sceneObjects.Add(sceneObject); + } + } + + m_log.InfoFormat("[ARCHIVER]: Restored {0} scene objects to the scene", sceneObjects.Count); + + int ignoredObjects = serialisedSceneObjects.Count - sceneObjects.Count; + + if (ignoredObjects > 0) + m_log.WarnFormat("[ARCHIVER]: Ignored {0} scene objects that already existed in the scene", ignoredObjects); + + m_log.InfoFormat("[ARCHIVER]: Successfully loaded archive"); + + m_log.Debug("[ARCHIVER]: Starting scripts"); + + foreach (SceneObjectGroup sceneObject in sceneObjects) + { + sceneObject.CreateScriptInstances(0, true, m_scene.DefaultScriptEngine, 0); + } + + m_scene.EventManager.TriggerOarFileLoaded(m_errorMessage); + } + + /// + /// Look up the given user id to check whether it's one that is valid for this grid. + /// + /// + /// + private bool resolveUserUuid(UUID uuid) + { + if (!m_validUserUuids.ContainsKey(uuid)) + { + CachedUserInfo profile = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(uuid); + if (profile != null && profile.UserProfile != null) + m_validUserUuids.Add(uuid, true); + else + m_validUserUuids.Add(uuid, false); + } + + if (m_validUserUuids[uuid]) + return true; + else + return false; + } + + /// + /// Load an asset + /// + /// + /// + /// true if asset was successfully loaded, false otherwise + private bool LoadAsset(string assetPath, byte[] data) + { + // Right now we're nastily obtaining the UUID from the filename + string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); + int i = filename.LastIndexOf(ArchiveConstants.ASSET_EXTENSION_SEPARATOR); + + if (i == -1) + { + m_log.ErrorFormat( + "[ARCHIVER]: Could not find extension information in asset path {0} since it's missing the separator {1}. Skipping", + assetPath, ArchiveConstants.ASSET_EXTENSION_SEPARATOR); + + return false; + } + + string extension = filename.Substring(i); + string uuid = filename.Remove(filename.Length - extension.Length); + + if (ArchiveConstants.EXTENSION_TO_ASSET_TYPE.ContainsKey(extension)) + { + sbyte assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension]; + + //m_log.DebugFormat("[ARCHIVER]: Importing asset {0}, type {1}", uuid, assetType); + + AssetBase asset = new AssetBase(new UUID(uuid), String.Empty); + asset.Metadata.Type = assetType; + asset.Data = data; + + m_scene.AssetCache.AddAsset(asset); + + /** + * Create layers on decode for image assets. This is likely to significantly increase the time to load archives so + * it might be best done when dearchive takes place on a separate thread + if (asset.Type=AssetType.Texture) + { + IJ2KDecoder cacheLayerDecode = scene.RequestModuleInterface(); + if (cacheLayerDecode != null) + cacheLayerDecode.syncdecode(asset.FullID, asset.Data); + } + */ + + return true; + } + else + { + m_log.ErrorFormat( + "[ARCHIVER]: Tried to dearchive data with path {0} with an unknown type extension {1}", + assetPath, extension); + + return false; + } + } + + /// + /// Load region settings data + /// + /// + /// + /// + /// true if settings were loaded successfully, false otherwise + /// + private bool LoadRegionSettings(string settingsPath, byte[] data) + { + RegionSettings loadedRegionSettings; + + try + { + loadedRegionSettings = RegionSettingsSerializer.Deserialize(data); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ARCHIVER]: Could not parse region settings file {0}. Ignoring. Exception was {1}", + settingsPath, e); + return false; + } + + RegionSettings currentRegionSettings = m_scene.RegionInfo.RegionSettings; + + currentRegionSettings.AgentLimit = loadedRegionSettings.AgentLimit; + currentRegionSettings.AllowDamage = loadedRegionSettings.AllowDamage; + currentRegionSettings.AllowLandJoinDivide = loadedRegionSettings.AllowLandJoinDivide; + currentRegionSettings.AllowLandResell = loadedRegionSettings.AllowLandResell; + currentRegionSettings.BlockFly = loadedRegionSettings.BlockFly; + currentRegionSettings.BlockShowInSearch = loadedRegionSettings.BlockShowInSearch; + currentRegionSettings.BlockTerraform = loadedRegionSettings.BlockTerraform; + currentRegionSettings.DisableCollisions = loadedRegionSettings.DisableCollisions; + currentRegionSettings.DisablePhysics = loadedRegionSettings.DisablePhysics; + currentRegionSettings.DisableScripts = loadedRegionSettings.DisableScripts; + currentRegionSettings.Elevation1NE = loadedRegionSettings.Elevation1NE; + currentRegionSettings.Elevation1NW = loadedRegionSettings.Elevation1NW; + currentRegionSettings.Elevation1SE = loadedRegionSettings.Elevation1SE; + currentRegionSettings.Elevation1SW = loadedRegionSettings.Elevation1SW; + currentRegionSettings.Elevation2NE = loadedRegionSettings.Elevation2NE; + currentRegionSettings.Elevation2NW = loadedRegionSettings.Elevation2NW; + currentRegionSettings.Elevation2SE = loadedRegionSettings.Elevation2SE; + currentRegionSettings.Elevation2SW = loadedRegionSettings.Elevation2SW; + currentRegionSettings.FixedSun = loadedRegionSettings.FixedSun; + currentRegionSettings.ObjectBonus = loadedRegionSettings.ObjectBonus; + currentRegionSettings.RestrictPushing = loadedRegionSettings.RestrictPushing; + currentRegionSettings.TerrainLowerLimit = loadedRegionSettings.TerrainLowerLimit; + currentRegionSettings.TerrainRaiseLimit = loadedRegionSettings.TerrainRaiseLimit; + currentRegionSettings.TerrainTexture1 = loadedRegionSettings.TerrainTexture1; + currentRegionSettings.TerrainTexture2 = loadedRegionSettings.TerrainTexture2; + currentRegionSettings.TerrainTexture3 = loadedRegionSettings.TerrainTexture3; + currentRegionSettings.TerrainTexture4 = loadedRegionSettings.TerrainTexture4; + currentRegionSettings.UseEstateSun = loadedRegionSettings.UseEstateSun; + currentRegionSettings.WaterHeight = loadedRegionSettings.WaterHeight; + + IEstateModule estateModule = m_scene.RequestModuleInterface(); + estateModule.sendRegionHandshakeToAll(); + + return true; + } + + /// + /// Load terrain data + /// + /// + /// + /// + /// true if terrain was resolved successfully, false otherwise. + /// + private bool LoadTerrain(string terrainPath, byte[] data) + { + ITerrainModule terrainModule = m_scene.RequestModuleInterface(); + + MemoryStream ms = new MemoryStream(data); + terrainModule.LoadFromStream(terrainPath, ms); + ms.Close(); + + m_log.DebugFormat("[ARCHIVER]: Restored terrain {0}", terrainPath); + + return true; + } + + /// + /// Resolve path to a working FileStream + /// + private Stream GetStream(string path) + { + try + { + if (File.Exists(path)) + { + return new FileStream(path, FileMode.Open); + } + else + { + Uri uri = new Uri(path); // throw exception if not valid URI + if (uri.Scheme == "file") + { + return new FileStream(uri.AbsolutePath, FileMode.Open); + } + else + { + if (uri.Scheme != "http") + throw new Exception(String.Format("Unsupported URI scheme ({0})", path)); + + // OK, now we know we have an HTTP URI to work with + + return URIFetch(uri); + } + } + } + catch (Exception e) + { + throw new Exception(String.Format("Unable to create file input stream for {0}: {1}", path, e)); + } + } + + private static Stream URIFetch(Uri uri) + { + HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri); + + // request.Credentials = credentials; + + request.ContentLength = 0; + + WebResponse response = request.GetResponse(); + Stream file = response.GetResponseStream(); + + if (response.ContentType != "application/x-oar") + throw new Exception(String.Format("{0} does not identify an OAR file", uri.ToString())); + + if (response.ContentLength == 0) + throw new Exception(String.Format("{0} returned an empty file", uri.ToString())); + + // return new BufferedStream(file, (int) response.ContentLength); + return new BufferedStream(file, 1000000); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestExecution.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestExecution.cs new file mode 100644 index 0000000..d3c2cd1 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestExecution.cs @@ -0,0 +1,161 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.World.Terrain; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Method called when all the necessary assets for an archive request have been received. + /// + public delegate void AssetsRequestCallback(IDictionary assetsFound, ICollection assetsNotFoundUuids); + + /// + /// Execute the write of an archive once we have received all the necessary data + /// + public class ArchiveWriteRequestExecution + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected ITerrainModule m_terrainModule; + protected IRegionSerialiserModule m_serialiser; + protected List m_sceneObjects; + protected Scene m_scene; + protected Stream m_saveStream; + + public ArchiveWriteRequestExecution( + List sceneObjects, + ITerrainModule terrainModule, + IRegionSerialiserModule serialiser, + Scene scene, + Stream saveStream) + { + m_sceneObjects = sceneObjects; + m_terrainModule = terrainModule; + m_serialiser = serialiser; + m_scene = scene; + m_saveStream = saveStream; + } + + protected internal void ReceivedAllAssets( + IDictionary assetsFound, ICollection assetsNotFoundUuids) + { + foreach (UUID uuid in assetsNotFoundUuids) + { + m_log.DebugFormat("[ARCHIVER]: Could not find asset {0}", uuid); + } + + m_log.InfoFormat( + "[ARCHIVER]: Received {0} of {1} assets requested", + assetsFound.Count, assetsFound.Count + assetsNotFoundUuids.Count); + + m_log.InfoFormat("[ARCHIVER]: Creating archive file. This may take some time."); + + TarArchiveWriter archive = new TarArchiveWriter(); + + // Write out control file + archive.AddFile(ArchiveConstants.CONTROL_FILE_PATH, Create0p2ControlFile()); + + // Write out region settings + string settingsPath + = String.Format("{0}{1}.xml", ArchiveConstants.SETTINGS_PATH, m_scene.RegionInfo.RegionName); + archive.AddFile(settingsPath, RegionSettingsSerializer.Serialize(m_scene.RegionInfo.RegionSettings)); + + // Write out terrain + string terrainPath + = String.Format("{0}{1}.r32", ArchiveConstants.TERRAINS_PATH, m_scene.RegionInfo.RegionName); + + MemoryStream ms = new MemoryStream(); + m_terrainModule.SaveToStream(terrainPath, ms); + archive.AddFile(terrainPath, ms.ToArray()); + ms.Close(); + + // Write out scene object metadata + foreach (SceneObjectGroup sceneObject in m_sceneObjects) + { + //m_log.DebugFormat("[ARCHIVER]: Saving {0} {1}, {2}", entity.Name, entity.UUID, entity.GetType()); + + Vector3 position = sceneObject.AbsolutePosition; + + string serializedObject = m_serialiser.SaveGroupToXml2(sceneObject); + string filename + = string.Format( + "{0}{1}_{2:000}-{3:000}-{4:000}__{5}.xml", + ArchiveConstants.OBJECTS_PATH, sceneObject.Name, + Math.Round(position.X), Math.Round(position.Y), Math.Round(position.Z), + sceneObject.UUID); + + archive.AddFile(filename, serializedObject); + } + + // Write out assets + AssetsArchiver assetsArchiver = new AssetsArchiver(assetsFound); + assetsArchiver.Archive(archive); + + archive.WriteTar(m_saveStream); + + m_log.InfoFormat("[ARCHIVER]: Wrote out OpenSimulator archive for {0}", m_scene.RegionInfo.RegionName); + + m_scene.EventManager.TriggerOarFileSaved(String.Empty); + } + + /// + /// Create the control file for a 0.2 version archive + /// + /// + public static string Create0p2ControlFile() + { + StringWriter sw = new StringWriter(); + XmlTextWriter xtw = new XmlTextWriter(sw); + xtw.Formatting = Formatting.Indented; + xtw.WriteStartDocument(); + xtw.WriteStartElement("archive"); + xtw.WriteAttributeString("major_version", "0"); + xtw.WriteAttributeString("minor_version", "2"); + xtw.WriteEndElement(); + + xtw.Flush(); + xtw.Close(); + + String s = sw.ToString(); + sw.Close(); + + return s; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestPreparation.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestPreparation.cs new file mode 100644 index 0000000..ee0ec69 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiveWriteRequestPreparation.cs @@ -0,0 +1,333 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.World.Terrain; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Prepare to write out an archive. + /// + public class ArchiveWriteRequestPreparation + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + protected Stream m_saveStream; + + /// + /// Used as a temporary store of an asset which represents an object. This can be a null if no appropriate + /// asset was found by the asset service. + /// + protected AssetBase m_requestedObjectAsset; + + /// + /// Signal whether we are currently waiting for the asset service to deliver an asset. + /// + protected bool m_waitingForObjectAsset; + + /// + /// Constructor + /// + public ArchiveWriteRequestPreparation(Scene scene, string savePath) + { + m_scene = scene; + m_saveStream = new GZipStream(new FileStream(savePath, FileMode.Create), CompressionMode.Compress); + } + + /// + /// Constructor. + /// + /// + /// The stream to which to save data. + public ArchiveWriteRequestPreparation(Scene scene, Stream saveStream) + { + m_scene = scene; + m_saveStream = saveStream; + } + + /// + /// The callback made when we request the asset for an object from the asset service. + /// + public void AssetRequestCallback(UUID assetID, AssetBase asset) + { + lock (this) + { + m_requestedObjectAsset = asset; + m_waitingForObjectAsset = false; + Monitor.Pulse(this); + } + } + + /// + /// Get an asset synchronously, potentially using an asynchronous callback. If the + /// asynchronous callback is used, we will wait for it to complete. + /// + /// + /// + protected AssetBase GetAsset(UUID uuid) + { + m_waitingForObjectAsset = true; + m_scene.AssetCache.GetAsset(uuid, AssetRequestCallback, true); + + // The asset cache callback can either + // + // 1. Complete on the same thread (if the asset is already in the cache) or + // 2. Come in via a different thread (if we need to go fetch it). + // + // The code below handles both these alternatives. + lock (this) + { + if (m_waitingForObjectAsset) + { + Monitor.Wait(this); + m_waitingForObjectAsset = false; + } + } + + return m_requestedObjectAsset; + } + + /// + /// Record the asset uuids embedded within the given script. + /// + /// + /// Dictionary in which to record the references + protected void GetScriptAssetUuids(UUID scriptUuid, IDictionary assetUuids) + { + AssetBase scriptAsset = GetAsset(scriptUuid); + + if (null != scriptAsset) + { + string script = Utils.BytesToString(scriptAsset.Data); + //m_log.DebugFormat("[ARCHIVER]: Script {0}", script); + MatchCollection uuidMatches = Util.UUIDPattern.Matches(script); + //m_log.DebugFormat("[ARCHIVER]: Found {0} matches in script", uuidMatches.Count); + + foreach (Match uuidMatch in uuidMatches) + { + UUID uuid = new UUID(uuidMatch.Value); + //m_log.DebugFormat("[ARCHIVER]: Recording {0} in script", uuid); + assetUuids[uuid] = 1; + } + } + } + + /// + /// Record the uuids referenced by the given wearable asset + /// + /// + /// Dictionary in which to record the references + protected void GetWearableAssetUuids(UUID wearableAssetUuid, IDictionary assetUuids) + { + AssetBase assetBase = GetAsset(wearableAssetUuid); + //m_log.Debug(new System.Text.ASCIIEncoding().GetString(bodypartAsset.Data)); + AssetWearable wearableAsset = new AssetBodypart(wearableAssetUuid, assetBase.Data); + wearableAsset.Decode(); + + //m_log.DebugFormat( + // "[ARCHIVER]: Wearable asset {0} references {1} assets", wearableAssetUuid, wearableAsset.Textures.Count); + + foreach (UUID uuid in wearableAsset.Textures.Values) + { + //m_log.DebugFormat("[ARCHIVER]: Got bodypart uuid {0}", uuid); + assetUuids[uuid] = 1; + } + } + + /// + /// Get all the asset uuids associated with a given object. This includes both those directly associated with + /// it (e.g. face textures) and recursively, those of items within it's inventory (e.g. objects contained + /// within this object). + /// + /// + /// + protected void GetSceneObjectAssetUuids(UUID sceneObjectUuid, IDictionary assetUuids) + { + AssetBase objectAsset = GetAsset(sceneObjectUuid); + + if (null != objectAsset) + { + string xml = Utils.BytesToString(objectAsset.Data); + SceneObjectGroup sog = new SceneObjectGroup(xml, true); + GetSceneObjectAssetUuids(sog, assetUuids); + } + } + + /// + /// Get all the asset uuids associated with a given object. This includes both those directly associated with + /// it (e.g. face textures) and recursively, those of items within it's inventory (e.g. objects contained + /// within this object). + /// + /// + /// + protected void GetSceneObjectAssetUuids(SceneObjectGroup sceneObject, IDictionary assetUuids) + { + m_log.DebugFormat( + "[ARCHIVER]: Getting assets for object {0}, {1}", sceneObject.Name, sceneObject.UUID); + + foreach (SceneObjectPart part in sceneObject.GetParts()) + { + //m_log.DebugFormat( + // "[ARCHIVER]: Getting part {0}, {1} for object {2}", part.Name, part.UUID, sceneObject.UUID); + + try + { + Primitive.TextureEntry textureEntry = part.Shape.Textures; + + // Get the prim's default texture. This will be used for faces which don't have their own texture + assetUuids[textureEntry.DefaultTexture.TextureID] = 1; + + // XXX: Not a great way to iterate through face textures, but there's no + // other method available to tell how many faces there actually are + //int i = 0; + foreach (Primitive.TextureEntryFace texture in textureEntry.FaceTextures) + { + if (texture != null) + { + //m_log.DebugFormat("[ARCHIVER]: Got face {0}", i++); + assetUuids[texture.TextureID] = 1; + } + } + + // If the prim is a sculpt then preserve this information too + if (part.Shape.SculptTexture != UUID.Zero) + assetUuids[part.Shape.SculptTexture] = 1; + + // Now analyze this prim's inventory items to preserve all the uuids that they reference + foreach (TaskInventoryItem tii in part.TaskInventory.Values) + { + //m_log.DebugFormat("[ARCHIVER]: Analysing item asset type {0}", tii.Type); + + if (!assetUuids.ContainsKey(tii.AssetID)) + { + assetUuids[tii.AssetID] = 1; + + if ((int)AssetType.Bodypart == tii.Type || ((int)AssetType.Clothing == tii.Type)) + { + GetWearableAssetUuids(tii.AssetID, assetUuids); + } + else if ((int)AssetType.LSLText == tii.Type) + { + GetScriptAssetUuids(tii.AssetID, assetUuids); + } + else if ((int)AssetType.Object == tii.Type) + { + GetSceneObjectAssetUuids(tii.AssetID, assetUuids); + } + //else + //{ + //m_log.DebugFormat("[ARCHIVER]: Recording asset {0} in object {1}", tii.AssetID, part.UUID); + //} + } + } + } + catch (Exception e) + { + m_log.ErrorFormat("[ARCHIVER]: Failed to get part - {0}", e); + m_log.DebugFormat("[ARCHIVER]: Texture entry length for prim was {0} (min is 46)", part.Shape.TextureEntry.Length); + } + } + } + + /// + /// Archive the region requested. + /// + /// if there was an io problem with creating the file + public void ArchiveRegion() + { + Dictionary assetUuids = new Dictionary(); + + List entities = m_scene.GetEntities(); + List sceneObjects = new List(); + + // Filter entities so that we only have scene objects. + // FIXME: Would be nicer to have this as a proper list in SceneGraph, since lots of methods + // end up having to do this + foreach (EntityBase entity in entities) + { + if (entity is SceneObjectGroup) + { + SceneObjectGroup sceneObject = (SceneObjectGroup)entity; + + if (!sceneObject.IsDeleted && !sceneObject.IsAttachment) + sceneObjects.Add((SceneObjectGroup)entity); + } + } + + foreach (SceneObjectGroup sceneObject in sceneObjects) + { + GetSceneObjectAssetUuids(sceneObject, assetUuids); + } + + m_log.DebugFormat( + "[ARCHIVER]: {0} scene objects to serialize requiring save of {1} assets", + sceneObjects.Count, assetUuids.Count); + + // Make sure that we also request terrain texture assets + RegionSettings regionSettings = m_scene.RegionInfo.RegionSettings; + + if (regionSettings.TerrainTexture1 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_1) + assetUuids[regionSettings.TerrainTexture1] = 1; + + if (regionSettings.TerrainTexture2 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_2) + assetUuids[regionSettings.TerrainTexture2] = 1; + + if (regionSettings.TerrainTexture3 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_3) + assetUuids[regionSettings.TerrainTexture3] = 1; + + if (regionSettings.TerrainTexture4 != RegionSettings.DEFAULT_TERRAIN_TEXTURE_4) + assetUuids[regionSettings.TerrainTexture4] = 1; + + // Asynchronously request all the assets required to perform this archive operation + ArchiveWriteRequestExecution awre + = new ArchiveWriteRequestExecution( + sceneObjects, + m_scene.RequestModuleInterface(), + m_scene.RequestModuleInterface(), + m_scene, + m_saveStream); + + new AssetsRequest(assetUuids.Keys, m_scene.AssetCache, awre.ReceivedAllAssets).Execute(); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs b/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs new file mode 100644 index 0000000..c1f5b18 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/ArchiverModule.cs @@ -0,0 +1,95 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Serialiser; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// This module loads and saves OpenSimulator archives + /// + public class ArchiverModule : IRegionModule, IRegionArchiverModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + + public string Name { get { return "Archiver Module"; } } + + public bool IsSharedModule { get { return false; } } + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public void ArchiveRegion(string savePath) + { + m_log.InfoFormat( + "[ARCHIVER]: Writing archive for region {0} to {1}", m_scene.RegionInfo.RegionName, savePath); + + new ArchiveWriteRequestPreparation(m_scene, savePath).ArchiveRegion(); + } + + public void ArchiveRegion(Stream saveStream) + { + new ArchiveWriteRequestPreparation(m_scene, saveStream).ArchiveRegion(); + } + + public void DearchiveRegion(string loadPath) + { + m_log.InfoFormat( + "[ARCHIVER]: Loading archive to region {0} from {1}", m_scene.RegionInfo.RegionName, loadPath); + + new ArchiveReadRequest(m_scene, loadPath).DearchiveRegion(); + } + + public void DearchiveRegion(Stream loadStream) + { + new ArchiveReadRequest(m_scene, loadStream).DearchiveRegion(); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs new file mode 100644 index 0000000..76d27ce --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsArchiver.cs @@ -0,0 +1,143 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Archives assets + /// + public class AssetsArchiver + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Archive assets + /// + protected IDictionary m_assets; + + public AssetsArchiver(IDictionary assets) + { + m_assets = assets; + } + + /// + /// Archive the assets given to this archiver to the given archive. + /// + /// + public void Archive(TarArchiveWriter archive) + { + //WriteMetadata(archive); + WriteData(archive); + } + + /// + /// Write an assets metadata file to the given archive + /// + /// + protected void WriteMetadata(TarArchiveWriter archive) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xtw = new XmlTextWriter(sw); + + xtw.Formatting = Formatting.Indented; + xtw.WriteStartDocument(); + + xtw.WriteStartElement("assets"); + + foreach (UUID uuid in m_assets.Keys) + { + AssetBase asset = m_assets[uuid]; + + if (asset != null) + { + xtw.WriteStartElement("asset"); + + string extension = string.Empty; + + if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(asset.Metadata.Type)) + { + extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[asset.Metadata.Type]; + } + + xtw.WriteElementString("filename", uuid.ToString() + extension); + + xtw.WriteElementString("name", asset.Metadata.Name); + xtw.WriteElementString("description", asset.Metadata.Description); + xtw.WriteElementString("asset-type", asset.Metadata.Type.ToString()); + + xtw.WriteEndElement(); + } + } + + xtw.WriteEndElement(); + + xtw.WriteEndDocument(); + + archive.AddFile("assets.xml", sw.ToString()); + } + + /// + /// Write asset data files to the given archive + /// + /// + protected void WriteData(TarArchiveWriter archive) + { + // It appears that gtar, at least, doesn't need the intermediate directory entries in the tar + //archive.AddDir("assets"); + + foreach (UUID uuid in m_assets.Keys) + { + AssetBase asset = m_assets[uuid]; + + string extension = string.Empty; + + if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(asset.Metadata.Type)) + { + extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[asset.Metadata.Type]; + } + else + { + m_log.ErrorFormat( + "[ARCHIVER]: Unrecognized asset type {0} with uuid {1}. This asset will be saved but not reloaded", + asset.Metadata.Type, asset.Metadata.ID); + } + + archive.AddFile( + ArchiveConstants.ASSETS_PATH + uuid.ToString() + extension, + asset.Data); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsDearchiver.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsDearchiver.cs new file mode 100644 index 0000000..f9909d9 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsDearchiver.cs @@ -0,0 +1,184 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Dearchives assets + /// + public class AssetsDearchiver + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected static System.Text.ASCIIEncoding m_asciiEncoding = new System.Text.ASCIIEncoding(); + + /// + /// Store for asset data we received before we get the metadata + /// + protected Dictionary m_assetDataAwaitingMetadata = new Dictionary(); + + /// + /// Asset metadata. Is null if asset metadata isn't yet available. + /// + protected Dictionary m_metadata; + + /// + /// Cache to which dearchived assets will be added + /// + protected IAssetCache m_cache; + + public AssetsDearchiver(IAssetCache cache) + { + m_cache = cache; + } + + /// + /// Add asset data to the dearchiver + /// + /// + /// + public void AddAssetData(string assetFilename, byte[] data) + { + if (null == m_metadata) + { + m_assetDataAwaitingMetadata[assetFilename] = data; + } + else + { + ResolveAssetData(assetFilename, data); + } + } + + /// + /// Add asset metadata xml + /// + /// + public void AddAssetMetadata(string xml) + { + m_metadata = new Dictionary(); + + StringReader sr = new StringReader(xml); + XmlTextReader reader = new XmlTextReader(sr); + + reader.ReadStartElement("assets"); + reader.Read(); + + while (reader.Name.Equals("asset")) + { + reader.Read(); + + AssetMetadata metadata = new AssetMetadata(); + + string filename = reader.ReadElementString("filename"); + m_log.DebugFormat("[DEARCHIVER]: Reading node {0}", filename); + + metadata.Name = reader.ReadElementString("name"); + metadata.Description = reader.ReadElementString("description"); + metadata.AssetType = Convert.ToSByte(reader.ReadElementString("asset-type")); + + m_metadata[filename] = metadata; + + // Read asset end tag + reader.ReadEndElement(); + + reader.Read(); + } + + m_log.DebugFormat("[DEARCHIVER]: Resolved {0} items of asset metadata", m_metadata.Count); + + ResolvePendingAssetData(); + } + + /// + /// Resolve asset data that we collected before receiving the metadata + /// + protected void ResolvePendingAssetData() + { + foreach (string filename in m_assetDataAwaitingMetadata.Keys) + { + ResolveAssetData(filename, m_assetDataAwaitingMetadata[filename]); + } + } + + /// + /// Resolve a new piece of asset data against stored metadata + /// + /// + /// + protected void ResolveAssetData(string assetPath, byte[] data) + { + // Right now we're nastily obtaining the UUID from the filename + string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); + + if (m_metadata.ContainsKey(filename)) + { + AssetMetadata metadata = m_metadata[filename]; + + if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(metadata.AssetType)) + { + string extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[metadata.AssetType]; + filename = filename.Remove(filename.Length - extension.Length); + } + + m_log.DebugFormat("[ARCHIVER]: Importing asset {0}", filename); + + AssetBase asset = new AssetBase(new UUID(filename), metadata.Name); + asset.Metadata.Description = metadata.Description; + asset.Metadata.Type = metadata.AssetType; + asset.Data = data; + + m_cache.AddAsset(asset); + } + else + { + m_log.ErrorFormat( + "[DEARCHIVER]: Tried to dearchive data with filename {0} without any corresponding metadata", + assetPath); + } + } + + /// + /// Metadata for an asset + /// + protected struct AssetMetadata + { + public string Name; + public string Description; + public sbyte AssetType; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs new file mode 100644 index 0000000..8971b6e --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/AssetsRequest.cs @@ -0,0 +1,138 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Encapsulate the asynchronous requests for the assets required for an archive operation + /// + class AssetsRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// uuids to request + /// + protected ICollection m_uuids; + + /// + /// Callback used when all the assets requested have been received. + /// + protected AssetsRequestCallback m_assetsRequestCallback; + + /// + /// Assets retrieved in this request + /// + protected Dictionary m_assets = new Dictionary(); + + /// + /// Maintain a list of assets that could not be found. This will be passed back to the requester. + /// + protected List m_notFoundAssetUuids = new List(); + + /// + /// Record the number of asset replies required so we know when we've finished + /// + private int m_repliesRequired; + + /// + /// Asset cache used to request the assets + /// + protected IAssetCache m_assetCache; + + protected internal AssetsRequest(ICollection uuids, IAssetCache assetCache, AssetsRequestCallback assetsRequestCallback) + { + m_uuids = uuids; + m_assetsRequestCallback = assetsRequestCallback; + m_assetCache = assetCache; + m_repliesRequired = uuids.Count; + } + + protected internal void Execute() + { + // We can stop here if there are no assets to fetch + if (m_repliesRequired == 0) + m_assetsRequestCallback(m_assets, m_notFoundAssetUuids); + + foreach (UUID uuid in m_uuids) + { + m_assetCache.GetAsset(uuid, AssetRequestCallback, true); + } + } + + /// + /// Called back by the asset cache when it has the asset + /// + /// + /// + public void AssetRequestCallback(UUID assetID, AssetBase asset) + { + if (asset != null) + m_assets[assetID] = asset; + else + m_notFoundAssetUuids.Add(assetID); + + //m_log.DebugFormat( + // "[ARCHIVER]: Received {0} assets and notification of {1} missing assets", m_assets.Count, m_notFoundAssetUuids.Count); + + if (m_assets.Count + m_notFoundAssetUuids.Count == m_repliesRequired) + { + // We want to stop using the asset cache thread asap as we now need to do the actual work of producing the archive + Thread newThread = new Thread(PerformAssetsRequestCallback); + newThread.Name = "OpenSimulator archiving thread post assets receipt"; + newThread.Start(); + } + } + + /// + /// Perform the callback on the original requester of the assets + /// + protected void PerformAssetsRequestCallback() + { + try + { + m_assetsRequestCallback(m_assets, m_notFoundAssetUuids); + } + catch (Exception e) + { + m_log.ErrorFormat( + "[ARCHIVER]: Terminating archive creation since asset requster callback failed with {0}", e); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/RegionSettingsSerializer.cs b/OpenSim/Region/CoreModules/World/Archiver/RegionSettingsSerializer.cs new file mode 100644 index 0000000..2580316 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/RegionSettingsSerializer.cs @@ -0,0 +1,258 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Text; +using System.Xml; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Serialize and deserialize region settings for an archive file format. + /// + /// We didn't simply use automatic .NET serializagion for OpenSim.Framework.RegionSettings since this is really + /// a file format rather than an object serialization. + /// TODO: However, we could still have used separate non-framework classes here to read and write the xml + /// automatically rather than laboriously doing it by hand using XmlTextReader and Writer. Should switch to this + /// in the future. + public class RegionSettingsSerializer + { + protected static ASCIIEncoding m_asciiEncoding = new ASCIIEncoding(); + + /// + /// Deserialize region settings + /// + /// + /// + /// + public static RegionSettings Deserialize(byte[] serializedSettings) + { + return Deserialize(m_asciiEncoding.GetString(serializedSettings, 0, serializedSettings.Length)); + } + + /// + /// Deserialize region settings + /// + /// + /// + /// + public static RegionSettings Deserialize(string serializedSettings) + { + RegionSettings settings = new RegionSettings(); + + StringReader sr = new StringReader(serializedSettings); + XmlTextReader xtr = new XmlTextReader(sr); + + xtr.ReadStartElement("RegionSettings"); + + xtr.ReadStartElement("General"); + + while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement) + { + switch (xtr.Name) + { + case "AllowDamage": + settings.AllowDamage = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "AllowLandResell": + settings.AllowLandResell = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "AllowLandJoinDivide": + settings.AllowLandJoinDivide = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "BlockFly": + settings.BlockFly = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "BlockLandShowInSearch": + settings.BlockShowInSearch = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "BlockTerraform": + settings.BlockTerraform = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "DisableCollisions": + settings.DisableCollisions = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "DisablePhysics": + settings.DisablePhysics = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "DisableScripts": + settings.DisableScripts = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "MaturityRating": + settings.Maturity = int.Parse(xtr.ReadElementContentAsString()); + break; + case "RestrictPushing": + settings.RestrictPushing = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "AgentLimit": + settings.AgentLimit = int.Parse(xtr.ReadElementContentAsString()); + break; + case "ObjectBonus": + settings.ObjectBonus = double.Parse(xtr.ReadElementContentAsString()); + break; + } + } + + xtr.ReadEndElement(); + xtr.ReadStartElement("GroundTextures"); + + while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement) + { + switch (xtr.Name) + { + case "Texture1": + settings.TerrainTexture1 = UUID.Parse(xtr.ReadElementContentAsString()); + break; + case "Texture2": + settings.TerrainTexture2 = UUID.Parse(xtr.ReadElementContentAsString()); + break; + case "Texture3": + settings.TerrainTexture3 = UUID.Parse(xtr.ReadElementContentAsString()); + break; + case "Texture4": + settings.TerrainTexture4 = UUID.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationLowSW": + settings.Elevation1SW = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationLowNW": + settings.Elevation1NW = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationLowSE": + settings.Elevation1SE = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationLowNE": + settings.Elevation1NE = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationHighSW": + settings.Elevation1SW = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationHighNW": + settings.Elevation2NW = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationHighSE": + settings.Elevation2SE = double.Parse(xtr.ReadElementContentAsString()); + break; + case "ElevationHighNE": + settings.Elevation2NE = double.Parse(xtr.ReadElementContentAsString()); + break; + } + } + + xtr.ReadEndElement(); + xtr.ReadStartElement("Terrain"); + + while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement) + { + switch (xtr.Name) + { + case "WaterHeight": + settings.WaterHeight = double.Parse(xtr.ReadElementContentAsString()); + break; + case "TerrainRaiseLimit": + settings.TerrainRaiseLimit = double.Parse(xtr.ReadElementContentAsString()); + break; + case "TerrainLowerLimit": + settings.TerrainLowerLimit = double.Parse(xtr.ReadElementContentAsString()); + break; + case "UseEstateSun": + settings.UseEstateSun = bool.Parse(xtr.ReadElementContentAsString()); + break; + case "FixedSun": + settings.FixedSun = bool.Parse(xtr.ReadElementContentAsString()); + break; + } + } + + xtr.Close(); + sr.Close(); + + return settings; + } + + public static string Serialize(RegionSettings settings) + { + StringWriter sw = new StringWriter(); + XmlTextWriter xtw = new XmlTextWriter(sw); + xtw.Formatting = Formatting.Indented; + xtw.WriteStartDocument(); + + xtw.WriteStartElement("RegionSettings"); + + xtw.WriteStartElement("General"); + xtw.WriteElementString("AllowDamage", settings.AllowDamage.ToString()); + xtw.WriteElementString("AllowLandResell", settings.AllowLandResell.ToString()); + xtw.WriteElementString("AllowLandJoinDivide", settings.AllowLandJoinDivide.ToString()); + xtw.WriteElementString("BlockFly", settings.BlockFly.ToString()); + xtw.WriteElementString("BlockLandShowInSearch", settings.BlockShowInSearch.ToString()); + xtw.WriteElementString("BlockTerraform", settings.BlockTerraform.ToString()); + xtw.WriteElementString("DisableCollisions", settings.DisableCollisions.ToString()); + xtw.WriteElementString("DisablePhysics", settings.DisablePhysics.ToString()); + xtw.WriteElementString("DisableScripts", settings.DisableScripts.ToString()); + xtw.WriteElementString("MaturityRating", settings.Maturity.ToString()); + xtw.WriteElementString("RestrictPushing", settings.RestrictPushing.ToString()); + xtw.WriteElementString("AgentLimit", settings.AgentLimit.ToString()); + xtw.WriteElementString("ObjectBonus", settings.ObjectBonus.ToString()); + xtw.WriteEndElement(); + + xtw.WriteStartElement("GroundTextures"); + xtw.WriteElementString("Texture1", settings.TerrainTexture1.ToString()); + xtw.WriteElementString("Texture2", settings.TerrainTexture2.ToString()); + xtw.WriteElementString("Texture3", settings.TerrainTexture3.ToString()); + xtw.WriteElementString("Texture4", settings.TerrainTexture4.ToString()); + xtw.WriteElementString("ElevationLowSW", settings.Elevation1SW.ToString()); + xtw.WriteElementString("ElevationLowNW", settings.Elevation1NW.ToString()); + xtw.WriteElementString("ElevationLowSE", settings.Elevation1SE.ToString()); + xtw.WriteElementString("ElevationLowNE", settings.Elevation1NE.ToString()); + xtw.WriteElementString("ElevationHighSW", settings.Elevation2SW.ToString()); + xtw.WriteElementString("ElevationHighNW", settings.Elevation2NW.ToString()); + xtw.WriteElementString("ElevationHighSE", settings.Elevation2SE.ToString()); + xtw.WriteElementString("ElevationHighNE", settings.Elevation2NE.ToString()); + xtw.WriteEndElement(); + + xtw.WriteStartElement("Terrain"); + xtw.WriteElementString("WaterHeight", settings.WaterHeight.ToString()); + xtw.WriteElementString("TerrainRaiseLimit", settings.TerrainRaiseLimit.ToString()); + xtw.WriteElementString("TerrainLowerLimit", settings.TerrainLowerLimit.ToString()); + xtw.WriteElementString("UseEstateSun", settings.UseEstateSun.ToString()); + xtw.WriteElementString("FixedSun", settings.FixedSun.ToString()); + // XXX: Need to expose interface to get sun phase information from sun module + // xtw.WriteStartElement("SunPhase", + xtw.WriteEndElement(); + + xtw.WriteEndElement(); + + xtw.Close(); + sw.Close(); + + return sw.ToString(); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/TarArchiveReader.cs b/OpenSim/Region/CoreModules/World/Archiver/TarArchiveReader.cs new file mode 100644 index 0000000..506d770 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/TarArchiveReader.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using System.Text; +using log4net; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Temporary code to do the bare minimum required to read a tar archive for our purposes + /// + public class TarArchiveReader + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public enum TarEntryType + { + TYPE_UNKNOWN = 0, + TYPE_NORMAL_FILE = 1, + TYPE_HARD_LINK = 2, + TYPE_SYMBOLIC_LINK = 3, + TYPE_CHAR_SPECIAL = 4, + TYPE_BLOCK_SPECIAL = 5, + TYPE_DIRECTORY = 6, + TYPE_FIFO = 7, + TYPE_CONTIGUOUS_FILE = 8, + } + + protected static ASCIIEncoding m_asciiEncoding = new ASCIIEncoding(); + + /// + /// Binary reader for the underlying stream + /// + protected BinaryReader m_br; + + /// + /// Used to trim off null chars + /// + protected char[] m_nullCharArray = new char[] { '\0' }; + + /// + /// Generate a tar reader which reads from the given stream. + /// + /// + public TarArchiveReader(Stream s) + { + m_br = new BinaryReader(s); + } + + /// + /// Read the next entry in the tar file. + /// + /// + /// the data for the entry. Returns null if there are no more entries + public byte[] ReadEntry(out string filePath, out TarEntryType entryType) + { + filePath = String.Empty; + entryType = TarEntryType.TYPE_UNKNOWN; + TarHeader header = ReadHeader(); + + if (null == header) + return null; + + entryType = header.EntryType; + filePath = header.FilePath; + byte[] data = m_br.ReadBytes(header.FileSize); + + //m_log.DebugFormat("[TAR ARCHIVE READER]: filePath {0}, fileSize {1}", filePath, header.FileSize); + + // Read the rest of the empty padding in the 512 byte block + if (header.FileSize % 512 != 0) + { + int paddingLeft = 512 - (header.FileSize % 512); + + //m_log.DebugFormat("[TAR ARCHIVE READER]: Reading {0} padding bytes", paddingLeft); + + m_br.ReadBytes(paddingLeft); + } + + return data; + } + + /// + /// Read the next 512 byte chunk of data as a tar header. + /// + /// A tar header struct. null if we have reached the end of the archive. + protected TarHeader ReadHeader() + { + byte[] header = m_br.ReadBytes(512); + + // If we've reached the end of the archive we'll be in null block territory, which means + // the next byte will be 0 + if (header[0] == 0) + return null; + + TarHeader tarHeader = new TarHeader(); + + tarHeader.FilePath = m_asciiEncoding.GetString(header, 0, 100); + tarHeader.FilePath = tarHeader.FilePath.Trim(m_nullCharArray); + tarHeader.FileSize = ConvertOctalBytesToDecimal(header, 124, 11); + + switch (header[156]) + { + case 0: + tarHeader.EntryType = TarEntryType.TYPE_NORMAL_FILE; + break; + case (byte)'0': + tarHeader.EntryType = TarEntryType.TYPE_NORMAL_FILE; + break; + case (byte)'1': + tarHeader.EntryType = TarEntryType.TYPE_HARD_LINK; + break; + case (byte)'2': + tarHeader.EntryType = TarEntryType.TYPE_SYMBOLIC_LINK; + break; + case (byte)'3': + tarHeader.EntryType = TarEntryType.TYPE_CHAR_SPECIAL; + break; + case (byte)'4': + tarHeader.EntryType = TarEntryType.TYPE_BLOCK_SPECIAL; + break; + case (byte)'5': + tarHeader.EntryType = TarEntryType.TYPE_DIRECTORY; + break; + case (byte)'6': + tarHeader.EntryType = TarEntryType.TYPE_FIFO; + break; + case (byte)'7': + tarHeader.EntryType = TarEntryType.TYPE_CONTIGUOUS_FILE; + break; + } + + return tarHeader; + } + + public void Close() + { + m_br.Close(); + } + + /// + /// Convert octal bytes to a decimal representation + /// + /// + /// + public static int ConvertOctalBytesToDecimal(byte[] bytes, int startIndex, int count) + { + string oString = m_asciiEncoding.GetString(bytes, startIndex, count); + + int d = 0; + + foreach (char c in oString) + { + d <<= 3; + d |= c - '0'; + } + + return d; + } + } + + public class TarHeader + { + public string FilePath; + public int FileSize; + public TarArchiveReader.TarEntryType EntryType; + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/TarArchiveWriter.cs b/OpenSim/Region/CoreModules/World/Archiver/TarArchiveWriter.cs new file mode 100644 index 0000000..437939e --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/TarArchiveWriter.cs @@ -0,0 +1,202 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Reflection; +using log4net; + +namespace OpenSim.Region.CoreModules.World.Archiver +{ + /// + /// Temporary code to produce a tar archive in tar v7 format + /// + public class TarArchiveWriter + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Dictionary m_files = new Dictionary(); + + protected static ASCIIEncoding m_asciiEncoding = new ASCIIEncoding(); + + /// + /// Add a directory to the tar archive. We can only handle one path level right now! + /// + /// + public void AddDir(string dirName) + { + // Directories are signalled by a final / + if (!dirName.EndsWith("/")) + dirName += "/"; + + AddFile(dirName, new byte[0]); + } + + /// + /// Add a file to the tar archive + /// + /// + /// + public void AddFile(string filePath, string data) + { + AddFile(filePath, m_asciiEncoding.GetBytes(data)); + } + + /// + /// Add a file to the tar archive + /// + /// + /// + public void AddFile(string filePath, byte[] data) + { + m_files[filePath] = data; + } + + /// + /// Write the raw tar archive data to a stream. The stream will be closed on completion. + /// + /// Stream to which to write the data + /// + public void WriteTar(Stream s) + { + BinaryWriter bw = new BinaryWriter(s); + + foreach (string filePath in m_files.Keys) + { + byte[] header = new byte[512]; + byte[] data = m_files[filePath]; + + // file path field (100) + byte[] nameBytes = m_asciiEncoding.GetBytes(filePath); + int nameSize = (nameBytes.Length >= 100) ? 100 : nameBytes.Length; + Array.Copy(nameBytes, header, nameSize); + + // file mode (8) + byte[] modeBytes = m_asciiEncoding.GetBytes("0000777"); + Array.Copy(modeBytes, 0, header, 100, 7); + + // owner user id (8) + byte[] ownerIdBytes = m_asciiEncoding.GetBytes("0000764"); + Array.Copy(ownerIdBytes, 0, header, 108, 7); + + // group user id (8) + byte[] groupIdBytes = m_asciiEncoding.GetBytes("0000764"); + Array.Copy(groupIdBytes, 0, header, 116, 7); + + // file size in bytes (12) + int fileSize = data.Length; + //m_log.DebugFormat("[TAR ARCHIVE WRITER]: File size of {0} is {1}", filePath, fileSize); + + byte[] fileSizeBytes = ConvertDecimalToPaddedOctalBytes(fileSize, 11); + + Array.Copy(fileSizeBytes, 0, header, 124, 11); + + // last modification time (12) + byte[] lastModTimeBytes = m_asciiEncoding.GetBytes("11017037332"); + Array.Copy(lastModTimeBytes, 0, header, 136, 11); + + // link indicator (1) + //header[156] = m_asciiEncoding.GetBytes("0")[0]; + if (filePath.EndsWith("/")) + { + header[156] = m_asciiEncoding.GetBytes("5")[0]; + } + else + { + header[156] = 0; + } + + Array.Copy(m_asciiEncoding.GetBytes("0000000"), 0, header, 329, 7); + Array.Copy(m_asciiEncoding.GetBytes("0000000"), 0, header, 337, 7); + + // check sum for header block (8) [calculated last] + Array.Copy(m_asciiEncoding.GetBytes(" "), 0, header, 148, 8); + + int checksum = 0; + foreach (byte b in header) + { + checksum += b; + } + + //m_log.DebugFormat("[TAR ARCHIVE WRITER]: Decimal header checksum is {0}", checksum); + + byte[] checkSumBytes = ConvertDecimalToPaddedOctalBytes(checksum, 6); + + Array.Copy(checkSumBytes, 0, header, 148, 6); + + header[154] = 0; + + // Write out header + bw.Write(header); + + // Write out data + bw.Write(data); + + if (data.Length % 512 != 0) + { + int paddingRequired = 512 - (data.Length % 512); + + //m_log.DebugFormat("[TAR ARCHIVE WRITER]: Padding data with {0} bytes", paddingRequired); + + byte[] padding = new byte[paddingRequired]; + bw.Write(padding); + } + } + + //m_log.Debug("[TAR ARCHIVE WRITER]: Writing final consecutive 0 blocks"); + + // Write two consecutive 0 blocks to end the archive + byte[] finalZeroPadding = new byte[1024]; + bw.Write(finalZeroPadding); + + bw.Flush(); + bw.Close(); + } + + public static byte[] ConvertDecimalToPaddedOctalBytes(int d, int padding) + { + string oString = ""; + + while (d > 0) + { + oString = Convert.ToString((byte)'0' + d & 7) + oString; + d >>= 3; + } + + while (oString.Length < padding) + { + oString = "0" + oString; + } + + byte[] oBytes = m_asciiEncoding.GetBytes(oString); + + return oBytes; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs b/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs new file mode 100644 index 0000000..a14e0f6 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Archiver/Tests/ArchiverTests.cs @@ -0,0 +1,188 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Threading; +using NUnit.Framework; +using NUnit.Framework.SyntaxHelpers; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.World.Archiver; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common.Setup; + +namespace OpenSim.Region.CoreModules.World.Archiver.Tests +{ + [TestFixture] + public class ArchiverTests + { + private EventWaitHandle m_waitHandle = new AutoResetEvent(false); + + private void SaveCompleted(string errorMessage) + { + m_waitHandle.Set(); + } + + /// + /// Test saving a V0.2 OpenSim Region Archive. + /// + [Test] + public void TestSaveOarV0p2() + { + log4net.Config.XmlConfigurator.Configure(); + + ArchiverModule archiverModule = new ArchiverModule(); + SerialiserModule serialiserModule = new SerialiserModule(); + TerrainModule terrainModule = new TerrainModule(); + + Scene scene = SceneSetupHelpers.SetupScene(); + SceneSetupHelpers.SetupSceneModules(scene, archiverModule, serialiserModule, terrainModule); + + SceneObjectPart part1; + + // Create and add prim 1 + { + string partName = "My Little Pony"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000015"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); + Vector3 groupPosition = new Vector3(10, 20, 30); + Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); + Vector3 offsetPosition = new Vector3(5, 10, 15); + + part1 + = new SceneObjectPart( + ownerId, shape, groupPosition, rotationOffset, offsetPosition); + part1.Name = partName; + + scene.AddNewSceneObject(new SceneObjectGroup(part1), false); + } + + SceneObjectPart part2; + + // Create and add prim 2 + { + string partName = "Action Man"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000016"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateCylinder(); + Vector3 groupPosition = new Vector3(90, 80, 70); + Quaternion rotationOffset = new Quaternion(60, 70, 80, 90); + Vector3 offsetPosition = new Vector3(20, 25, 30); + + part2 + = new SceneObjectPart( + ownerId, shape, groupPosition, rotationOffset, offsetPosition); + part2.Name = partName; + + scene.AddNewSceneObject(new SceneObjectGroup(part2), false); + } + + MemoryStream archiveWriteStream = new MemoryStream(); + + scene.EventManager.OnOarFileSaved += SaveCompleted; + archiverModule.ArchiveRegion(archiveWriteStream); + m_waitHandle.WaitOne(60000, true); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + bool gotControlFile = false; + bool gotObject1File = false; + bool gotObject2File = false; + string expectedObject1FileName = string.Format( + "{0}_{1:000}-{2:000}-{3:000}__{4}.xml", + part1.Name, + Math.Round(part1.GroupPosition.X), Math.Round(part1.GroupPosition.Y), Math.Round(part1.GroupPosition.Z), + part1.UUID); + string expectedObject2FileName = string.Format( + "{0}_{1:000}-{2:000}-{3:000}__{4}.xml", + part2.Name, + Math.Round(part2.GroupPosition.X), Math.Round(part2.GroupPosition.Y), Math.Round(part2.GroupPosition.Z), + part2.UUID); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + if (ArchiveConstants.CONTROL_FILE_PATH == filePath) + { + gotControlFile = true; + } + else if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + { + string fileName = filePath.Remove(0, ArchiveConstants.OBJECTS_PATH.Length); + + if (fileName.StartsWith(part1.Name)) + { + Assert.That(fileName, Is.EqualTo(expectedObject1FileName)); + gotObject1File = true; + } + else if (fileName.StartsWith(part2.Name)) + { + Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); + gotObject2File = true; + } + } + } + + Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotObject1File, Is.True, "No object1 file in archive"); + Assert.That(gotObject2File, Is.True, "No object2 file in archive"); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test loading a V0.2 OpenSim Region Archive. Does not yet do what it says on the tin. + /// + [Test] + public void TestLoadOarV0p2() + { + MemoryStream archiveWriteStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(); + + tar.AddFile(ArchiveConstants.CONTROL_FILE_PATH, ArchiveWriteRequestExecution.Create0p2ControlFile()); + tar.WriteTar(archiveWriteStream); + + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + ArchiverModule archiverModule = new ArchiverModule(); + + Scene scene = SceneSetupHelpers.SetupScene(); + SceneSetupHelpers.SetupSceneModules(scene, archiverModule); + + archiverModule.DearchiveRegion(archiveReadStream); + + // TODO: Okay, so nothing is tested yet apart from the fact that it doesn't blow up + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs new file mode 100644 index 0000000..8b15308 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs @@ -0,0 +1,1012 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Threading; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Estate +{ + public class EstateManagementModule : IEstateModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private delegate void LookupUUIDS(List uuidLst); + + private Scene m_scene; + + private EstateTerrainXferHandler TerrainUploader = null; + + #region Packet Data Responders + + private void sendDetailedEstateData(IClientAPI remote_client, UUID invoice) + { + uint sun = 0; + + if (!m_scene.RegionInfo.EstateSettings.UseGlobalTime) + sun=(uint)(m_scene.RegionInfo.EstateSettings.SunPosition*1024.0) + 0x1800; + UUID estateOwner; + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + estateOwner = m_scene.RegionInfo.EstateSettings.EstateOwner; + else + estateOwner = m_scene.RegionInfo.MasterAvatarAssignedUUID; + + if (m_scene.Permissions.IsGod(remote_client.AgentId)) + estateOwner = remote_client.AgentId; + + remote_client.SendDetailedEstateData(invoice, + m_scene.RegionInfo.EstateSettings.EstateName, + m_scene.RegionInfo.EstateSettings.EstateID, + m_scene.RegionInfo.EstateSettings.ParentEstateID, + GetEstateFlags(), + sun, + m_scene.RegionInfo.RegionSettings.Covenant, + m_scene.RegionInfo.EstateSettings.AbuseEmail, + estateOwner); + + remote_client.SendEstateManagersList(invoice, + m_scene.RegionInfo.EstateSettings.EstateManagers, + m_scene.RegionInfo.EstateSettings.EstateID); + + remote_client.SendBannedUserList(invoice, + m_scene.RegionInfo.EstateSettings.EstateBans, + m_scene.RegionInfo.EstateSettings.EstateID); + } + + private void estateSetRegionInfoHandler(bool blockTerraform, bool noFly, bool allowDamage, bool blockLandResell, int maxAgents, float objectBonusFactor, + int matureLevel, bool restrictPushObject, bool allowParcelChanges) + { + if (blockTerraform) + m_scene.RegionInfo.RegionSettings.BlockTerraform = true; + else + m_scene.RegionInfo.RegionSettings.BlockTerraform = false; + + if (noFly) + m_scene.RegionInfo.RegionSettings.BlockFly = true; + else + m_scene.RegionInfo.RegionSettings.BlockFly = false; + + if (allowDamage) + m_scene.RegionInfo.RegionSettings.AllowDamage = true; + else + m_scene.RegionInfo.RegionSettings.AllowDamage = false; + + if (blockLandResell) + m_scene.RegionInfo.RegionSettings.AllowLandResell = false; + else + m_scene.RegionInfo.RegionSettings.AllowLandResell = true; + + m_scene.RegionInfo.RegionSettings.AgentLimit = (byte) maxAgents; + + m_scene.RegionInfo.RegionSettings.ObjectBonus = objectBonusFactor; + + if (matureLevel <= 13) + m_scene.RegionInfo.RegionSettings.Maturity = 0; + else + m_scene.RegionInfo.RegionSettings.Maturity = 1; + + if (restrictPushObject) + m_scene.RegionInfo.RegionSettings.RestrictPushing = true; + else + m_scene.RegionInfo.RegionSettings.RestrictPushing = false; + + if (allowParcelChanges) + m_scene.RegionInfo.RegionSettings.AllowLandJoinDivide = true; + else + m_scene.RegionInfo.RegionSettings.AllowLandJoinDivide = false; + + m_scene.RegionInfo.RegionSettings.Save(); + + sendRegionInfoPacketToAll(); + } + + public void setEstateTerrainBaseTexture(IClientAPI remoteClient, int corner, UUID texture) + { + if (texture == UUID.Zero) + return; + + switch (corner) + { + case 0: + m_scene.RegionInfo.RegionSettings.TerrainTexture1 = texture; + break; + case 1: + m_scene.RegionInfo.RegionSettings.TerrainTexture2 = texture; + break; + case 2: + m_scene.RegionInfo.RegionSettings.TerrainTexture3 = texture; + break; + case 3: + m_scene.RegionInfo.RegionSettings.TerrainTexture4 = texture; + break; + } + m_scene.RegionInfo.RegionSettings.Save(); + } + + public void setEstateTerrainTextureHeights(IClientAPI client, int corner, float lowValue, float highValue) + { + switch (corner) + { + case 0: + m_scene.RegionInfo.RegionSettings.Elevation1SW = lowValue; + m_scene.RegionInfo.RegionSettings.Elevation2SW = highValue; + break; + case 1: + m_scene.RegionInfo.RegionSettings.Elevation1NW = lowValue; + m_scene.RegionInfo.RegionSettings.Elevation2NW = highValue; + break; + case 2: + m_scene.RegionInfo.RegionSettings.Elevation1SE = lowValue; + m_scene.RegionInfo.RegionSettings.Elevation2SE = highValue; + break; + case 3: + m_scene.RegionInfo.RegionSettings.Elevation1NE = lowValue; + m_scene.RegionInfo.RegionSettings.Elevation2NE = highValue; + break; + } + m_scene.RegionInfo.RegionSettings.Save(); + } + + private void handleCommitEstateTerrainTextureRequest(IClientAPI remoteClient) + { + sendRegionHandshakeToAll(); + } + + public void setRegionTerrainSettings(float WaterHeight, + float TerrainRaiseLimit, float TerrainLowerLimit, + bool UseEstateSun, bool UseFixedSun, float SunHour, + bool UseGlobal, bool EstateFixedSun, float EstateSunHour) + { + // Water Height + m_scene.RegionInfo.RegionSettings.WaterHeight = WaterHeight; + + // Terraforming limits + m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit = TerrainRaiseLimit; + m_scene.RegionInfo.RegionSettings.TerrainLowerLimit = TerrainLowerLimit; + + // Time of day / fixed sun + m_scene.RegionInfo.RegionSettings.UseEstateSun = UseEstateSun; + m_scene.RegionInfo.RegionSettings.FixedSun = UseFixedSun; + m_scene.RegionInfo.RegionSettings.SunPosition = SunHour; + + m_scene.EventManager.TriggerEstateToolsTimeUpdate(m_scene.RegionInfo.RegionHandle, UseFixedSun, UseEstateSun, SunHour); + + //m_log.Debug("[ESTATE]: UFS: " + UseFixedSun.ToString()); + //m_log.Debug("[ESTATE]: SunHour: " + SunHour.ToString()); + + sendRegionInfoPacketToAll(); + m_scene.RegionInfo.RegionSettings.Save(); + } + + private void handleEstateRestartSimRequest(IClientAPI remoteClient, int timeInSeconds) + { + m_scene.Restart(timeInSeconds); + } + + private void handleChangeEstateCovenantRequest(IClientAPI remoteClient, UUID estateCovenantID) + { + m_scene.RegionInfo.RegionSettings.Covenant = estateCovenantID; + m_scene.RegionInfo.RegionSettings.Save(); + } + + private void handleEstateAccessDeltaRequest(IClientAPI remote_client, UUID invoice, int estateAccessType, UUID user) + { + // EstateAccessDelta handles Estate Managers, Sim Access, Sim Banlist, allowed Groups.. etc. + + if (user == m_scene.RegionInfo.EstateSettings.EstateOwner) + return; // never process EO + if (user == m_scene.RegionInfo.MasterAvatarAssignedUUID) + return; // never process owner + + switch (estateAccessType) + { + case 64: + if (m_scene.Permissions.CanIssueEstateCommand(remote_client.AgentId, false) || m_scene.Permissions.BypassPermissions()) + { + EstateBan[] banlistcheck = m_scene.RegionInfo.EstateSettings.EstateBans; + + bool alreadyInList = false; + + for (int i = 0; i < banlistcheck.Length; i++) + { + if (user == banlistcheck[i].bannedUUID) + { + alreadyInList = true; + break; + } + + } + if (!alreadyInList) + { + + EstateBan item = new EstateBan(); + + item.bannedUUID = user; + item.estateID = m_scene.RegionInfo.EstateSettings.EstateID; + item.bannedIP = "0.0.0.0"; + item.bannedIPHostMask = "0.0.0.0"; + + m_scene.RegionInfo.EstateSettings.AddBan(item); + m_scene.RegionInfo.EstateSettings.Save(); + + ScenePresence s = m_scene.GetScenePresence(user); + if (s != null) + { + if (!s.IsChildAgent) + { + s.ControllingClient.SendTeleportLocationStart(); + m_scene.TeleportClientHome(user, s.ControllingClient); + } + } + + } + else + { + remote_client.SendAlertMessage("User is already on the region ban list"); + } + //m_scene.RegionInfo.regionBanlist.Add(Manager(user); + remote_client.SendBannedUserList(invoice, m_scene.RegionInfo.EstateSettings.EstateBans, m_scene.RegionInfo.EstateSettings.EstateID); + } + else + { + remote_client.SendAlertMessage("Method EstateAccessDelta Failed, you don't have permissions"); + } + break; + case 128: + if (m_scene.Permissions.CanIssueEstateCommand(remote_client.AgentId, false) || m_scene.Permissions.BypassPermissions()) + { + EstateBan[] banlistcheck = m_scene.RegionInfo.EstateSettings.EstateBans; + + bool alreadyInList = false; + EstateBan listitem = null; + + for (int i = 0; i < banlistcheck.Length; i++) + { + if (user == banlistcheck[i].bannedUUID) + { + alreadyInList = true; + listitem = banlistcheck[i]; + break; + } + + } + if (alreadyInList && listitem != null) + { + m_scene.RegionInfo.EstateSettings.RemoveBan(listitem.bannedUUID); + m_scene.RegionInfo.EstateSettings.Save(); + } + else + { + remote_client.SendAlertMessage("User is not on the region ban list"); + } + //m_scene.RegionInfo.regionBanlist.Add(Manager(user); + remote_client.SendBannedUserList(invoice, m_scene.RegionInfo.EstateSettings.EstateBans, m_scene.RegionInfo.EstateSettings.EstateID); + } + else + { + remote_client.SendAlertMessage("Method EstateAccessDelta Failed, you don't have permissions"); + } + break; + case 256: + + if (m_scene.Permissions.CanIssueEstateCommand(remote_client.AgentId, true) || m_scene.Permissions.BypassPermissions()) + { + m_scene.RegionInfo.EstateSettings.AddEstateManager(user); + m_scene.RegionInfo.EstateSettings.Save(); + remote_client.SendEstateManagersList(invoice, m_scene.RegionInfo.EstateSettings.EstateManagers, m_scene.RegionInfo.EstateSettings.EstateID); + } + else + { + remote_client.SendAlertMessage("Method EstateAccessDelta Failed, you don't have permissions"); + } + + break; + case 512: + if (m_scene.Permissions.CanIssueEstateCommand(remote_client.AgentId, true) || m_scene.Permissions.BypassPermissions()) + { + m_scene.RegionInfo.EstateSettings.RemoveEstateManager(user); + m_scene.RegionInfo.EstateSettings.Save(); + + remote_client.SendEstateManagersList(invoice, m_scene.RegionInfo.EstateSettings.EstateManagers, m_scene.RegionInfo.EstateSettings.EstateID); + } + else + { + remote_client.SendAlertMessage("Method EstateAccessDelta Failed, you don't have permissions"); + } + break; + + default: + + m_log.ErrorFormat("EstateOwnerMessage: Unknown EstateAccessType requested in estateAccessDelta: {0}", estateAccessType.ToString()); + break; + } + } + + private void SendSimulatorBlueBoxMessage( + IClientAPI remote_client, UUID invoice, UUID senderID, UUID sessionID, string senderName, string message) + { + IDialogModule dm = m_scene.RequestModuleInterface(); + + if (dm != null) + dm.SendNotificationToUsersInRegion(senderID, senderName, message); + } + + private void SendEstateBlueBoxMessage( + IClientAPI remote_client, UUID invoice, UUID senderID, UUID sessionID, string senderName, string message) + { + IDialogModule dm = m_scene.RequestModuleInterface(); + + if (dm != null) + dm.SendNotificationToUsersInEstate(senderID, senderName, message); + } + + private void handleEstateDebugRegionRequest(IClientAPI remote_client, UUID invoice, UUID senderID, bool scripted, bool collisionEvents, bool physics) + { + if (physics) + m_scene.RegionInfo.RegionSettings.DisablePhysics = true; + else + m_scene.RegionInfo.RegionSettings.DisablePhysics = false; + + if (scripted) + m_scene.RegionInfo.RegionSettings.DisableScripts = true; + else + m_scene.RegionInfo.RegionSettings.DisableScripts = false; + + if (collisionEvents) + m_scene.RegionInfo.RegionSettings.DisableCollisions = true; + else + m_scene.RegionInfo.RegionSettings.DisableCollisions = false; + + + m_scene.RegionInfo.RegionSettings.Save(); + + m_scene.SetSceneCoreDebug(scripted, collisionEvents, physics); + } + + private void handleEstateTeleportOneUserHomeRequest(IClientAPI remover_client, UUID invoice, UUID senderID, UUID prey) + { + if (prey != UUID.Zero) + { + ScenePresence s = m_scene.GetScenePresence(prey); + if (s != null) + { + s.ControllingClient.SendTeleportLocationStart(); + m_scene.TeleportClientHome(prey, s.ControllingClient); + } + } + } + + private void handleEstateTeleportAllUsersHomeRequest(IClientAPI remover_client, UUID invoice, UUID senderID) + { + // Get a fresh list that will not change as people get teleported away + List prescences = m_scene.GetScenePresences(); + foreach (ScenePresence p in prescences) + { + if (p.UUID != senderID) + { + // make sure they are still there, we could be working down a long list + ScenePresence s = m_scene.GetScenePresence(p.UUID); + if (s != null) + { + // Also make sure they are actually in the region + if (!s.IsChildAgent) + { + s.ControllingClient.SendTeleportLocationStart(); + m_scene.TeleportClientHome(s.UUID, s.ControllingClient); + } + } + } + } + } + private void AbortTerrainXferHandler(IClientAPI remoteClient, ulong XferID) + { + if (TerrainUploader != null) + { + lock (TerrainUploader) + { + if (XferID == TerrainUploader.XferID) + { + remoteClient.OnXferReceive -= TerrainUploader.XferReceive; + remoteClient.OnAbortXfer -= AbortTerrainXferHandler; + TerrainUploader.TerrainUploadDone -= HandleTerrainApplication; + + TerrainUploader = null; + remoteClient.SendAlertMessage("Terrain Upload aborted by the client"); + } + } + } + + } + private void HandleTerrainApplication(string filename, byte[] terrainData, IClientAPI remoteClient) + { + lock (TerrainUploader) + { + remoteClient.OnXferReceive -= TerrainUploader.XferReceive; + remoteClient.OnAbortXfer -= AbortTerrainXferHandler; + TerrainUploader.TerrainUploadDone -= HandleTerrainApplication; + + TerrainUploader = null; + } + remoteClient.SendAlertMessage("Terrain Upload Complete. Loading...."); + OpenSim.Region.CoreModules.World.Terrain.ITerrainModule terr = m_scene.RequestModuleInterface(); + + if (terr != null) + { + m_log.Warn("[CLIENT]: Got Request to Send Terrain in region " + m_scene.RegionInfo.RegionName); + if (System.IO.File.Exists(Util.dataDir() + "/terrain.raw")) + { + System.IO.File.Delete(Util.dataDir() + "/terrain.raw"); + } + try + { + System.IO.FileStream input = new System.IO.FileStream(Util.dataDir() + "/terrain.raw", System.IO.FileMode.CreateNew); + input.Write(terrainData, 0, terrainData.Length); + input.Close(); + } + catch (System.IO.IOException e) + { + m_log.ErrorFormat("[TERRAIN]: Error Saving a terrain file uploaded via the estate tools. It gave us the following error: {0}", e.ToString()); + remoteClient.SendAlertMessage("There was an IO Exception loading your terrain. Please check free space"); + + return; + } + catch (System.Security.SecurityException e) + { + m_log.ErrorFormat("[TERRAIN]: Error Saving a terrain file uploaded via the estate tools. It gave us the following error: {0}", e.ToString()); + remoteClient.SendAlertMessage("There was a security Exception loading your terrain. Please check the security on the simulator drive"); + + return; + } + catch (System.UnauthorizedAccessException e) + { + m_log.ErrorFormat("[TERRAIN]: Error Saving a terrain file uploaded via the estate tools. It gave us the following error: {0}", e.ToString()); + remoteClient.SendAlertMessage("There was a security Exception loading your terrain. Please check the security on the simulator drive"); + + return; + } + + + + + try + { + terr.LoadFromFile(Util.dataDir() + "/terrain.raw"); + remoteClient.SendAlertMessage("Your terrain was loaded. Give it a minute or two to apply"); + } + catch (Exception e) + { + m_log.ErrorFormat("[TERRAIN]: Error loading a terrain file uploaded via the estate tools. It gave us the following error: {0}", e.ToString()); + remoteClient.SendAlertMessage("There was a general error loading your terrain. Please fix the terrain file and try again"); + } + + } + else + { + remoteClient.SendAlertMessage("Unable to apply terrain. Cannot get an instance of the terrain module"); + } + + + + } + + private void handleUploadTerrain(IClientAPI remote_client, string clientFileName) + { + + if (TerrainUploader == null) + { + + TerrainUploader = new EstateTerrainXferHandler(remote_client, clientFileName); + lock (TerrainUploader) + { + remote_client.OnXferReceive += TerrainUploader.XferReceive; + remote_client.OnAbortXfer += AbortTerrainXferHandler; + TerrainUploader.TerrainUploadDone += HandleTerrainApplication; + } + TerrainUploader.RequestStartXfer(remote_client); + + } + else + { + remote_client.SendAlertMessage("Another Terrain Upload is in progress. Please wait your turn!"); + } + + } + private void handleTerrainRequest(IClientAPI remote_client, string clientFileName) + { + // Save terrain here + OpenSim.Region.CoreModules.World.Terrain.ITerrainModule terr = m_scene.RequestModuleInterface(); + + if (terr != null) + { + m_log.Warn("[CLIENT]: Got Request to Send Terrain in region " + m_scene.RegionInfo.RegionName); + if (System.IO.File.Exists(Util.dataDir() + "/terrain.raw")) + { + System.IO.File.Delete(Util.dataDir() + "/terrain.raw"); + } + terr.SaveToFile(Util.dataDir() + "/terrain.raw"); + + System.IO.FileStream input = new System.IO.FileStream(Util.dataDir() + "/terrain.raw", System.IO.FileMode.Open); + byte[] bdata = new byte[input.Length]; + input.Read(bdata, 0, (int)input.Length); + remote_client.SendAlertMessage("Terrain file written, starting download..."); + m_scene.XferManager.AddNewFile("terrain.raw", bdata); + // Tell client about it + m_log.Warn("[CLIENT]: Sending Terrain to " + remote_client.Name); + remote_client.SendInitiateDownload("terrain.raw", clientFileName); + } + } + + private void HandleRegionInfoRequest(IClientAPI remote_client) + { + RegionInfoForEstateMenuArgs args = new RegionInfoForEstateMenuArgs(); + args.billableFactor = m_scene.RegionInfo.EstateSettings.BillableFactor; + args.estateID = m_scene.RegionInfo.EstateSettings.EstateID; + args.maxAgents = (byte)m_scene.RegionInfo.RegionSettings.AgentLimit; + args.objectBonusFactor = (float)m_scene.RegionInfo.RegionSettings.ObjectBonus; + args.parentEstateID = m_scene.RegionInfo.EstateSettings.ParentEstateID; + args.pricePerMeter = m_scene.RegionInfo.EstateSettings.PricePerMeter; + args.redirectGridX = m_scene.RegionInfo.EstateSettings.RedirectGridX; + args.redirectGridY = m_scene.RegionInfo.EstateSettings.RedirectGridY; + args.regionFlags = GetRegionFlags(); + byte mature = 13; + if (m_scene.RegionInfo.RegionSettings.Maturity == 1) + mature = 21; + args.simAccess = mature; + + args.sunHour = (float)m_scene.RegionInfo.RegionSettings.SunPosition; + args.terrainLowerLimit = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; + args.terrainRaiseLimit = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; + args.useEstateSun = m_scene.RegionInfo.RegionSettings.UseEstateSun; + args.waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; + args.simName = m_scene.RegionInfo.RegionName; + + remote_client.SendRegionInfoToEstateMenu(args); + } + + private void HandleEstateCovenantRequest(IClientAPI remote_client) + { + remote_client.SendEstateCovenantInformation(m_scene.RegionInfo.RegionSettings.Covenant); + } + + private void HandleLandStatRequest(int parcelID, uint reportType, uint requestFlags, string filter, IClientAPI remoteClient) + { + Dictionary SceneData = new Dictionary(); + List uuidNameLookupList = new List(); + + if (reportType == 1) + { + SceneData = m_scene.PhysicsScene.GetTopColliders(); + } + else if (reportType == 0) + { + SceneData = m_scene.m_sceneGraph.GetTopScripts(); + } + + List SceneReport = new List(); + lock (SceneData) + { + foreach (uint obj in SceneData.Keys) + { + SceneObjectPart prt = m_scene.GetSceneObjectPart(obj); + if (prt != null) + { + if (prt.ParentGroup != null) + { + SceneObjectGroup sog = prt.ParentGroup; + if (sog != null) + { + LandStatReportItem lsri = new LandStatReportItem(); + lsri.LocationX = sog.AbsolutePosition.X; + lsri.LocationY = sog.AbsolutePosition.Y; + lsri.LocationZ = sog.AbsolutePosition.Z; + lsri.Score = SceneData[obj]; + lsri.TaskID = sog.UUID; + lsri.TaskLocalID = sog.LocalId; + lsri.TaskName = sog.GetPartName(obj); + if (m_scene.CommsManager.UUIDNameCachedTest(sog.OwnerID)) + { + lsri.OwnerName = m_scene.CommsManager.UUIDNameRequestString(sog.OwnerID); + } + else + { + lsri.OwnerName = "waiting"; + lock (uuidNameLookupList) + uuidNameLookupList.Add(sog.OwnerID); + } + + if (filter.Length != 0) + { + if ((lsri.OwnerName.Contains(filter) || lsri.TaskName.Contains(filter))) + { + } + else + { + continue; + } + } + + SceneReport.Add(lsri); + } + } + } + + } + } + remoteClient.SendLandStatReply(reportType, requestFlags, (uint)SceneReport.Count,SceneReport.ToArray()); + + if (uuidNameLookupList.Count > 0) + LookupUUID(uuidNameLookupList); + } + + private void LookupUUIDSCompleted(IAsyncResult iar) + { + LookupUUIDS icon = (LookupUUIDS)iar.AsyncState; + icon.EndInvoke(iar); + } + private void LookupUUID(List uuidLst) + { + LookupUUIDS d = LookupUUIDsAsync; + + d.BeginInvoke(uuidLst, + LookupUUIDSCompleted, + d); + } + private void LookupUUIDsAsync(List uuidLst) + { + UUID[] uuidarr = new UUID[0]; + + lock (uuidLst) + { + uuidarr = uuidLst.ToArray(); + } + + for (int i = 0; i < uuidarr.Length; i++) + { + // string lookupname = m_scene.CommsManager.UUIDNameRequestString(uuidarr[i]); + m_scene.CommsManager.UUIDNameRequestString(uuidarr[i]); + // we drop it. It gets cached though... so we're ready for the next request. + } + } + #endregion + + #region Outgoing Packets + + public void sendRegionInfoPacketToAll() + { + List avatars = m_scene.GetAvatars(); + + for (int i = 0; i < avatars.Count; i++) + { + HandleRegionInfoRequest(avatars[i].ControllingClient); ; + } + } + + public void sendRegionHandshake(IClientAPI remoteClient) + { + RegionHandshakeArgs args = new RegionHandshakeArgs(); + + args.isEstateManager = m_scene.RegionInfo.EstateSettings.IsEstateManager(remoteClient.AgentId); + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero && m_scene.RegionInfo.EstateSettings.EstateOwner == remoteClient.AgentId) + args.isEstateManager = true; + + args.billableFactor = m_scene.RegionInfo.EstateSettings.BillableFactor; + args.terrainStartHeight0 = (float)m_scene.RegionInfo.RegionSettings.Elevation1SW; + args.terrainHeightRange0 = (float)m_scene.RegionInfo.RegionSettings.Elevation2SW; + args.terrainStartHeight1 = (float)m_scene.RegionInfo.RegionSettings.Elevation1NW; + args.terrainHeightRange1 = (float)m_scene.RegionInfo.RegionSettings.Elevation2NW; + args.terrainStartHeight2 = (float)m_scene.RegionInfo.RegionSettings.Elevation1SE; + args.terrainHeightRange2 = (float)m_scene.RegionInfo.RegionSettings.Elevation2SE; + args.terrainStartHeight3 = (float)m_scene.RegionInfo.RegionSettings.Elevation1NE; + args.terrainHeightRange3 = (float)m_scene.RegionInfo.RegionSettings.Elevation2NE; + byte mature = 13; + if (m_scene.RegionInfo.RegionSettings.Maturity == 1) + mature = 21; + args.simAccess = mature; + args.waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; + + args.regionFlags = GetRegionFlags(); + args.regionName = m_scene.RegionInfo.RegionName; + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + args.SimOwner = m_scene.RegionInfo.EstateSettings.EstateOwner; + else + args.SimOwner = m_scene.RegionInfo.MasterAvatarAssignedUUID; + + // Fudge estate owner + //if (m_scene.Permissions.IsGod(remoteClient.AgentId)) + // args.SimOwner = remoteClient.AgentId; + + args.terrainBase0 = UUID.Zero; + args.terrainBase1 = UUID.Zero; + args.terrainBase2 = UUID.Zero; + args.terrainBase3 = UUID.Zero; + args.terrainDetail0 = m_scene.RegionInfo.RegionSettings.TerrainTexture1; + args.terrainDetail1 = m_scene.RegionInfo.RegionSettings.TerrainTexture2; + args.terrainDetail2 = m_scene.RegionInfo.RegionSettings.TerrainTexture3; + args.terrainDetail3 = m_scene.RegionInfo.RegionSettings.TerrainTexture4; + + remoteClient.SendRegionHandshake(m_scene.RegionInfo,args); + } + + public void sendRegionHandshakeToAll() + { + m_scene.Broadcast(sendRegionHandshake); + } + + public void handleEstateChangeInfo(IClientAPI remoteClient, UUID invoice, UUID senderID, UInt32 parms1, UInt32 parms2) + { + if (parms2 == 0) + { + m_scene.RegionInfo.EstateSettings.UseGlobalTime = true; + m_scene.RegionInfo.EstateSettings.SunPosition = 0.0; + } + else + { + m_scene.RegionInfo.EstateSettings.UseGlobalTime = false; + m_scene.RegionInfo.EstateSettings.SunPosition = (double)(parms2 - 0x1800)/1024.0; + } + + if ((parms1 & 0x00000010) != 0) + m_scene.RegionInfo.EstateSettings.FixedSun = true; + else + m_scene.RegionInfo.EstateSettings.FixedSun = false; + + if ((parms1 & 0x00008000) != 0) + m_scene.RegionInfo.EstateSettings.PublicAccess = true; + else + m_scene.RegionInfo.EstateSettings.PublicAccess = false; + + if ((parms1 & 0x10000000) != 0) + m_scene.RegionInfo.EstateSettings.AllowVoice = true; + else + m_scene.RegionInfo.EstateSettings.AllowVoice = false; + + if ((parms1 & 0x00100000) != 0) + m_scene.RegionInfo.EstateSettings.AllowDirectTeleport = true; + else + m_scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + if ((parms1 & 0x00800000) != 0) + m_scene.RegionInfo.EstateSettings.DenyAnonymous = true; + else + m_scene.RegionInfo.EstateSettings.DenyAnonymous = false; + + if ((parms1 & 0x01000000) != 0) + m_scene.RegionInfo.EstateSettings.DenyIdentified = true; + else + m_scene.RegionInfo.EstateSettings.DenyIdentified = false; + + if ((parms1 & 0x02000000) != 0) + m_scene.RegionInfo.EstateSettings.DenyTransacted = true; + else + m_scene.RegionInfo.EstateSettings.DenyTransacted = false; + + if ((parms1 & 0x40000000) != 0) + m_scene.RegionInfo.EstateSettings.DenyMinors = true; + else + m_scene.RegionInfo.EstateSettings.DenyMinors = false; + + m_scene.RegionInfo.EstateSettings.Save(); + + float sun = (float)m_scene.RegionInfo.RegionSettings.SunPosition; + if (m_scene.RegionInfo.RegionSettings.UseEstateSun) + { + sun = (float)m_scene.RegionInfo.EstateSettings.SunPosition; + if (m_scene.RegionInfo.EstateSettings.UseGlobalTime) + sun = m_scene.EventManager.GetSunLindenHour(); + } + + m_scene.EventManager.TriggerEstateToolsTimeUpdate( + m_scene.RegionInfo.RegionHandle, + m_scene.RegionInfo.EstateSettings.FixedSun || + m_scene.RegionInfo.RegionSettings.FixedSun, + m_scene.RegionInfo.RegionSettings.UseEstateSun, sun); + + sendDetailedEstateData(remoteClient, invoice); + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + m_scene.EventManager.OnNewClient += EventManager_OnNewClient; + m_scene.EventManager.OnRequestChangeWaterHeight += changeWaterHeight; + } + + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "EstateManagementModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region Other Functions + + public void changeWaterHeight(float height) + { + setRegionTerrainSettings(height, + (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit, + (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit, + m_scene.RegionInfo.RegionSettings.UseEstateSun, + m_scene.RegionInfo.RegionSettings.FixedSun, + (float)m_scene.RegionInfo.RegionSettings.SunPosition, + m_scene.RegionInfo.EstateSettings.UseGlobalTime, + m_scene.RegionInfo.EstateSettings.FixedSun, + (float)m_scene.RegionInfo.EstateSettings.SunPosition); + + sendRegionInfoPacketToAll(); + } + + #endregion + + private void EventManager_OnNewClient(IClientAPI client) + { + client.OnDetailedEstateDataRequest += sendDetailedEstateData; + client.OnSetEstateFlagsRequest += estateSetRegionInfoHandler; +// client.OnSetEstateTerrainBaseTexture += setEstateTerrainBaseTexture; + client.OnSetEstateTerrainDetailTexture += setEstateTerrainBaseTexture; + client.OnSetEstateTerrainTextureHeights += setEstateTerrainTextureHeights; + client.OnCommitEstateTerrainTextureRequest += handleCommitEstateTerrainTextureRequest; + client.OnSetRegionTerrainSettings += setRegionTerrainSettings; + client.OnEstateRestartSimRequest += handleEstateRestartSimRequest; + client.OnEstateChangeCovenantRequest += handleChangeEstateCovenantRequest; + client.OnEstateChangeInfo += handleEstateChangeInfo; + client.OnUpdateEstateAccessDeltaRequest += handleEstateAccessDeltaRequest; + client.OnSimulatorBlueBoxMessageRequest += SendSimulatorBlueBoxMessage; + client.OnEstateBlueBoxMessageRequest += SendEstateBlueBoxMessage; + client.OnEstateDebugRegionRequest += handleEstateDebugRegionRequest; + client.OnEstateTeleportOneUserHomeRequest += handleEstateTeleportOneUserHomeRequest; + client.OnEstateTeleportAllUsersHomeRequest += handleEstateTeleportAllUsersHomeRequest; + client.OnRequestTerrain += handleTerrainRequest; + client.OnUploadTerrain += handleUploadTerrain; + + client.OnRegionInfoRequest += HandleRegionInfoRequest; + client.OnEstateCovenantRequest += HandleEstateCovenantRequest; + client.OnLandStatRequest += HandleLandStatRequest; + sendRegionHandshake(client); + } + + public uint GetRegionFlags() + { + RegionFlags flags = RegionFlags.None; + + // Fully implemented + // + if (m_scene.RegionInfo.RegionSettings.AllowDamage) + flags |= RegionFlags.AllowDamage; + if (m_scene.RegionInfo.RegionSettings.BlockTerraform) + flags |= RegionFlags.BlockTerraform; + if (!m_scene.RegionInfo.RegionSettings.AllowLandResell) + flags |= RegionFlags.BlockLandResell; + if (m_scene.RegionInfo.RegionSettings.DisableCollisions) + flags |= RegionFlags.SkipCollisions; + if (m_scene.RegionInfo.RegionSettings.DisableScripts) + flags |= RegionFlags.SkipScripts; + if (m_scene.RegionInfo.RegionSettings.DisablePhysics) + flags |= RegionFlags.SkipPhysics; + if (m_scene.RegionInfo.RegionSettings.BlockFly) + flags |= RegionFlags.NoFly; + if (m_scene.RegionInfo.RegionSettings.RestrictPushing) + flags |= RegionFlags.RestrictPushObject; + if (m_scene.RegionInfo.RegionSettings.AllowLandJoinDivide) + flags |= RegionFlags.AllowParcelChanges; + if (m_scene.RegionInfo.RegionSettings.BlockShowInSearch) + flags |= (RegionFlags)(1 << 29); + + if (m_scene.RegionInfo.RegionSettings.FixedSun) + flags |= RegionFlags.SunFixed; + if (m_scene.RegionInfo.RegionSettings.Sandbox) + flags |= RegionFlags.Sandbox; + + // Fudge these to always on, so the menu options activate + // + flags |= RegionFlags.AllowLandmark; + flags |= RegionFlags.AllowSetHome; + + // TODO: SkipUpdateInterestList + + // Omitted + // + // Omitted: NullLayer (what is that?) + // Omitted: SkipAgentAction (what does it do?) + + return (uint)flags; + } + + public uint GetEstateFlags() + { + RegionFlags flags = RegionFlags.None; + + if (m_scene.RegionInfo.EstateSettings.FixedSun) + flags |= RegionFlags.SunFixed; + if (m_scene.RegionInfo.EstateSettings.PublicAccess) + flags |= (RegionFlags.PublicAllowed | + RegionFlags.ExternallyVisible); + if (m_scene.RegionInfo.EstateSettings.AllowVoice) + flags |= RegionFlags.AllowVoice; + if (m_scene.RegionInfo.EstateSettings.AllowDirectTeleport) + flags |= RegionFlags.AllowDirectTeleport; + if (m_scene.RegionInfo.EstateSettings.DenyAnonymous) + flags |= RegionFlags.DenyAnonymous; + if (m_scene.RegionInfo.EstateSettings.DenyIdentified) + flags |= RegionFlags.DenyIdentified; + if (m_scene.RegionInfo.EstateSettings.DenyTransacted) + flags |= RegionFlags.DenyTransacted; + if (m_scene.RegionInfo.EstateSettings.AbuseEmailToEstateOwner) + flags |= RegionFlags.AbuseEmailToEstateOwner; + if (m_scene.RegionInfo.EstateSettings.BlockDwell) + flags |= RegionFlags.BlockDwell; + if (m_scene.RegionInfo.EstateSettings.EstateSkipScripts) + flags |= RegionFlags.EstateSkipScripts; + if (m_scene.RegionInfo.EstateSettings.ResetHomeOnTeleport) + flags |= RegionFlags.ResetHomeOnTeleport; + if (m_scene.RegionInfo.EstateSettings.TaxFree) + flags |= RegionFlags.TaxFree; + if (m_scene.RegionInfo.EstateSettings.DenyMinors) + flags |= (RegionFlags)(1 << 30); + + return (uint)flags; + } + + public bool IsManager(UUID avatarID) + { + if (avatarID == m_scene.RegionInfo.MasterAvatarAssignedUUID) + return true; + if (avatarID == m_scene.RegionInfo.EstateSettings.EstateOwner) + return true; + + List ems = new List(m_scene.RegionInfo.EstateSettings.EstateManagers); + if (ems.Contains(avatarID)) + return true; + + return false; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs b/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs new file mode 100644 index 0000000..94a4072 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Estate/EstateTerrainXferHandler.cs @@ -0,0 +1,127 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Reflection; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Scenes; + + +namespace OpenSim.Region.CoreModules.World.Estate +{ + + public class EstateTerrainXferHandler + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private AssetBase m_asset; + + public delegate void TerrainUploadComplete(string name, byte[] filedata, IClientAPI remoteClient); + + public event TerrainUploadComplete TerrainUploadDone; + + //private string m_description = String.Empty; + //private string m_name = String.Empty; + //private UUID TransactionID = UUID.Zero; + private sbyte type = 0; + + public ulong mXferID; + private TerrainUploadComplete handlerTerrainUploadDone; + + public EstateTerrainXferHandler(IClientAPI pRemoteClient, string pClientFilename) + { + + m_asset = new AssetBase(); + m_asset.Metadata.FullID = UUID.Zero; + m_asset.Metadata.Type = type; + m_asset.Data = new byte[0]; + m_asset.Metadata.Name = pClientFilename; + m_asset.Metadata.Description = "empty"; + m_asset.Metadata.Local = true; + m_asset.Metadata.Temporary = true; + + } + + public ulong XferID + { + get { return mXferID; } + } + + public void RequestStartXfer(IClientAPI pRemoteClient) + { + mXferID = Util.GetNextXferID(); + pRemoteClient.SendXferRequest(mXferID, m_asset.Metadata.Type, m_asset.Metadata.FullID, 0, Utils.StringToBytes(m_asset.Metadata.Name)); + } + + /// + /// Process transfer data received from the client. + /// + /// + /// + /// + public void XferReceive(IClientAPI remoteClient, ulong xferID, uint packetID, byte[] data) + { + if (mXferID == xferID) + { + if (m_asset.Data.Length > 1) + { + byte[] destinationArray = new byte[m_asset.Data.Length + data.Length]; + Array.Copy(m_asset.Data, 0, destinationArray, 0, m_asset.Data.Length); + Array.Copy(data, 0, destinationArray, m_asset.Data.Length, data.Length); + m_asset.Data = destinationArray; + } + else + { + byte[] buffer2 = new byte[data.Length - 4]; + Array.Copy(data, 4, buffer2, 0, data.Length - 4); + m_asset.Data = buffer2; + } + + remoteClient.SendConfirmXfer(xferID, packetID); + + if ((packetID & 0x80000000) != 0) + { + SendCompleteMessage(remoteClient); + + } + } + } + + public void SendCompleteMessage(IClientAPI remoteClient) + { + handlerTerrainUploadDone = TerrainUploadDone; + if (handlerTerrainUploadDone != null) + { + handlerTerrainUploadDone(m_asset.Metadata.Name, m_asset.Data, remoteClient); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Land/LandChannel.cs b/OpenSim/Region/CoreModules/World/Land/LandChannel.cs new file mode 100644 index 0000000..41163a0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Land/LandChannel.cs @@ -0,0 +1,188 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Land +{ + public class LandChannel : ILandChannel + { + #region Constants + + //Land types set with flags in ParcelOverlay. + //Only one of these can be used. + public const float BAN_LINE_SAFETY_HIEGHT = 100; + public const byte LAND_FLAG_PROPERTY_BORDER_SOUTH = 128; //Equals 10000000 + public const byte LAND_FLAG_PROPERTY_BORDER_WEST = 64; //Equals 01000000 + + //RequestResults (I think these are right, they seem to work): + public const int LAND_RESULT_MULTIPLE = 1; // The request they made contained more than a single peice of land + public const int LAND_RESULT_SINGLE = 0; // The request they made contained only a single piece of land + + //ParcelSelectObjects + public const int LAND_SELECT_OBJECTS_GROUP = 4; + public const int LAND_SELECT_OBJECTS_OTHER = 8; + public const int LAND_SELECT_OBJECTS_OWNER = 2; + public const byte LAND_TYPE_IS_BEING_AUCTIONED = 5; //Equals 00000101 + public const byte LAND_TYPE_IS_FOR_SALE = 4; //Equals 00000100 + public const byte LAND_TYPE_OWNED_BY_GROUP = 2; //Equals 00000010 + public const byte LAND_TYPE_OWNED_BY_OTHER = 1; //Equals 00000001 + public const byte LAND_TYPE_OWNED_BY_REQUESTER = 3; //Equals 00000011 + public const byte LAND_TYPE_PUBLIC = 0; //Equals 00000000 + + //These are other constants. Yay! + public const int START_LAND_LOCAL_ID = 1; + + #endregion + + private readonly Scene m_scene; + private readonly LandManagementModule m_landManagementModule; + + public LandChannel(Scene scene, LandManagementModule landManagementMod) + { + m_scene = scene; + m_landManagementModule = landManagementMod; + } + + #region ILandChannel Members + + + /// + /// Get the land object at the specified point + /// + /// Value between 0 - 256 on the x axis of the point + /// Value between 0 - 256 on the y axis of the point + /// Land object at the point supplied + public ILandObject GetLandObject(float x_float, float y_float) + { + if (m_landManagementModule != null) + { + return m_landManagementModule.GetLandObject(x_float, y_float); + } + ILandObject obj = new LandObject(UUID.Zero, false, m_scene); + obj.landData.Name = "NO LAND"; + return obj; + } + + public ILandObject GetLandObject(int x, int y) + { + if (m_landManagementModule != null) + { + return m_landManagementModule.GetLandObject(x, y); + } + ILandObject obj = new LandObject(UUID.Zero, false, m_scene); + obj.landData.Name = "NO LAND"; + return obj; + } + + public List AllParcels() + { + if (m_landManagementModule != null) + { + return m_landManagementModule.AllParcels(); + } + + return new List(); + } + + public List ParcelsNearPoint(Vector3 position) + { + if (m_landManagementModule != null) + { + return m_landManagementModule.ParcelsNearPoint(position); + } + + return new List(); + } + + public bool IsLandPrimCountTainted() + { + if (m_landManagementModule != null) + { + return m_landManagementModule.IsLandPrimCountTainted(); + } + + return false; + } + + public bool IsForcefulBansAllowed() + { + if (m_landManagementModule != null) + { + return m_landManagementModule.AllowedForcefulBans; + } + + return false; + } + + public void UpdateLandObject(int localID, LandData data) + { + if (m_landManagementModule != null) + { + m_landManagementModule.UpdateLandObject(localID, data); + } + } + public void ReturnObjectsInParcel(int localID, uint returnType, UUID[] agentIDs, UUID[] taskIDs, IClientAPI remoteClient) + { + if (m_landManagementModule != null) + { + m_landManagementModule.ReturnObjectsInParcel(localID, returnType, agentIDs, taskIDs, remoteClient); + } + } + + public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel) + { + if (m_landManagementModule != null) + { + m_landManagementModule.setParcelObjectMaxOverride(overrideDel); + } + } + + public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel) + { + if (m_landManagementModule != null) + { + m_landManagementModule.setSimulatorObjectMaxOverride(overrideDel); + } + } + + public void SetParcelOtherCleanTime(IClientAPI remoteClient, int localID, int otherCleanTime) + { + if (m_landManagementModule != null) + { + m_landManagementModule.setParcelOtherCleanTime(remoteClient, localID, otherCleanTime); + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs new file mode 100644 index 0000000..6ae6576 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Land/LandManagementModule.cs @@ -0,0 +1,1347 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Region.Physics.Manager; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +namespace OpenSim.Region.CoreModules.World.Land +{ + // used for caching + internal class ExtendedLandData { + public LandData landData; + public ulong regionHandle; + public uint x, y; + } + + public class LandManagementModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static readonly string remoteParcelRequestPath = "0009/"; + + private LandChannel landChannel; + private Scene m_scene; + + private readonly int[,] m_landIDList = new int[64, 64]; + private readonly Dictionary m_landList = new Dictionary(); + + private bool m_landPrimCountTainted; + private int m_lastLandLocalID = LandChannel.START_LAND_LOCAL_ID - 1; + + private bool m_allowedForcefulBans = true; + + // caches ExtendedLandData + private Cache parcelInfoCache; + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_landIDList.Initialize(); + landChannel = new LandChannel(scene, this); + + parcelInfoCache = new Cache(); + parcelInfoCache.Size = 30; // the number of different parcel requests in this region to cache + parcelInfoCache.DefaultTTL = new TimeSpan(0, 5, 0); + + m_scene.EventManager.OnParcelPrimCountAdd += AddPrimToLandPrimCounts; + m_scene.EventManager.OnParcelPrimCountUpdate += UpdateLandPrimCounts; + m_scene.EventManager.OnAvatarEnteringNewParcel += new EventManager.AvatarEnteringNewParcel(handleAvatarChangingParcel); + m_scene.EventManager.OnClientMovement += new EventManager.ClientMovement(handleAnyClientMovement); + m_scene.EventManager.OnValidateLandBuy += handleLandValidationRequest; + m_scene.EventManager.OnLandBuy += handleLandBuyRequest; + m_scene.EventManager.OnNewClient += new EventManager.OnNewClientDelegate(EventManager_OnNewClient); + m_scene.EventManager.OnSignificantClientMovement += handleSignificantClientMovement; + m_scene.EventManager.OnObjectBeingRemovedFromScene += RemovePrimFromLandPrimCounts; + + m_scene.EventManager.OnNoticeNoLandDataFromStorage += this.NoLandDataFromStorage; + m_scene.EventManager.OnIncomingLandDataFromStorage += this.IncomingLandObjectsFromStorage; + m_scene.EventManager.OnSetAllowForcefulBan += this.SetAllowedForcefulBans; + m_scene.EventManager.OnRequestParcelPrimCountUpdate += this.PerformParcelPrimCountUpdate; + m_scene.EventManager.OnParcelPrimCountTainted += this.SetPrimsTainted; + m_scene.EventManager.OnRegisterCaps += this.OnRegisterCaps; + + lock (m_scene) + { + m_scene.LandChannel = (ILandChannel) landChannel; + } + } + + void EventManager_OnNewClient(IClientAPI client) + { + //Register some client events + client.OnParcelPropertiesRequest += new ParcelPropertiesRequest(handleParcelPropertiesRequest); + client.OnParcelDivideRequest += new ParcelDivideRequest(handleParcelDivideRequest); + client.OnParcelJoinRequest += new ParcelJoinRequest(handleParcelJoinRequest); + client.OnParcelPropertiesUpdateRequest += new ParcelPropertiesUpdateRequest(handleParcelPropertiesUpdateRequest); + client.OnParcelSelectObjects += new ParcelSelectObjects(handleParcelSelectObjectsRequest); + client.OnParcelObjectOwnerRequest += new ParcelObjectOwnerRequest(handleParcelObjectOwnersRequest); + client.OnParcelAccessListRequest += new ParcelAccessListRequest(handleParcelAccessRequest); + client.OnParcelAccessListUpdateRequest += new ParcelAccessListUpdateRequest(handleParcelAccessUpdateRequest); + client.OnParcelAbandonRequest += new ParcelAbandonRequest(handleParcelAbandonRequest); + client.OnParcelGodForceOwner += new ParcelGodForceOwner(handleParcelGodForceOwner); + client.OnParcelReclaim += new ParcelReclaim(handleParcelReclaim); + client.OnParcelInfoRequest += new ParcelInfoRequest(handleParcelInfo); + client.OnParcelDwellRequest += new ParcelDwellRequest(handleParcelDwell); + if (m_scene.Entities.ContainsKey(client.AgentId)) + { + SendLandUpdate((ScenePresence)m_scene.Entities[client.AgentId], true); + SendParcelOverlay(client); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "LandManagementModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region Parcel Add/Remove/Get/Create + + public void SetAllowedForcefulBans(bool forceful) + { + AllowedForcefulBans = forceful; + } + + public void UpdateLandObject(int local_id, LandData data) + { + LandData newData = data.Copy(); + newData.LocalID = local_id; + + lock (m_landList) + { + if (m_landList.ContainsKey(local_id)) + { + m_landList[local_id].landData = newData; + m_scene.EventManager.TriggerLandObjectUpdated((uint)local_id, m_landList[local_id]); + } + } + } + + public bool AllowedForcefulBans + { + get { return m_allowedForcefulBans; } + set { m_allowedForcefulBans = value; } + } + + /// + /// Resets the sim to the default land object (full sim piece of land owned by the default user) + /// + public void ResetSimLandObjects() + { + //Remove all the land objects in the sim and add a blank, full sim land object set to public + lock (m_landList) + { + m_landList.Clear(); + m_lastLandLocalID = LandChannel.START_LAND_LOCAL_ID - 1; + m_landIDList.Initialize(); + } + + ILandObject fullSimParcel = new LandObject(UUID.Zero, false, m_scene); + + fullSimParcel.setLandBitmap(fullSimParcel.getSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + fullSimParcel.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner; + else + fullSimParcel.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID; + fullSimParcel.landData.ClaimDate = Util.UnixTimeSinceEpoch(); + AddLandObject(fullSimParcel); + } + + public List AllParcels() + { + lock (m_landList) + { + return new List(m_landList.Values); + } + } + + public List ParcelsNearPoint(Vector3 position) + { + List parcelsNear = new List(); + for (int x = -4; x <= 4; x += 4) + { + for (int y = -4; y <= 4; y += 4) + { + ILandObject check = GetLandObject(position.X + x, position.Y + y); + if (check != null) + { + if (!parcelsNear.Contains(check)) + { + parcelsNear.Add(check); + } + } + } + } + + return parcelsNear; + } + + public void SendYouAreBannedNotice(ScenePresence avatar) + { + if (AllowedForcefulBans) + { + avatar.ControllingClient.SendAlertMessage( + "You are not allowed on this parcel because you are banned. Please go away."); + + avatar.PhysicsActor.Position = + new PhysicsVector(avatar.lastKnownAllowedPosition.X, avatar.lastKnownAllowedPosition.Y, + avatar.lastKnownAllowedPosition.Z); + avatar.PhysicsActor.Velocity = new PhysicsVector(0, 0, 0); + } + else + { + avatar.ControllingClient.SendAlertMessage( + "You are not allowed on this parcel because you are banned; however, the grid administrator has disabled ban lines globally. Please obey the land owner's requests or you can be banned from the entire sim!"); + } + } + + public void handleAvatarChangingParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + if (m_scene.RegionInfo.RegionID == regionID) + { + ILandObject parcelAvatarIsEntering; + lock (m_landList) + { + parcelAvatarIsEntering = m_landList[localLandID]; + } + + if (parcelAvatarIsEntering != null) + { + if (avatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT) + { + if (parcelAvatarIsEntering.isBannedFromLand(avatar.UUID)) + { + SendYouAreBannedNotice(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!)."); + } + else + { + avatar.sentMessageAboutRestrictedParcelFlyingDown = true; + } + } + else + { + avatar.sentMessageAboutRestrictedParcelFlyingDown = true; + } + } + } + } + + public void SendOutNearestBanLine(IClientAPI avatar) + { + List avatars = m_scene.GetAvatars(); + foreach (ScenePresence presence in avatars) + { + if (presence.UUID == avatar.AgentId) + { + List checkLandParcels = ParcelsNearPoint(presence.AbsolutePosition); + foreach (ILandObject checkBan in checkLandParcels) + { + if (checkBan.isBannedFromLand(avatar.AgentId)) + { + checkBan.sendLandProperties((int)ParcelStatus.CollisionBanned, false, (int)ParcelResult.Single, avatar); + return; //Only send one + } + if (checkBan.isRestrictedFromLand(avatar.AgentId)) + { + checkBan.sendLandProperties((int)ParcelStatus.CollisionNotOnAccessList, false, (int)ParcelResult.Single, avatar); + return; //Only send one + } + } + return; + } + } + } + + public void SendLandUpdate(ScenePresence avatar, bool force) + { + ILandObject over = GetLandObject((int)Math.Min(255, Math.Max(0, Math.Round(avatar.AbsolutePosition.X))), + (int)Math.Min(255, Math.Max(0, Math.Round(avatar.AbsolutePosition.Y)))); + + if (over != null) + { + if (force) + { + if (!avatar.IsChildAgent) + { + over.sendLandUpdateToClient(avatar.ControllingClient); + m_scene.EventManager.TriggerAvatarEnteringNewParcel(avatar, over.landData.LocalID, + m_scene.RegionInfo.RegionID); + } + } + + if (avatar.currentParcelUUID != over.landData.GlobalID) + { + if (!avatar.IsChildAgent) + { + over.sendLandUpdateToClient(avatar.ControllingClient); + avatar.currentParcelUUID = over.landData.GlobalID; + m_scene.EventManager.TriggerAvatarEnteringNewParcel(avatar, over.landData.LocalID, + m_scene.RegionInfo.RegionID); + } + } + } + } + + public void SendLandUpdate(ScenePresence avatar) + { + SendLandUpdate(avatar, false); + } + + public void handleSignificantClientMovement(IClientAPI remote_client) + { + ScenePresence clientAvatar = m_scene.GetScenePresence(remote_client.AgentId); + + if (clientAvatar != null) + { + SendLandUpdate(clientAvatar); + SendOutNearestBanLine(remote_client); + ILandObject parcel = GetLandObject(clientAvatar.AbsolutePosition.X, clientAvatar.AbsolutePosition.Y); + if (parcel != null) + { + if (clientAvatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT && + clientAvatar.sentMessageAboutRestrictedParcelFlyingDown) + { + handleAvatarChangingParcel(clientAvatar, parcel.landData.LocalID, m_scene.RegionInfo.RegionID); + //They are going below the safety line! + if (!parcel.isBannedFromLand(clientAvatar.UUID)) + { + clientAvatar.sentMessageAboutRestrictedParcelFlyingDown = false; + } + } + else if (clientAvatar.AbsolutePosition.Z < LandChannel.BAN_LINE_SAFETY_HIEGHT && + parcel.isBannedFromLand(clientAvatar.UUID)) + { + SendYouAreBannedNotice(clientAvatar); + } + } + } + } + + public void handleAnyClientMovement(ScenePresence avatar) + //Like handleSignificantClientMovement, but called with an AgentUpdate regardless of distance. + { + 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) + { + avatar.lastKnownAllowedPosition = + new Vector3(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y, avatar.AbsolutePosition.Z); + } + } + } + + + public void handleParcelAccessRequest(UUID agentID, UUID sessionID, uint flags, int sequenceID, + int landLocalID, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(landLocalID, out land); + } + + if (land != null) + { + m_landList[landLocalID].sendAccessList(agentID, sessionID, flags, sequenceID, remote_client); + } + } + + public void handleParcelAccessUpdateRequest(UUID agentID, UUID sessionID, uint flags, int landLocalID, + List entries, + IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(landLocalID, out land); + } + + if (land != null) + { + if (agentID == land.landData.OwnerID) + { + land.updateAccessList(flags, entries, remote_client); + } + } + else + { + m_log.WarnFormat("[LAND]: Invalid local land ID {0}", landLocalID); + } + } + + /// + /// Creates a basic Parcel object without an owner (a zeroed key) + /// + /// + public ILandObject CreateBaseLand() + { + return new LandObject(UUID.Zero, false, m_scene); + } + + /// + /// Adds a land object to the stored list and adds them to the landIDList to what they own + /// + /// The land object being added + public ILandObject AddLandObject(ILandObject land) + { + ILandObject new_land = land.Copy(); + + lock (m_landList) + { + int newLandLocalID = ++m_lastLandLocalID; + new_land.landData.LocalID = newLandLocalID; + + bool[,] landBitmap = new_land.getLandBitmap(); + for (int x = 0; x < 64; x++) + { + for (int y = 0; y < 64; y++) + { + if (landBitmap[x, y]) + { + m_landIDList[x, y] = newLandLocalID; + } + } + } + + m_landList.Add(newLandLocalID, new_land); + } + + new_land.forceUpdateLandInfo(); + m_scene.EventManager.TriggerLandObjectAdded(new_land); + return new_land; + } + + /// + /// Removes a land object from the list. Will not remove if local_id is still owning an area in landIDList + /// + /// Land.localID of the peice of land to remove. + public void removeLandObject(int local_id) + { + lock (m_landList) + { + for (int x = 0; x < 64; x++) + { + for (int y = 0; y < 64; y++) + { + if (m_landIDList[x, y] == local_id) + { + m_log.WarnFormat("[LAND]: Not removing land object {0}; still being used at {1}, {2}", + local_id, x, y); + return; + //throw new Exception("Could not remove land object. Still being used at " + x + ", " + y); + } + } + } + + m_scene.EventManager.TriggerLandObjectRemoved(m_landList[local_id].landData.GlobalID); + m_landList.Remove(local_id); + } + } + + private void performFinalLandJoin(ILandObject master, ILandObject slave) + { + bool[,] landBitmapSlave = slave.getLandBitmap(); + lock (m_landList) + { + for (int x = 0; x < 64; x++) + { + for (int y = 0; y < 64; y++) + { + if (landBitmapSlave[x, y]) + { + m_landIDList[x, y] = master.landData.LocalID; + } + } + } + } + + removeLandObject(slave.landData.LocalID); + UpdateLandObject(master.landData.LocalID, master.landData); + } + + public ILandObject GetLandObject(int parcelLocalID) + { + lock (m_landList) + { + if (m_landList.ContainsKey(parcelLocalID)) + { + return m_landList[parcelLocalID]; + } + } + return null; + } + + /// + /// Get the land object at the specified point + /// + /// Value between 0 - 256 on the x axis of the point + /// Value between 0 - 256 on the y axis of the point + /// Land object at the point supplied + public ILandObject GetLandObject(float x_float, float y_float) + { + int x; + int y; + + try + { + x = Convert.ToInt32(Math.Floor(Convert.ToDouble(x_float) / 4.0)); + y = Convert.ToInt32(Math.Floor(Convert.ToDouble(y_float) / 4.0)); + } + catch (OverflowException) + { + return null; + } + + if (x >= 64 || y >= 64 || x < 0 || y < 0) + { + return null; + } + lock (m_landList) + { + // Corner case. If an autoreturn happens during sim startup + // we will come here with the list uninitialized + // + if (m_landList.ContainsKey(m_landIDList[x, y])) + return m_landList[m_landIDList[x, y]]; + return null; + } + } + + public ILandObject GetLandObject(int x, int y) + { + if (x >= Convert.ToInt32(Constants.RegionSize) || y >= Convert.ToInt32(Constants.RegionSize) || x < 0 || y < 0) + { + // These exceptions here will cause a lot of complaints from the users specifically because + // they happen every time at border crossings + throw new Exception("Error: Parcel not found at point " + x + ", " + y); + } + lock (m_landIDList) + { + return m_landList[m_landIDList[x / 4, y / 4]]; + } + } + + #endregion + + #region Parcel Modification + + public void ResetAllLandPrimCounts() + { + lock (m_landList) + { + foreach (LandObject p in m_landList.Values) + { + p.resetLandPrimCounts(); + } + } + } + + public void SetPrimsTainted() + { + m_landPrimCountTainted = true; + } + + public bool IsLandPrimCountTainted() + { + return m_landPrimCountTainted; + } + + public void AddPrimToLandPrimCounts(SceneObjectGroup obj) + { + Vector3 position = obj.AbsolutePosition; + ILandObject landUnderPrim = GetLandObject(position.X, position.Y); + if (landUnderPrim != null) + { + landUnderPrim.addPrimToCount(obj); + } + } + + public void RemovePrimFromLandPrimCounts(SceneObjectGroup obj) + { + + lock (m_landList) + { + foreach (LandObject p in m_landList.Values) + { + p.removePrimFromCount(obj); + } + } + } + + public void FinalizeLandPrimCountUpdate() + { + //Get Simwide prim count for owner + Dictionary> landOwnersAndParcels = new Dictionary>(); + lock (m_landList) + { + foreach (LandObject p in m_landList.Values) + { + if (!landOwnersAndParcels.ContainsKey(p.landData.OwnerID)) + { + List tempList = new List(); + tempList.Add(p); + landOwnersAndParcels.Add(p.landData.OwnerID, tempList); + } + else + { + landOwnersAndParcels[p.landData.OwnerID].Add(p); + } + } + } + + foreach (UUID owner in landOwnersAndParcels.Keys) + { + int simArea = 0; + int simPrims = 0; + foreach (LandObject p in landOwnersAndParcels[owner]) + { + simArea += p.landData.Area; + simPrims += p.landData.OwnerPrims + p.landData.OtherPrims + p.landData.GroupPrims + + p.landData.SelectedPrims; + } + + foreach (LandObject p in landOwnersAndParcels[owner]) + { + p.landData.SimwideArea = simArea; + p.landData.SimwidePrims = simPrims; + } + } + } + + public void UpdateLandPrimCounts() + { + ResetAllLandPrimCounts(); + foreach (EntityBase obj in m_scene.Entities) + { + if (obj != null) + { + if ((obj is SceneObjectGroup) && !obj.IsDeleted && !((SceneObjectGroup) obj).IsAttachment) + { + m_scene.EventManager.TriggerParcelPrimCountAdd((SceneObjectGroup) obj); + } + } + } + FinalizeLandPrimCountUpdate(); + m_landPrimCountTainted = false; + } + + public void PerformParcelPrimCountUpdate() + { + ResetAllLandPrimCounts(); + m_scene.EventManager.TriggerParcelPrimCountUpdate(); + FinalizeLandPrimCountUpdate(); + m_landPrimCountTainted = false; + } + + /// + /// Subdivides a piece of land + /// + /// West Point + /// South Point + /// East Point + /// North Point + /// UUID of user who is trying to subdivide + /// Returns true if successful + private void subdivide(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) + { + //First, lets loop through the points and make sure they are all in the same peice of land + //Get the land object at start + + ILandObject startLandObject = GetLandObject(start_x, start_y); + + if (startLandObject == null) return; + + //Loop through the points + try + { + int totalX = end_x - start_x; + int totalY = end_y - start_y; + for (int y = 0; y < totalY; y++) + { + for (int x = 0; x < totalX; x++) + { + ILandObject tempLandObject = GetLandObject(start_x + x, start_y + y); + if (tempLandObject == null) return; + if (tempLandObject != startLandObject) return; + } + } + } + catch (Exception) + { + return; + } + + //If we are still here, then they are subdividing within one piece of land + //Check owner + if (!m_scene.Permissions.CanEditParcel(attempting_user_id, startLandObject)) + { + return; + } + + //Lets create a new land object with bitmap activated at that point (keeping the old land objects info) + ILandObject newLand = startLandObject.Copy(); + newLand.landData.Name = "Subdivision of " + newLand.landData.Name; + newLand.landData.GlobalID = UUID.Random(); + + newLand.setLandBitmap(newLand.getSquareLandBitmap(start_x, start_y, end_x, end_y)); + + //Now, lets set the subdivision area of the original to false + int startLandObjectIndex = startLandObject.landData.LocalID; + lock (m_landList) + { + m_landList[startLandObjectIndex].setLandBitmap( + newLand.modifyLandBitmapSquare(startLandObject.getLandBitmap(), start_x, start_y, end_x, end_y, false)); + m_landList[startLandObjectIndex].forceUpdateLandInfo(); + } + + SetPrimsTainted(); + + //Now add the new land object + ILandObject result = AddLandObject(newLand); + UpdateLandObject(startLandObject.landData.LocalID, startLandObject.landData); + result.sendLandUpdateToAvatarsOverMe(); + } + + /// + /// Join 2 land objects together + /// + /// x value in first piece of land + /// y value in first piece of land + /// x value in second peice of land + /// y value in second peice of land + /// UUID of the avatar trying to join the land objects + /// Returns true if successful + private void join(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) + { + end_x -= 4; + end_y -= 4; + + List selectedLandObjects = new List(); + int stepYSelected; + for (stepYSelected = start_y; stepYSelected <= end_y; stepYSelected += 4) + { + int stepXSelected; + for (stepXSelected = start_x; stepXSelected <= end_x; stepXSelected += 4) + { + ILandObject p = GetLandObject(stepXSelected, stepYSelected); + + if (p != null) + { + if (!selectedLandObjects.Contains(p)) + { + selectedLandObjects.Add(p); + } + } + } + } + ILandObject masterLandObject = selectedLandObjects[0]; + selectedLandObjects.RemoveAt(0); + + if (selectedLandObjects.Count < 1) + { + return; + } + if (!m_scene.Permissions.CanEditParcel(attempting_user_id, masterLandObject)) + { + return; + } + foreach (ILandObject p in selectedLandObjects) + { + if (p.landData.OwnerID != masterLandObject.landData.OwnerID) + { + return; + } + } + + lock (m_landList) + { + foreach (ILandObject slaveLandObject in selectedLandObjects) + { + m_landList[masterLandObject.landData.LocalID].setLandBitmap( + slaveLandObject.mergeLandBitmaps(masterLandObject.getLandBitmap(), slaveLandObject.getLandBitmap())); + performFinalLandJoin(masterLandObject, slaveLandObject); + } + } + SetPrimsTainted(); + + masterLandObject.sendLandUpdateToAvatarsOverMe(); + } + + #endregion + + #region Parcel Updating + + /// + /// Where we send the ParcelOverlay packet to the client + /// + /// The object representing the client + public void SendParcelOverlay(IClientAPI remote_client) + { + const int LAND_BLOCKS_PER_PACKET = 1024; + + byte[] byteArray = new byte[LAND_BLOCKS_PER_PACKET]; + int byteArrayCount = 0; + int sequenceID = 0; + + for (int y = 0; y < 64; y++) + { + for (int x = 0; x < 64; x++) + { + byte tempByte = 0; //This represents the byte for the current 4x4 + + ILandObject currentParcelBlock = GetLandObject(x * 4, y * 4); + + if (currentParcelBlock != null) + { + if (currentParcelBlock.landData.OwnerID == remote_client.AgentId) + { + //Owner Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_REQUESTER); + } + else if (currentParcelBlock.landData.SalePrice > 0 && + (currentParcelBlock.landData.AuthBuyerID == UUID.Zero || + currentParcelBlock.landData.AuthBuyerID == remote_client.AgentId)) + { + //Sale Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_IS_FOR_SALE); + } + else if (currentParcelBlock.landData.OwnerID == UUID.Zero) + { + //Public Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_PUBLIC); + } + else + { + //Other Flag + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_TYPE_OWNED_BY_OTHER); + } + + //Now for border control + + ILandObject westParcel = null; + ILandObject southParcel = null; + if (x > 0) + { + westParcel = GetLandObject((x - 1) * 4, y * 4); + } + if (y > 0) + { + southParcel = GetLandObject(x * 4, (y - 1) * 4); + } + + if (x == 0) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); + } + else if (westParcel != null && westParcel != currentParcelBlock) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_WEST); + } + + if (y == 0) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); + } + else if (southParcel != null && southParcel != currentParcelBlock) + { + tempByte = Convert.ToByte(tempByte | LandChannel.LAND_FLAG_PROPERTY_BORDER_SOUTH); + } + + byteArray[byteArrayCount] = tempByte; + byteArrayCount++; + if (byteArrayCount >= LAND_BLOCKS_PER_PACKET) + { + remote_client.SendLandParcelOverlay(byteArray, sequenceID); + byteArrayCount = 0; + sequenceID++; + byteArray = new byte[LAND_BLOCKS_PER_PACKET]; + } + } + } + } + } + + public void handleParcelPropertiesRequest(int start_x, int start_y, int end_x, int end_y, int sequence_id, + bool snap_selection, IClientAPI remote_client) + { + //Get the land objects within the bounds + List temp = new List(); + int inc_x = end_x - start_x; + int inc_y = end_y - start_y; + for (int x = 0; x < inc_x; x++) + { + for (int y = 0; y < inc_y; y++) + { + ILandObject currentParcel = GetLandObject(start_x + x, start_y + y); + + if (currentParcel != null) + { + if (!temp.Contains(currentParcel)) + { + currentParcel.forceUpdateLandInfo(); + temp.Add(currentParcel); + } + } + } + } + + int requestResult = LandChannel.LAND_RESULT_SINGLE; + if (temp.Count > 1) + { + requestResult = LandChannel.LAND_RESULT_MULTIPLE; + } + + for (int i = 0; i < temp.Count; i++) + { + temp[i].sendLandProperties(sequence_id, snap_selection, requestResult, remote_client); + } + + SendParcelOverlay(remote_client); + } + + public void handleParcelPropertiesUpdateRequest(LandUpdateArgs args, int localID, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(localID, out land); + } + + if (land != null) land.updateLandProperties(args, remote_client); + } + + public void handleParcelDivideRequest(int west, int south, int east, int north, IClientAPI remote_client) + { + subdivide(west, south, east, north, remote_client.AgentId); + } + + public void handleParcelJoinRequest(int west, int south, int east, int north, IClientAPI remote_client) + { + join(west, south, east, north, remote_client.AgentId); + } + + public void handleParcelSelectObjectsRequest(int local_id, int request_type, List returnIDs, IClientAPI remote_client) + { + m_landList[local_id].sendForceObjectSelect(local_id, request_type, returnIDs, remote_client); + } + + public void handleParcelObjectOwnersRequest(int local_id, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(local_id, out land); + } + + if (land != null) + { + m_landList[local_id].sendLandObjectOwners(remote_client); + } + else + { + m_log.WarnFormat("[PARCEL]: Invalid land object {0} passed for parcel object owner request", local_id); + } + } + + public void handleParcelGodForceOwner(int local_id, UUID ownerID, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(local_id, out land); + } + + if (land != null) + { + if (m_scene.Permissions.IsGod(remote_client.AgentId)) + { + land.landData.OwnerID = ownerID; + + m_scene.Broadcast(SendParcelOverlay); + land.sendLandUpdateToClient(remote_client); + } + } + } + + public void handleParcelAbandonRequest(int local_id, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(local_id, out land); + } + + if (land != null) + { + if (m_scene.Permissions.CanAbandonParcel(remote_client.AgentId, land)) + { + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + land.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner; + else + land.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID; + m_scene.Broadcast(SendParcelOverlay); + land.sendLandUpdateToClient(remote_client); + } + } + } + + public void handleParcelReclaim(int local_id, IClientAPI remote_client) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(local_id, out land); + } + + if (land != null) + { + if (m_scene.Permissions.CanReclaimParcel(remote_client.AgentId, land)) + { + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + land.landData.OwnerID = m_scene.RegionInfo.EstateSettings.EstateOwner; + else + land.landData.OwnerID = m_scene.RegionInfo.MasterAvatarAssignedUUID; + land.landData.ClaimDate = Util.UnixTimeSinceEpoch(); + m_scene.Broadcast(SendParcelOverlay); + land.sendLandUpdateToClient(remote_client); + } + } + } + #endregion + + // If the economy has been validated by the economy module, + // and land has been validated as well, this method transfers + // the land ownership + + public void handleLandBuyRequest(Object o, EventManager.LandBuyArgs e) + { + if (e.economyValidated && e.landValidated) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(e.parcelLocalID, out land); + } + + if (land != null) + { + land.updateLandSold(e.agentId, e.groupId, e.groupOwned, (uint)e.transactionID, e.parcelPrice, e.parcelArea); + } + } + } + + // After receiving a land buy packet, first the data needs to + // be validated. This method validates the right to buy the + // parcel + + public void handleLandValidationRequest(Object o, EventManager.LandBuyArgs e) + { + if (e.landValidated == false) + { + ILandObject lob = null; + lock (m_landList) + { + m_landList.TryGetValue(e.parcelLocalID, out lob); + } + + if (lob != null) + { + UUID AuthorizedID = lob.landData.AuthBuyerID; + int saleprice = lob.landData.SalePrice; + UUID pOwnerID = lob.landData.OwnerID; + + bool landforsale = ((lob.landData.Flags & + (uint)(Parcel.ParcelFlags.ForSale | Parcel.ParcelFlags.ForSaleObjects | Parcel.ParcelFlags.SellParcelObjects)) != 0); + if ((AuthorizedID == UUID.Zero || AuthorizedID == e.agentId) && e.parcelPrice >= saleprice && landforsale) + { + // TODO I don't think we have to lock it here, no? + //lock (e) + //{ + e.parcelOwnerID = pOwnerID; + e.landValidated = true; + //} + } + } + } + } + + #region Land Object From Storage Functions + + public void IncomingLandObjectsFromStorage(List data) + { + for (int i = 0; i < data.Count; i++) + { + IncomingLandObjectFromStorage(data[i]); + } + } + + public void IncomingLandObjectFromStorage(LandData data) + { + ILandObject new_land = new LandObject(data.OwnerID, data.IsGroupOwned, m_scene); + new_land.landData = data.Copy(); + new_land.setLandBitmapFromByteArray(); + AddLandObject(new_land); + } + + public void ReturnObjectsInParcel(int localID, uint returnType, UUID[] agentIDs, UUID[] taskIDs, IClientAPI remoteClient) + { + ILandObject selectedParcel = null; + lock (m_landList) + { + m_landList.TryGetValue(localID, out selectedParcel); + } + + if (selectedParcel == null) return; + + selectedParcel.returnLandObjects(returnType, agentIDs, taskIDs, remoteClient); + } + + public void NoLandDataFromStorage() + { + ResetSimLandObjects(); + } + + #endregion + + public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel) + { + lock (m_landList) + { + foreach (LandObject obj in m_landList.Values) + { + obj.setParcelObjectMaxOverride(overrideDel); + } + } + } + + public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel) + { + } + + #region CAPS handler + + private void OnRegisterCaps(UUID agentID, Caps caps) + { + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("RemoteParcelRequest", + new RestStreamHandler("POST", capsBase + remoteParcelRequestPath, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return RemoteParcelRequest(request, path, param, agentID, caps); + })); + } + + // we cheat here: As we don't have (and want) a grid-global parcel-store, we can't return the + // "real" parcelID, because we wouldn't be able to map that to the region the parcel belongs to. + // So, we create a "fake" parcelID by using the regionHandle (64 bit), and the local (integer) x + // and y coordinate (each 8 bit), encoded in a UUID (128 bit). + // + // Request format: + // + // + // location + // + // 1.23 + // 45..6 + // 78.9 + // + // region_id + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // + // + private string RemoteParcelRequest(string request, string path, string param, UUID agentID, Caps caps) + { + UUID parcelID = UUID.Zero; + try + { + Hashtable hash = new Hashtable(); + hash = (Hashtable)LLSD.LLSDDeserialize(Utils.StringToBytes(request)); + if (hash.ContainsKey("region_id") && hash.ContainsKey("location")) + { + UUID regionID = (UUID)hash["region_id"]; + ArrayList list = (ArrayList)hash["location"]; + uint x = (uint)(double)list[0]; + uint y = (uint)(double)list[1]; + if (hash.ContainsKey("region_handle")) + { + // if you do a "About Landmark" on a landmark a second time, the viewer sends the + // region_handle it got earlier via RegionHandleRequest + ulong regionHandle = Util.BytesToUInt64Big((byte[])hash["region_handle"]); + parcelID = Util.BuildFakeParcelID(regionHandle, x, y); + } + else if (regionID == m_scene.RegionInfo.RegionID) + { + // a parcel request for a local parcel => no need to query the grid + parcelID = Util.BuildFakeParcelID(m_scene.RegionInfo.RegionHandle, x, y); + } + else + { + // a parcel request for a parcel in another region. Ask the grid about the region + RegionInfo info = m_scene.CommsManager.GridService.RequestNeighbourInfo(regionID); + if (info != null) + parcelID = Util.BuildFakeParcelID(info.RegionHandle, x, y); + } + } + } + catch (LLSD.LLSDParseException e) + { + m_log.ErrorFormat("[LAND] Fetch error: {0}", e.Message); + m_log.ErrorFormat("[LAND] ... in request {0}", request); + } + catch(InvalidCastException) + { + m_log.ErrorFormat("[LAND] Wrong type in request {0}", request); + } + + LLSDRemoteParcelResponse response = new LLSDRemoteParcelResponse(); + response.parcel_id = parcelID; + m_log.DebugFormat("[LAND] got parcelID {0}", parcelID); + + return LLSDHelpers.SerialiseLLSDReply(response); + } + + #endregion + + private void handleParcelDwell(int localID, IClientAPI remoteClient) + { + ILandObject selectedParcel = null; + lock (m_landList) + { + if (!m_landList.TryGetValue(localID, out selectedParcel)) + return; + } + + remoteClient.SendParcelDwellReply(localID, selectedParcel.landData.GlobalID, selectedParcel.landData.Dwell); + } + + private void handleParcelInfo(IClientAPI remoteClient, UUID parcelID) + { + if (parcelID == UUID.Zero) + return; + + ExtendedLandData data = (ExtendedLandData)parcelInfoCache.Get(parcelID, delegate(UUID parcel) { + // assume we've got the parcelID we just computed in RemoteParcelRequest + ExtendedLandData extLandData = new ExtendedLandData(); + Util.ParseFakeParcelID(parcel, out extLandData.regionHandle, out extLandData.x, out extLandData.y); + m_log.DebugFormat("[LAND] got parcelinfo request for regionHandle {0}, x/y {1}/{2}", + extLandData.regionHandle, extLandData.x, extLandData.y); + + // for this region or for somewhere else? + if (extLandData.regionHandle == m_scene.RegionInfo.RegionHandle) + { + extLandData.landData = this.GetLandObject(extLandData.x, extLandData.y).landData; + } + else + { + extLandData.landData = m_scene.CommsManager.GridService.RequestLandData(extLandData.regionHandle, + extLandData.x, + extLandData.y); + if (extLandData.landData == null) + { + // we didn't find the region/land => don't cache + return null; + } + } + return extLandData; + }); + + if (data != null) // if we found some data, send it + { + RegionInfo info; + if (data.regionHandle == m_scene.RegionInfo.RegionHandle) + { + info = m_scene.RegionInfo; + } + else + { + // most likely still cached from building the extLandData entry + info = m_scene.CommsManager.GridService.RequestNeighbourInfo(data.regionHandle); + } + // we need to transfer the fake parcelID, not the one in landData, so the viewer can match it to the landmark. + m_log.DebugFormat("[LAND] got parcelinfo for parcel {0} in region {1}; sending...", + data.landData.Name, data.regionHandle); + remoteClient.SendParcelInfo(info, data.landData, parcelID, data.x, data.y); + } + else + m_log.Debug("[LAND] got no parcelinfo; not sending"); + } + + public void setParcelOtherCleanTime(IClientAPI remoteClient, int localID, int otherCleanTime) + { + ILandObject land; + lock (m_landList) + { + m_landList.TryGetValue(localID, out land); + } + + if (land == null) return; + + if (!m_scene.Permissions.CanEditParcel(remoteClient.AgentId, land)) + return; + + land.landData.OtherCleanTime = otherCleanTime; + + UpdateLandObject(localID, land.landData); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Land/LandObject.cs b/OpenSim/Region/CoreModules/World/Land/LandObject.cs new file mode 100644 index 0000000..fc5bef1 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Land/LandObject.cs @@ -0,0 +1,930 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using log4net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Land +{ + /// + /// Keeps track of a specific piece of land's information + /// + public class LandObject : ILandObject + { + #region Member Variables + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private bool[,] m_landBitmap = new bool[64,64]; + + protected LandData m_landData = new LandData(); + protected Scene m_scene; + protected List primsOverMe = new List(); + + public bool[,] landBitmap + { + get { return m_landBitmap; } + set { m_landBitmap = value; } + } + + #endregion + + #region ILandObject Members + + public LandData landData + { + get { return m_landData; } + + set { m_landData = value; } + } + + public UUID regionUUID + { + get { return m_scene.RegionInfo.RegionID; } + } + + #region Constructors + + public LandObject(UUID owner_id, bool is_group_owned, Scene scene) + { + m_scene = scene; + landData.OwnerID = owner_id; + landData.IsGroupOwned = is_group_owned; + } + + #endregion + + #region Member Functions + + #region General Functions + + /// + /// Checks to see if this land object contains a point + /// + /// + /// + /// 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) + { + return (landBitmap[x / 4, y / 4] == true); + } + else + { + return false; + } + } + + public ILandObject Copy() + { + ILandObject newLand = new LandObject(landData.OwnerID, landData.IsGroupOwned, m_scene); + + //Place all new variables here! + newLand.landBitmap = (bool[,]) (landBitmap.Clone()); + newLand.landData = landData.Copy(); + + return newLand; + } + + + static overrideParcelMaxPrimCountDelegate overrideParcelMaxPrimCount; + static overrideSimulatorMaxPrimCountDelegate overrideSimulatorMaxPrimCount; + + public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel) + { + overrideParcelMaxPrimCount = overrideDel; + } + public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel) + { + overrideSimulatorMaxPrimCount = overrideDel; + } + + public int getParcelMaxPrimCount(ILandObject thisObject) + { + if (overrideParcelMaxPrimCount != null) + { + return overrideParcelMaxPrimCount(thisObject); + } + else + { + //Normal Calculations + return Convert.ToInt32( + Math.Round((Convert.ToDecimal(landData.Area) / Convert.ToDecimal(65536)) * m_scene.objectCapacity * + Convert.ToDecimal(m_scene.RegionInfo.RegionSettings.ObjectBonus))); ; + } + } + public int getSimulatorMaxPrimCount(ILandObject thisObject) + { + if (overrideSimulatorMaxPrimCount != null) + { + return overrideSimulatorMaxPrimCount(thisObject); + } + else + { + //Normal Calculations + return m_scene.objectCapacity; + } + } + #endregion + + #region Packet Request Handling + + public void sendLandProperties(int sequence_id, bool snap_selection, int request_result, IClientAPI remote_client) + { + IEstateModule estateModule = m_scene.RequestModuleInterface(); + uint regionFlags = 336723974 & ~((uint)(RegionFlags.AllowLandmark | RegionFlags.AllowSetHome)); + if (estateModule != null) + regionFlags = estateModule.GetRegionFlags(); + + // In a perfect world, this would have worked. + // +// if ((landData.Flags & (uint)Parcel.ParcelFlags.AllowLandmark) != 0) +// regionFlags |= (uint)RegionFlags.AllowLandmark; +// if (landData.OwnerID == remote_client.AgentId) +// regionFlags |= (uint)RegionFlags.AllowSetHome; + remote_client.SendLandProperties(sequence_id, + snap_selection, request_result, landData, + (float)m_scene.RegionInfo.RegionSettings.ObjectBonus, + getParcelMaxPrimCount(this), + getSimulatorMaxPrimCount(this), regionFlags); + } + + public void updateLandProperties(LandUpdateArgs args, IClientAPI remote_client) + { + if (m_scene.Permissions.CanEditParcel(remote_client.AgentId,this)) + { + //Needs later group support + LandData newData = landData.Copy(); + + if (args.AuthBuyerID != newData.AuthBuyerID || args.SalePrice != newData.SalePrice) + { + if (m_scene.Permissions.CanSellParcel(remote_client.AgentId, this)) + { + newData.AuthBuyerID = args.AuthBuyerID; + newData.SalePrice = args.SalePrice; + } + } + newData.Category = args.Category; + newData.Description = args.Desc; + newData.GroupID = args.GroupID; + newData.LandingType = args.LandingType; + newData.MediaAutoScale = args.MediaAutoScale; + newData.MediaID = args.MediaID; + newData.MediaURL = args.MediaURL; + newData.MusicURL = args.MusicURL; + newData.Name = args.Name; + newData.Flags = args.ParcelFlags; + newData.PassHours = args.PassHours; + newData.PassPrice = args.PassPrice; + newData.SnapshotID = args.SnapshotID; + newData.UserLocation = args.UserLocation; + newData.UserLookAt = args.UserLookAt; + + m_scene.LandChannel.UpdateLandObject(landData.LocalID, newData); + + sendLandUpdateToAvatarsOverMe(); + } + } + + public void updateLandSold(UUID avatarID, UUID groupID, bool groupOwned, uint AuctionID, int claimprice, int area) + { + LandData newData = landData.Copy(); + newData.OwnerID = avatarID; + newData.GroupID = groupID; + newData.IsGroupOwned = groupOwned; + //newData.auctionID = AuctionID; + newData.ClaimDate = Util.UnixTimeSinceEpoch(); + newData.ClaimPrice = claimprice; + newData.SalePrice = 0; + newData.AuthBuyerID = UUID.Zero; + newData.Flags &= ~(uint) (Parcel.ParcelFlags.ForSale | Parcel.ParcelFlags.ForSaleObjects | Parcel.ParcelFlags.SellParcelObjects); + m_scene.LandChannel.UpdateLandObject(landData.LocalID, newData); + + sendLandUpdateToAvatarsOverMe(); + } + + public bool isEitherBannedOrRestricted(UUID avatar) + { + if (isBannedFromLand(avatar)) + { + return true; + } + else if (isRestrictedFromLand(avatar)) + { + return true; + } + return false; + } + + public bool isBannedFromLand(UUID avatar) + { + if ((landData.Flags & (uint) Parcel.ParcelFlags.UseBanList) > 0) + { + ParcelManager.ParcelAccessEntry entry = new ParcelManager.ParcelAccessEntry(); + entry.AgentID = avatar; + entry.Flags = AccessList.Ban; + entry.Time = new DateTime(); + if (landData.ParcelAccessList.Contains(entry)) + { + //They are banned, so lets send them a notice about this parcel + return true; + } + } + return false; + } + + public bool isRestrictedFromLand(UUID avatar) + { + if ((landData.Flags & (uint) Parcel.ParcelFlags.UseAccessList) > 0) + { + ParcelManager.ParcelAccessEntry entry = new ParcelManager.ParcelAccessEntry(); + entry.AgentID = avatar; + entry.Flags = AccessList.Access; + entry.Time = new DateTime(); + if (!landData.ParcelAccessList.Contains(entry)) + { + //They are not allowed in this parcel, but not banned, so lets send them a notice about this parcel + return true; + } + } + return false; + } + + public void sendLandUpdateToClient(IClientAPI remote_client) + { + sendLandProperties(0, false, 0, remote_client); + } + + public void sendLandUpdateToAvatarsOverMe() + { + List avatars = m_scene.GetAvatars(); + ILandObject over = null; + for (int i = 0; i < avatars.Count; i++) + { + try + { + over = + m_scene.LandChannel.GetLandObject(Util.Clamp((int)Math.Round(avatars[i].AbsolutePosition.X), 0, 255), + Util.Clamp((int)Math.Round(avatars[i].AbsolutePosition.Y), 0, 255)); + } + catch (Exception) + { + m_log.Warn("[LAND]: " + "unable to get land at x: " + Math.Round(avatars[i].AbsolutePosition.X) + " y: " + + Math.Round(avatars[i].AbsolutePosition.Y)); + } + + if (over != null) + { + if (over.landData.LocalID == landData.LocalID) + { + if (((over.landData.Flags & (uint)Parcel.ParcelFlags.AllowDamage) != 0) && m_scene.RegionInfo.RegionSettings.AllowDamage) + avatars[i].Invulnerable = false; + else + avatars[i].Invulnerable = true; + + sendLandUpdateToClient(avatars[i].ControllingClient); + } + } + } + } + + #endregion + + #region AccessList Functions + + public List createAccessListArrayByFlag(AccessList flag) + { + List list = new List(); + foreach (ParcelManager.ParcelAccessEntry entry in landData.ParcelAccessList) + { + if (entry.Flags == flag) + { + list.Add(entry.AgentID); + } + } + if (list.Count == 0) + { + list.Add(UUID.Zero); + } + + return list; + } + + public void sendAccessList(UUID agentID, UUID sessionID, uint flags, int sequenceID, + IClientAPI remote_client) + { + + if (flags == (uint) AccessList.Access || flags == (uint) AccessList.Both) + { + List avatars = createAccessListArrayByFlag(AccessList.Access); + remote_client.SendLandAccessListData(avatars,(uint) AccessList.Access,landData.LocalID); + } + + if (flags == (uint) AccessList.Ban || flags == (uint) AccessList.Both) + { + List avatars = createAccessListArrayByFlag(AccessList.Ban); + remote_client.SendLandAccessListData(avatars, (uint)AccessList.Ban, landData.LocalID); + } + } + + public void updateAccessList(uint flags, List entries, IClientAPI remote_client) + { + LandData newData = landData.Copy(); + + if (entries.Count == 1 && entries[0].AgentID == UUID.Zero) + { + entries.Clear(); + } + + List toRemove = new List(); + foreach (ParcelManager.ParcelAccessEntry entry in newData.ParcelAccessList) + { + if (entry.Flags == (AccessList)flags) + { + toRemove.Add(entry); + } + } + + foreach (ParcelManager.ParcelAccessEntry entry in toRemove) + { + newData.ParcelAccessList.Remove(entry); + } + foreach (ParcelManager.ParcelAccessEntry entry in entries) + { + ParcelManager.ParcelAccessEntry temp = new ParcelManager.ParcelAccessEntry(); + temp.AgentID = entry.AgentID; + temp.Time = new DateTime(); //Pointless? Yes. + temp.Flags = (AccessList)flags; + + if (!newData.ParcelAccessList.Contains(temp)) + { + newData.ParcelAccessList.Add(temp); + } + } + + m_scene.LandChannel.UpdateLandObject(landData.LocalID, newData); + } + + #endregion + + #region Update Functions + + public void updateLandBitmapByteArray() + { + landData.Bitmap = convertLandBitmapToBytes(); + } + + /// + /// Update all settings in land such as area, bitmap byte array, etc + /// + public void forceUpdateLandInfo() + { + updateAABBAndAreaValues(); + updateLandBitmapByteArray(); + } + + public void setLandBitmapFromByteArray() + { + landBitmap = convertBytesToLandBitmap(); + } + + /// + /// Updates the AABBMin and AABBMax values after area/shape modification of the land object + /// + private void updateAABBAndAreaValues() + { + int min_x = 64; + int min_y = 64; + int max_x = 0; + int max_y = 0; + int tempArea = 0; + int x, y; + for (x = 0; x < 64; x++) + { + for (y = 0; y < 64; y++) + { + if (landBitmap[x, y] == true) + { + if (min_x > x) min_x = x; + if (min_y > y) min_y = y; + if (max_x < x) max_x = x; + if (max_y < y) max_y = y; + tempArea += 16; //16sqm peice of land + } + } + } + int tx = min_x * 4; + if (tx > 255) + tx = 255; + int ty = min_y * 4; + if (ty > 255) + ty = 255; + landData.AABBMin = + new Vector3((float) (min_x * 4), (float) (min_y * 4), + (float) m_scene.Heightmap[tx, ty]); + + tx = max_x * 4; + if (tx > 255) + tx = 255; + ty = max_y * 4; + if (ty > 255) + ty = 255; + landData.AABBMax = + new Vector3((float) (max_x * 4), (float) (max_y * 4), + (float) m_scene.Heightmap[tx, ty]); + landData.Area = tempArea; + } + + #endregion + + #region Land Bitmap Functions + + /// + /// Sets the land's bitmap manually + /// + /// 64x64 block representing where this land is on a map + public void setLandBitmap(bool[,] bitmap) + { + if (bitmap.GetLength(0) != 64 || bitmap.GetLength(1) != 64 || bitmap.Rank != 2) + { + //Throw an exception - The bitmap is not 64x64 + //throw new Exception("Error: Invalid Parcel Bitmap"); + } + else + { + //Valid: Lets set it + landBitmap = bitmap; + forceUpdateLandInfo(); + } + } + + /// + /// Gets the land's bitmap manually + /// + /// + public bool[,] getLandBitmap() + { + return landBitmap; + } + + /// + /// Full sim land object creation + /// + /// + public bool[,] basicFullRegionLandBitmap() + { + return getSquareLandBitmap(0, 0, (int) Constants.RegionSize, (int) Constants.RegionSize); + } + + /// + /// Used to modify the bitmap between the x and y points. Points use 64 scale + /// + /// + /// + /// + /// + /// + public bool[,] getSquareLandBitmap(int start_x, int start_y, int end_x, int end_y) + { + bool[,] tempBitmap = new bool[64,64]; + tempBitmap.Initialize(); + + tempBitmap = modifyLandBitmapSquare(tempBitmap, start_x, start_y, end_x, end_y, true); + return tempBitmap; + } + + /// + /// Change a land bitmap at within a square and set those points to a specific value + /// + /// + /// + /// + /// + /// + /// + /// + public bool[,] modifyLandBitmapSquare(bool[,] land_bitmap, int start_x, int start_y, int end_x, int end_y, + bool set_value) + { + if (land_bitmap.GetLength(0) != 64 || land_bitmap.GetLength(1) != 64 || land_bitmap.Rank != 2) + { + //Throw an exception - The bitmap is not 64x64 + //throw new Exception("Error: Invalid Parcel Bitmap in modifyLandBitmapSquare()"); + } + + int x, y; + for (y = 0; y < 64; y++) + { + for (x = 0; x < 64; x++) + { + if (x >= start_x / 4 && x < end_x / 4 + && y >= start_y / 4 && y < end_y / 4) + { + land_bitmap[x, y] = set_value; + } + } + } + return land_bitmap; + } + + /// + /// Join the true values of 2 bitmaps together + /// + /// + /// + /// + public bool[,] mergeLandBitmaps(bool[,] bitmap_base, bool[,] bitmap_add) + { + if (bitmap_base.GetLength(0) != 64 || bitmap_base.GetLength(1) != 64 || bitmap_base.Rank != 2) + { + //Throw an exception - The bitmap is not 64x64 + throw new Exception("Error: Invalid Parcel Bitmap - Bitmap_base in mergeLandBitmaps"); + } + if (bitmap_add.GetLength(0) != 64 || bitmap_add.GetLength(1) != 64 || bitmap_add.Rank != 2) + { + //Throw an exception - The bitmap is not 64x64 + throw new Exception("Error: Invalid Parcel Bitmap - Bitmap_add in mergeLandBitmaps"); + } + + int x, y; + for (y = 0; y < 64; y++) + { + for (x = 0; x < 64; x++) + { + if (bitmap_add[x, y]) + { + bitmap_base[x, y] = true; + } + } + } + return bitmap_base; + } + + /// + /// Converts the land bitmap to a packet friendly byte array + /// + /// + private byte[] convertLandBitmapToBytes() + { + byte[] tempConvertArr = new byte[512]; + byte tempByte = 0; + int x, y, i, byteNum = 0; + i = 0; + for (y = 0; y < 64; y++) + { + for (x = 0; x < 64; x++) + { + tempByte = Convert.ToByte(tempByte | Convert.ToByte(landBitmap[x, y]) << (i++ % 8)); + if (i % 8 == 0) + { + tempConvertArr[byteNum] = tempByte; + tempByte = (byte) 0; + i = 0; + byteNum++; + } + } + } + return tempConvertArr; + } + + private bool[,] convertBytesToLandBitmap() + { + bool[,] tempConvertMap = new bool[64,64]; + tempConvertMap.Initialize(); + byte tempByte = 0; + int x = 0, y = 0, i = 0, bitNum = 0; + for (i = 0; i < 512; i++) + { + tempByte = landData.Bitmap[i]; + for (bitNum = 0; bitNum < 8; bitNum++) + { + bool bit = Convert.ToBoolean(Convert.ToByte(tempByte >> bitNum) & (byte) 1); + tempConvertMap[x, y] = bit; + x++; + if (x > 63) + { + x = 0; + y++; + } + } + } + return tempConvertMap; + } + + #endregion + + #region Object Select and Object Owner Listing + + public void sendForceObjectSelect(int local_id, int request_type, List returnIDs, IClientAPI remote_client) + { + if (m_scene.Permissions.CanEditParcel(remote_client.AgentId, this)) + { + List resultLocalIDs = new List(); + try + { + lock (primsOverMe) + { + foreach (SceneObjectGroup obj in primsOverMe) + { + if (obj.LocalId > 0) + { + if (request_type == LandChannel.LAND_SELECT_OBJECTS_OWNER && obj.OwnerID == landData.OwnerID) + { + resultLocalIDs.Add(obj.LocalId); + } + else if (request_type == LandChannel.LAND_SELECT_OBJECTS_GROUP && obj.GroupID == landData.GroupID && landData.GroupID != UUID.Zero) + { + resultLocalIDs.Add(obj.LocalId); + } + else if (request_type == LandChannel.LAND_SELECT_OBJECTS_OTHER && + obj.OwnerID != remote_client.AgentId) + { + resultLocalIDs.Add(obj.LocalId); + } + else if (request_type == (int)ObjectReturnType.List && returnIDs.Contains(obj.OwnerID)) + { + resultLocalIDs.Add(obj.LocalId); + } + } + } + } + } catch (InvalidOperationException) + { + m_log.Error("[LAND]: Unable to force select the parcel objects. Arr."); + } + + remote_client.SendForceClientSelectObjects(resultLocalIDs); + } + } + + /// + /// Notify the parcel owner each avatar that owns prims situated on their land. This notification includes + /// aggreagete details such as the number of prims. + /// + /// + /// + /// A + /// + public void sendLandObjectOwners(IClientAPI remote_client) + { + if (m_scene.Permissions.CanEditParcel(remote_client.AgentId, this)) + { + Dictionary primCount = new Dictionary(); + + lock (primsOverMe) + { + try + { + + foreach (SceneObjectGroup obj in primsOverMe) + { + try + { + if (!primCount.ContainsKey(obj.OwnerID)) + { + primCount.Add(obj.OwnerID, 0); + } + } + catch (NullReferenceException) + { + m_log.Info("[LAND]: " + "Got Null Reference when searching land owners from the parcel panel"); + } + try + { + primCount[obj.OwnerID] += obj.PrimCount; + } + catch (KeyNotFoundException) + { + m_log.Error("[LAND]: Unable to match a prim with it's owner."); + } + } + } + catch (InvalidOperationException) + { + m_log.Error("[LAND]: Unable to Enumerate Land object arr."); + } + } + + remote_client.SendLandObjectOwners(primCount); + } + } + + public Dictionary getLandObjectOwners() + { + Dictionary ownersAndCount = new Dictionary(); + lock (primsOverMe) + { + try + { + + foreach (SceneObjectGroup obj in primsOverMe) + { + if (!ownersAndCount.ContainsKey(obj.OwnerID)) + { + ownersAndCount.Add(obj.OwnerID, 0); + } + ownersAndCount[obj.OwnerID] += obj.PrimCount; + } + } + catch (InvalidOperationException) + { + m_log.Error("[LAND]: Unable to enumerate land owners. arr."); + } + + } + return ownersAndCount; + } + + #endregion + + #region Object Returning + + public void returnObject(SceneObjectGroup obj) + { + SceneObjectGroup[] objs = new SceneObjectGroup[1]; + objs[0] = obj; + m_scene.returnObjects(objs, obj.OwnerID); + } + + public void returnLandObjects(uint type, UUID[] owners, UUID[] tasks, IClientAPI remote_client) + { + Dictionary> returns = + new Dictionary>(); + + lock (primsOverMe) + { + if (type == (uint)ObjectReturnType.Owner) + { + foreach (SceneObjectGroup obj in primsOverMe) + { + if (obj.OwnerID == m_landData.OwnerID) + { + if (!returns.ContainsKey(obj.OwnerID)) + returns[obj.OwnerID] = + new List(); + returns[obj.OwnerID].Add(obj); + } + } + } + else if (type == (uint)ObjectReturnType.Group && m_landData.GroupID != UUID.Zero) + { + foreach (SceneObjectGroup obj in primsOverMe) + { + if (obj.GroupID == m_landData.GroupID) + { + if (!returns.ContainsKey(obj.OwnerID)) + returns[obj.OwnerID] = + new List(); + returns[obj.OwnerID].Add(obj); + } + } + } + else if (type == (uint)ObjectReturnType.Other) + { + foreach (SceneObjectGroup obj in primsOverMe) + { + if (obj.OwnerID != m_landData.OwnerID && + (obj.GroupID != m_landData.GroupID || + m_landData.GroupID == UUID.Zero)) + { + if (!returns.ContainsKey(obj.OwnerID)) + returns[obj.OwnerID] = + new List(); + returns[obj.OwnerID].Add(obj); + } + } + } + else if (type == (uint)ObjectReturnType.List) + { + List ownerlist = new List(owners); + + foreach (SceneObjectGroup obj in primsOverMe) + { + if (ownerlist.Contains(obj.OwnerID)) + { + if (!returns.ContainsKey(obj.OwnerID)) + returns[obj.OwnerID] = + new List(); + returns[obj.OwnerID].Add(obj); + } + } + } + } + + foreach (List ol in returns.Values) + m_scene.returnObjects(ol.ToArray(), remote_client.AgentId); + } + + #endregion + + #region Object Adding/Removing from Parcel + + public void resetLandPrimCounts() + { + landData.GroupPrims = 0; + landData.OwnerPrims = 0; + landData.OtherPrims = 0; + landData.SelectedPrims = 0; + + + lock (primsOverMe) + primsOverMe.Clear(); + } + + public void addPrimToCount(SceneObjectGroup obj) + { + + UUID prim_owner = obj.OwnerID; + int prim_count = obj.PrimCount; + + if (obj.IsSelected) + { + landData.SelectedPrims += prim_count; + } + else + { + if (prim_owner == landData.OwnerID) + { + landData.OwnerPrims += prim_count; + } + else if ((obj.GroupID == landData.GroupID || + prim_owner == landData.GroupID) && + landData.GroupID != UUID.Zero) + { + landData.GroupPrims += prim_count; + } + else + { + landData.OtherPrims += prim_count; + } + } + + lock (primsOverMe) + primsOverMe.Add(obj); + } + + public void removePrimFromCount(SceneObjectGroup obj) + { + lock (primsOverMe) + { + if (primsOverMe.Contains(obj)) + { + UUID prim_owner = obj.OwnerID; + int prim_count = obj.PrimCount; + + if (prim_owner == landData.OwnerID) + { + landData.OwnerPrims -= prim_count; + } + else if (obj.GroupID == landData.GroupID || + prim_owner == landData.GroupID) + { + landData.GroupPrims -= prim_count; + } + else + { + landData.OtherPrims -= prim_count; + } + + primsOverMe.Remove(obj); + } + } + } + + #endregion + + #endregion + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs new file mode 100644 index 0000000..1469f5d --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Permissions/PermissionsModule.cs @@ -0,0 +1,1498 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using Nini.Config; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using OpenSim; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Framework.Communications.Cache; + +namespace OpenSim.Region.CoreModules.World.Permissions +{ + public class PermissionsModule : IRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + #region Constants + // These are here for testing. They will be taken out + + //private uint PERM_ALL = (uint)2147483647; + private uint PERM_COPY = (uint)32768; + //private uint PERM_MODIFY = (uint)16384; + private uint PERM_MOVE = (uint)524288; + //private uint PERM_TRANS = (uint)8192; + private uint PERM_LOCKED = (uint)540672; + + /// + /// Different user set names that come in from the configuration file. + /// + enum UserSet + { + All, + Administrators + }; + + #endregion + + #region Bypass Permissions / Debug Permissions Stuff + + // Bypasses the permissions engine + private bool m_bypassPermissions = true; + private bool m_bypassPermissionsValue = true; + private bool m_propagatePermissions = false; + private bool m_debugPermissions = false; + private bool m_allowGridGods = false; + private bool m_RegionOwnerIsGod = false; + private bool m_ParcelOwnerIsGod = false; + + /// + /// The set of users that are allowed to create scripts. This is only active if permissions are not being + /// bypassed. This overrides normal permissions. + /// + private UserSet m_allowedScriptCreators = UserSet.All; + + /// + /// The set of users that are allowed to edit (save) scripts. This is only active if + /// permissions are not being bypassed. This overrides normal permissions.- + /// + private UserSet m_allowedScriptEditors = UserSet.All; + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + + IConfig myConfig = config.Configs["Startup"]; + + string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); + + List modules=new List(permissionModules.Split(',')); + + if (!modules.Contains("DefaultPermissionsModule")) + return; + + m_allowGridGods = myConfig.GetBoolean("allow_grid_gods", false); + m_bypassPermissions = !myConfig.GetBoolean("serverside_object_permissions", true); + m_propagatePermissions = myConfig.GetBoolean("propagate_permissions", true); + m_RegionOwnerIsGod = myConfig.GetBoolean("region_owner_is_god", true); + m_ParcelOwnerIsGod = myConfig.GetBoolean("parcel_owner_is_god", true); + + m_allowedScriptCreators + = ParseUserSetConfigSetting(myConfig, "allowed_script_creators", m_allowedScriptCreators); + m_allowedScriptEditors + = ParseUserSetConfigSetting(myConfig, "allowed_script_editors", m_allowedScriptEditors); + + if (m_bypassPermissions) + m_log.Info("[PERMISSIONS]: serviceside_object_permissions = false in ini file so disabling all region service permission checks"); + else + m_log.Debug("[PERMISSIONS]: Enabling all region service permission checks"); + + //Register functions with Scene External Checks! + m_scene.Permissions.AddBypassPermissionsHandler(BypassPermissions); //FULLY IMPLEMENTED + m_scene.Permissions.AddSetBypassPermissionsHandler(SetBypassPermissions); //FULLY IMPLEMENTED + m_scene.Permissions.AddPropagatePermissionsHandler(PropagatePermissions); //FULLY IMPLEMENTED + m_scene.Permissions.AddGenerateClientFlagsHandler(GenerateClientFlags); //NOT YET FULLY IMPLEMENTED + m_scene.Permissions.AddAbandonParcelHandler(CanAbandonParcel); //FULLY IMPLEMENTED + m_scene.Permissions.AddReclaimParcelHandler(CanReclaimParcel); //FULLY IMPLEMENTED + m_scene.Permissions.AddIsGodHandler(IsGod); //FULLY IMPLEMENTED + m_scene.Permissions.AddDuplicateObjectHandler(CanDuplicateObject); //FULLY IMPLEMENTED + m_scene.Permissions.AddDeleteObjectHandler(CanDeleteObject); //MAYBE FULLY IMPLEMENTED + m_scene.Permissions.AddEditObjectHandler(CanEditObject);//MAYBE FULLY IMPLEMENTED + m_scene.Permissions.AddEditParcelHandler(CanEditParcel); //FULLY IMPLEMENTED + m_scene.Permissions.AddInstantMessageHandler(CanInstantMessage); //FULLY IMPLEMENTED + m_scene.Permissions.AddInventoryTransferHandler(CanInventoryTransfer); //NOT YET IMPLEMENTED + m_scene.Permissions.AddIssueEstateCommandHandler(CanIssueEstateCommand); //FULLY IMPLEMENTED + m_scene.Permissions.AddMoveObjectHandler(CanMoveObject); //HOPEFULLY FULLY IMPLEMENTED + m_scene.Permissions.AddObjectEntryHandler(CanObjectEntry); //FULLY IMPLEMENTED + m_scene.Permissions.AddReturnObjectHandler(CanReturnObject); //NOT YET IMPLEMENTED + m_scene.Permissions.AddRezObjectHandler(CanRezObject); //HOPEFULLY FULLY IMPLEMENTED + m_scene.Permissions.AddRunConsoleCommandHandler(CanRunConsoleCommand); //FULLY IMPLEMENTED + m_scene.Permissions.AddRunScriptHandler(CanRunScript); //NOT YET IMPLEMENTED + m_scene.Permissions.AddSellParcelHandler(CanSellParcel); //FULLY IMPLEMENTED + m_scene.Permissions.AddTakeObjectHandler(CanTakeObject); //FULLY IMPLEMENTED + m_scene.Permissions.AddTakeCopyObjectHandler(CanTakeCopyObject); //FULLY IMPLEMENTED + m_scene.Permissions.AddTerraformLandHandler(CanTerraformLand); //FULL IMPLEMENTED (POINT ONLY!!! NOT AREA!!!) + m_scene.Permissions.AddCanLinkObjectHandler(CanLinkObject); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanDelinkObjectHandler(CanDelinkObject); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanBuyLandHandler(CanBuyLand); //NOT YET IMPLEMENTED + + m_scene.Permissions.AddViewNotecardHandler(CanViewNotecard); //NOT YET IMPLEMENTED + m_scene.Permissions.AddViewScriptHandler(CanViewScript); //NOT YET IMPLEMENTED + m_scene.Permissions.AddEditNotecardHandler(CanEditNotecard); //NOT YET IMPLEMENTED + m_scene.Permissions.AddEditScriptHandler(CanEditScript); //NOT YET IMPLEMENTED + + m_scene.Permissions.AddCanCreateObjectInventoryHandler(CanCreateObjectInventory); //NOT IMPLEMENTED HERE + m_scene.Permissions.AddEditObjectInventoryHandler(CanEditObjectInventory);//MAYBE FULLY IMPLEMENTED + m_scene.Permissions.AddCanCopyObjectInventoryHandler(CanCopyObjectInventory); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanDeleteObjectInventoryHandler(CanDeleteObjectInventory); //NOT YET IMPLEMENTED + m_scene.Permissions.AddResetScriptHandler(CanResetScript); + + m_scene.Permissions.AddCanCreateUserInventoryHandler(CanCreateUserInventory); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanCopyUserInventoryHandler(CanCopyUserInventory); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanEditUserInventoryHandler(CanEditUserInventory); //NOT YET IMPLEMENTED + m_scene.Permissions.AddCanDeleteUserInventoryHandler(CanDeleteUserInventory); //NOT YET IMPLEMENTED + + m_scene.Permissions.AddCanTeleportHandler(CanTeleport); //NOT YET IMPLEMENTED + + m_scene.AddCommand("permissions", "bypass permissions", + "bypass permissions ", + "Bypass permission checks", + HandleBypassPermissions); + + m_scene.AddCommand("permissions", "force permissions", + "force permissions ", + "Force permissions on or off", + HandleForcePermissions); + + m_scene.AddCommand("permissions", "debug permissions", + "debug permissions ", + "Enable permissions debugging", + HandleDebugPermissions); + } + + public void HandleBypassPermissions(string module, string[] args) + { + if (m_scene.ConsoleScene() != null && + m_scene.ConsoleScene() != m_scene) + { + return; + } + + if (args.Length > 2) + { + bool val; + + if (!bool.TryParse(args[2], out val)) + return; + + m_bypassPermissions = val; + + m_log.InfoFormat( + "[PERMISSIONS]: Set permissions bypass to {0} for {1}", + m_bypassPermissions, m_scene.RegionInfo.RegionName); + } + } + + public void HandleForcePermissions(string module, string[] args) + { + if (m_scene.ConsoleScene() != null && + m_scene.ConsoleScene() != m_scene) + { + return; + } + + if (!m_bypassPermissions) + { + m_log.Error("[PERMISSIONS] Permissions can't be forced unless they are bypassed first"); + return; + } + + if (args.Length > 2) + { + bool val; + + if (!bool.TryParse(args[2], out val)) + return; + + m_bypassPermissionsValue = val; + + m_log.InfoFormat("[PERMISSIONS] Forced permissions to {0} in {1}", m_bypassPermissionsValue, m_scene.RegionInfo.RegionName); + } + } + + public void HandleDebugPermissions(string module, string[] args) + { + if (m_scene.ConsoleScene() != null && + m_scene.ConsoleScene() != m_scene) + { + return; + } + + if (args.Length > 2) + { + bool val; + + if (!bool.TryParse(args[2], out val)) + return; + + m_debugPermissions = val; + + m_log.InfoFormat("[PERMISSIONS] Set permissions debugging to {0} in {1}", m_debugPermissions, m_scene.RegionInfo.RegionName); + } + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "PermissionsModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region Helper Functions + protected void SendPermissionError(UUID user, string reason) + { + m_scene.EventManager.TriggerPermissionError(user, reason); + } + + protected void DebugPermissionInformation(string permissionCalled) + { + if (m_debugPermissions) + m_log.Debug("[PERMISSIONS]: " + permissionCalled + " was called from " + m_scene.RegionInfo.RegionName); + } + + /// + /// Parse a user set configuration setting + /// + /// + /// + /// The default value for this attribute + /// The parsed value + private static UserSet ParseUserSetConfigSetting(IConfig config, string settingName, UserSet defaultValue) + { + UserSet userSet = defaultValue; + + string rawSetting = config.GetString(settingName, defaultValue.ToString()); + + // Temporary measure to allow 'gods' to be specified in config for consistency's sake. In the long term + // this should disappear. + if ("gods" == rawSetting.ToLower()) + rawSetting = UserSet.Administrators.ToString(); + + // Doing it this was so that we can do a case insensitive conversion + try + { + userSet = (UserSet)Enum.Parse(typeof(UserSet), rawSetting, true); + } + catch + { + m_log.ErrorFormat( + "[PERMISSIONS]: {0} is not a valid {1} value, setting to {2}", + rawSetting, settingName, userSet); + } + + m_log.DebugFormat("[PERMISSIONS]: {0} {1}", settingName, userSet); + + return userSet; + } + + /// + /// Is the given user an administrator (in other words, a god)? + /// + /// + /// + protected bool IsAdministrator(UUID user) + { + if (m_scene.RegionInfo.MasterAvatarAssignedUUID != UUID.Zero) + { + if (m_RegionOwnerIsGod && (m_scene.RegionInfo.MasterAvatarAssignedUUID == user)) + return true; + } + + if (m_scene.RegionInfo.EstateSettings.EstateOwner != UUID.Zero) + { + if (m_scene.RegionInfo.EstateSettings.EstateOwner == user) + return true; + } + + if (m_allowGridGods) + { + CachedUserInfo profile = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(user); + if (profile != null && profile.UserProfile != null) + { + if (profile.UserProfile.GodLevel >= 200) + return true; + } + else + { + m_log.ErrorFormat("[PERMISSIONS]: Could not find user {0} for administrator check", user); + } + } + + return false; + } + + protected bool IsEstateManager(UUID user) + { + return m_scene.RegionInfo.EstateSettings.IsEstateManager(user); + } +#endregion + + public bool PropagatePermissions() + { + if (m_bypassPermissions) + return false; + + return m_propagatePermissions; + } + + public bool BypassPermissions() + { + return m_bypassPermissions; + } + + public void SetBypassPermissions(bool value) + { + m_bypassPermissions=value; + } + + #region Object Permissions + + public uint GenerateClientFlags(UUID user, UUID objID) + { + // Here's the way this works, + // ObjectFlags and Permission flags are two different enumerations + // ObjectFlags, however, tells the client to change what it will allow the user to do. + // So, that means that all of the permissions type ObjectFlags are /temporary/ and only + // supposed to be set when customizing the objectflags for the client. + + // These temporary objectflags get computed and added in this function based on the + // Permission mask that's appropriate! + // Outside of this method, they should never be added to objectflags! + // -teravus + + SceneObjectPart task = m_scene.GetSceneObjectPart(objID); + + // this shouldn't ever happen.. return no permissions/objectflags. + if (task == null) + return (uint)0; + + uint objflags = task.GetEffectiveObjectFlags(); + UUID objectOwner = task.OwnerID; + + + // Remove any of the objectFlags that are temporary. These will get added back if appropriate + // in the next bit of code + + // libomv will moan about PrimFlags.ObjectYouOfficer being + // deprecated + #pragma warning disable 0612 + objflags &= (uint) + ~(PrimFlags.ObjectCopy | // Tells client you can copy the object + PrimFlags.ObjectModify | // tells client you can modify the object + PrimFlags.ObjectMove | // tells client that you can move the object (only, no mod) + PrimFlags.ObjectTransfer | // tells the client that you can /take/ the object if you don't own it + PrimFlags.ObjectYouOwner | // Tells client that you're the owner of the object + PrimFlags.ObjectAnyOwner | // Tells client that someone owns the object + PrimFlags.ObjectOwnerModify | // Tells client that you're the owner of the object + PrimFlags.ObjectYouOfficer // Tells client that you've got group object editing permission. Used when ObjectGroupOwned is set + ); + #pragma warning restore 0612 + + // Creating the three ObjectFlags options for this method to choose from. + // Customize the OwnerMask + uint objectOwnerMask = ApplyObjectModifyMasks(task.OwnerMask, objflags); + objectOwnerMask |= (uint)PrimFlags.ObjectYouOwner | (uint)PrimFlags.ObjectAnyOwner | (uint)PrimFlags.ObjectOwnerModify; + + // Customize the GroupMask + // uint objectGroupMask = ApplyObjectModifyMasks(task.GroupMask, objflags); + + // Customize the EveryoneMask + uint objectEveryoneMask = ApplyObjectModifyMasks(task.EveryoneMask, objflags); + + + // Hack to allow collaboration until Groups and Group Permissions are implemented + if ((objectEveryoneMask & (uint)PrimFlags.ObjectMove) != 0) + objectEveryoneMask |= (uint)PrimFlags.ObjectModify; + + if (m_bypassPermissions) + return objectOwnerMask; + + // Object owners should be able to edit their own content + if (user == objectOwner) + { + return objectOwnerMask; + } + + // Users should be able to edit what is over their land. + ILandObject parcel = m_scene.LandChannel.GetLandObject(task.AbsolutePosition.X, task.AbsolutePosition.Y); + if (parcel != null && parcel.landData.OwnerID == user && m_ParcelOwnerIsGod) + return objectOwnerMask; + + // Admin objects should not be editable by the above + if (IsAdministrator(objectOwner)) + return objectEveryoneMask; + + // Estate users should be able to edit anything in the sim + if (IsEstateManager(user) && m_RegionOwnerIsGod) + return objectOwnerMask; + + // Admin should be able to edit anything in the sim (including admin objects) + if (IsAdministrator(user)) + return objectOwnerMask; + + + return objectEveryoneMask; + } + + private uint ApplyObjectModifyMasks(uint setPermissionMask, uint objectFlagsMask) + { + // We are adding the temporary objectflags to the object's objectflags based on the + // permission flag given. These change the F flags on the client. + + if ((setPermissionMask & (uint)PermissionMask.Copy) != 0) + { + objectFlagsMask |= (uint)PrimFlags.ObjectCopy; + } + + if ((setPermissionMask & (uint)PermissionMask.Move) != 0) + { + objectFlagsMask |= (uint)PrimFlags.ObjectMove; + } + + if ((setPermissionMask & (uint)PermissionMask.Modify) != 0) + { + objectFlagsMask |= (uint)PrimFlags.ObjectModify; + } + + if ((setPermissionMask & (uint)PermissionMask.Transfer) != 0) + { + objectFlagsMask |= (uint)PrimFlags.ObjectTransfer; + } + + return objectFlagsMask; + } + + /// + /// General permissions checks for any operation involving an object. These supplement more specific checks + /// implemented by callers. + /// + /// + /// + /// + /// + protected bool GenericObjectPermission(UUID currentUser, UUID objId, bool denyOnLocked) + { + // Default: deny + bool permission = false; + bool locked = false; + + if (!m_scene.Entities.ContainsKey(objId)) + { + return false; + } + + // If it's not an object, we cant edit it. + if ((!(m_scene.Entities[objId] is SceneObjectGroup))) + { + return false; + } + + SceneObjectGroup group = (SceneObjectGroup)m_scene.Entities[objId]; + + UUID objectOwner = group.OwnerID; + locked = ((group.RootPart.OwnerMask & PERM_LOCKED) == 0); + + // People shouldn't be able to do anything with locked objects, except the Administrator + // The 'set permissions' runs through a different permission check, so when an object owner + // sets an object locked, the only thing that they can do is unlock it. + // + // Nobody but the object owner can set permissions on an object + // + + if (locked && (!IsAdministrator(currentUser)) && denyOnLocked) + { + return false; + } + + // Object owners should be able to edit their own content + if (currentUser == objectOwner) + { + permission = true; + } + else if (group.IsAttachment) + { + permission = false; + } + + // Users should be able to edit what is over their land. + ILandObject parcel = m_scene.LandChannel.GetLandObject(group.AbsolutePosition.X, group.AbsolutePosition.Y); + if ((parcel != null) && (parcel.landData.OwnerID == currentUser)) + { + permission = true; + } + + // Estate users should be able to edit anything in the sim + if (IsEstateManager(currentUser)) + { + permission = true; + } + + // Admin objects should not be editable by the above + if (IsAdministrator(objectOwner)) + { + permission = false; + } + + // Admin should be able to edit anything in the sim (including admin objects) + if (IsAdministrator(currentUser)) + { + permission = true; + } + + return permission; + } + + #endregion + + #region Generic Permissions + protected bool GenericCommunicationPermission(UUID user, UUID target) + { + // Setting this to true so that cool stuff can happen until we define what determines Generic Communication Permission + bool permission = true; + string reason = "Only registered users may communicate with another account."; + + // Uhh, we need to finish this before we enable it.. because it's blocking all sorts of goodies and features + if (IsAdministrator(user)) + permission = true; + + if (IsEstateManager(user)) + permission = true; + + if (!permission) + SendPermissionError(user, reason); + + return permission; + } + + public bool GenericEstatePermission(UUID user) + { + // Default: deny + bool permission = false; + + // Estate admins should be able to use estate tools + if (IsEstateManager(user)) + permission = true; + + // Administrators always have permission + if (IsAdministrator(user)) + permission = true; + + return permission; + } + + protected bool GenericParcelPermission(UUID user, ILandObject parcel) + { + bool permission = false; + + if (parcel.landData.OwnerID == user) + { + permission = true; + } + + if (parcel.landData.IsGroupOwned) + { + // TODO: Need to do some extra checks here. Requires group code. + } + + if (IsEstateManager(user)) + { + permission = true; + } + + if (IsAdministrator(user)) + { + permission = true; + } + + return permission; + } + + protected bool GenericParcelPermission(UUID user, Vector3 pos) + { + ILandObject parcel = m_scene.LandChannel.GetLandObject(pos.X, pos.Y); + if (parcel == null) return false; + return GenericParcelPermission(user, parcel); + } +#endregion + + #region Permission Checks + private bool CanAbandonParcel(UUID user, ILandObject parcel, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericParcelPermission(user, parcel); + } + + private bool CanReclaimParcel(UUID user, ILandObject parcel, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericParcelPermission(user, parcel); + } + + private bool IsGod(UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return IsAdministrator(user); + } + + private bool CanDuplicateObject(int objectCount, UUID objectID, UUID owner, Scene scene, Vector3 objectPosition) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (!GenericObjectPermission(owner, objectID, true)) + { + //They can't even edit the object + return false; + } + + SceneObjectPart part = scene.GetSceneObjectPart(objectID); + if (part == null) + return false; + + if ((part.OwnerMask & PERM_COPY) == 0) + return false; + + if ((part.ParentGroup.GetEffectivePermissions() & PERM_COPY) == 0) + return false; + + //If they can rez, they can duplicate + return CanRezObject(objectCount, owner, objectPosition, scene); + } + + private bool CanDeleteObject(UUID objectID, UUID deleter, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericObjectPermission(deleter, objectID, false); + } + + private bool CanEditObject(UUID objectID, UUID editorID, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + + return GenericObjectPermission(editorID, objectID, false); + } + + private bool CanEditObjectInventory(UUID objectID, UUID editorID, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + SceneObjectPart part = m_scene.GetSceneObjectPart(objectID); + + // If we selected a sub-prim to edit, the objectID won't represent the object, but only a part. + // We have to check the permissions of the group, though. + if (part.ParentID != 0) + { + objectID = part.ParentUUID; + part = m_scene.GetSceneObjectPart(objectID); + } + + // TODO: add group support! + // + if (part.OwnerID != editorID) + return false; + + return GenericObjectPermission(editorID, objectID, false); + } + + private bool CanEditParcel(UUID user, ILandObject parcel, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericParcelPermission(user, parcel); + } + + /// + /// Check whether the specified user can edit the given script + /// + /// + /// + /// + /// + /// + private bool CanEditScript(UUID script, UUID objectID, UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (m_allowedScriptEditors == UserSet.Administrators && !IsAdministrator(user)) + return false; + + // Ordinarily, if you can view it, you can edit it + // There is no viewing a no mod script + // + return CanViewScript(script, objectID, user, scene); + } + + /// + /// Check whether the specified user can edit the given notecard + /// + /// + /// + /// + /// + /// + private bool CanEditNotecard(UUID notecard, UUID objectID, UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (objectID == UUID.Zero) // User inventory + { + CachedUserInfo userInfo = + scene.CommsManager.UserProfileCacheService.GetUserDetails(user); + + if (userInfo == null) + { + m_log.ErrorFormat("[PERMISSIONS]: Could not find user {0} for edit notecard check", user); + return false; + } + + if (userInfo.RootFolder == null) + return false; + + InventoryItemBase assetRequestItem = userInfo.RootFolder.FindItem(notecard); + if (assetRequestItem == null) // Library item + { + assetRequestItem = scene.CommsManager.UserProfileCacheService.LibraryRoot.FindItem(notecard); + + if (assetRequestItem != null) // Implicitly readable + return true; + } + + // Notecards must be both mod and copy to be saveable + // This is because of they're not copy, you can't read + // them, and if they're not mod, well, then they're + // not mod. Duh. + // + if ((assetRequestItem.CurrentPermissions & + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy)) != + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy)) + return false; + } + else // Prim inventory + { + SceneObjectPart part = scene.GetSceneObjectPart(objectID); + + if (part == null) + return false; + + if (part.OwnerID != user) + return false; + + if ((part.OwnerMask & (uint)PermissionMask.Modify) == 0) + return false; + + TaskInventoryItem ti = part.Inventory.GetInventoryItem(notecard); + + if (ti == null) + return false; + + if (ti.OwnerID != user) + return false; + + // Require full perms + if ((ti.CurrentPermissions & + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy)) != + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy)) + return false; + } + + return true; + } + + private bool CanInstantMessage(UUID user, UUID target, Scene startScene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + // If the sender is an object, check owner instead + // + SceneObjectPart part = startScene.GetSceneObjectPart(user); + if (part != null) + user = part.OwnerID; + + return GenericCommunicationPermission(user, target); + } + + private bool CanInventoryTransfer(UUID user, UUID target, Scene startScene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericCommunicationPermission(user, target); + } + + private bool CanIssueEstateCommand(UUID user, Scene requestFromScene, bool ownerCommand) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (IsAdministrator(user)) + return true; + + if (m_scene.RegionInfo.EstateSettings.IsEstateOwner(user)) + return true; + + if (ownerCommand) + return false; + + return GenericEstatePermission(user); + } + + private bool CanMoveObject(UUID objectID, UUID moverID, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) + { + SceneObjectPart part = scene.GetSceneObjectPart(objectID); + if (part.OwnerID != moverID) + { + if (part.ParentGroup != null && !part.ParentGroup.IsDeleted) + { + if (part.ParentGroup.IsAttachment) + return false; + } + } + return m_bypassPermissionsValue; + } + + bool permission = GenericObjectPermission(moverID, objectID, true); + if (!permission) + { + if (!m_scene.Entities.ContainsKey(objectID)) + { + return false; + } + + // The client + // may request to edit linked parts, and therefore, it needs + // to also check for SceneObjectPart + + // If it's not an object, we cant edit it. + if ((!(m_scene.Entities[objectID] is SceneObjectGroup))) + { + return false; + } + + + SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; + + + // UUID taskOwner = null; + // Added this because at this point in time it wouldn't be wise for + // the administrator object permissions to take effect. + // UUID objectOwner = task.OwnerID; + + // Anyone can move + if ((task.RootPart.EveryoneMask & PERM_MOVE) != 0) + permission = true; + + // Locked + if ((task.RootPart.OwnerMask & PERM_LOCKED) == 0) + permission = false; + } + else + { + bool locked = false; + if (!m_scene.Entities.ContainsKey(objectID)) + { + return false; + } + + // If it's not an object, we cant edit it. + if ((!(m_scene.Entities[objectID] is SceneObjectGroup))) + { + return false; + } + + SceneObjectGroup group = (SceneObjectGroup)m_scene.Entities[objectID]; + + UUID objectOwner = group.OwnerID; + locked = ((group.RootPart.OwnerMask & PERM_LOCKED) == 0); + + // This is an exception to the generic object permission. + // Administrators who lock their objects should not be able to move them, + // however generic object permission should return true. + // This keeps locked objects from being affected by random click + drag actions by accident + // and allows the administrator to grab or delete a locked object. + + // Administrators and estate managers are still able to click+grab locked objects not + // owned by them in the scene + // This is by design. + + if (locked && (moverID == objectOwner)) + return false; + } + return permission; + } + + private bool CanObjectEntry(UUID objectID, bool enteringRegion, Vector3 newPoint, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if ((newPoint.X > 257f || newPoint.X < -1f || newPoint.Y > 257f || newPoint.Y < -1f)) + { + return true; + } + + SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; + + ILandObject land = m_scene.LandChannel.GetLandObject(newPoint.X, newPoint.Y); + + if (!enteringRegion) + { + ILandObject fromland = m_scene.LandChannel.GetLandObject(task.AbsolutePosition.X, task.AbsolutePosition.Y); + + if (fromland == land) // Not entering + return true; + } + + if (land == null) + { + return false; + } + + if ((land.landData.Flags & ((int)Parcel.ParcelFlags.AllowAPrimitiveEntry)) != 0) + { + return true; + } + + //TODO: check for group rights + + if (!m_scene.Entities.ContainsKey(objectID)) + { + return false; + } + + // If it's not an object, we cant edit it. + if (!(m_scene.Entities[objectID] is SceneObjectGroup)) + { + return false; + } + + + if (GenericParcelPermission(task.OwnerID, newPoint)) + { + return true; + } + + //Otherwise, false! + return false; + } + + private bool CanReturnObject(UUID objectID, UUID returnerID, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericObjectPermission(returnerID, objectID, false); + } + + private bool CanRezObject(int objectCount, UUID owner, Vector3 objectPosition, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + bool permission = false; + + ILandObject land = m_scene.LandChannel.GetLandObject(objectPosition.X, objectPosition.Y); + if (land == null) return false; + + if ((land.landData.Flags & ((int)Parcel.ParcelFlags.CreateObjects)) == + (int)Parcel.ParcelFlags.CreateObjects) + permission = true; + + //TODO: check for group rights + + if (IsAdministrator(owner)) + { + permission = true; + } + + if (GenericParcelPermission(owner, objectPosition)) + { + permission = true; + } + + return permission; + } + + private bool CanRunConsoleCommand(UUID user, Scene requestFromScene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + + return IsAdministrator(user); + } + + private bool CanRunScript(UUID script, UUID objectID, UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanSellParcel(UUID user, ILandObject parcel, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericParcelPermission(user, parcel); + } + + private bool CanTakeObject(UUID objectID, UUID stealer, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return GenericObjectPermission(stealer,objectID, false); + } + + private bool CanTakeCopyObject(UUID objectID, UUID userID, Scene inScene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + bool permission = GenericObjectPermission(userID, objectID,false); + if (!permission) + { + if (!m_scene.Entities.ContainsKey(objectID)) + { + return false; + } + + // If it's not an object, we cant edit it. + if (!(m_scene.Entities[objectID] is SceneObjectGroup)) + { + return false; + } + + SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; + // UUID taskOwner = null; + // Added this because at this point in time it wouldn't be wise for + // the administrator object permissions to take effect. + // UUID objectOwner = task.OwnerID; + + if ((task.RootPart.EveryoneMask & PERM_COPY) != 0) + permission = true; + + if ((task.GetEffectivePermissions() & PERM_COPY) == 0) + permission = false; + } + else + { + SceneObjectGroup task = (SceneObjectGroup)m_scene.Entities[objectID]; + + if ((task.GetEffectivePermissions() & PERM_COPY) == 0) + permission = false; + } + + return permission; + } + + private bool CanTerraformLand(UUID user, Vector3 position, Scene requestFromScene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + // Estate override + if (GenericEstatePermission(user)) + return true; + + float X = position.X; + float Y = position.Y; + + if (X > 255) + X = 255; + if (Y > 255) + Y = 255; + if (X < 0) + X = 0; + if (Y < 0) + Y = 0; + + ILandObject parcel = m_scene.LandChannel.GetLandObject(X, Y); + if (parcel == null) + return false; + + // Others allowed to terraform? + if ((parcel.landData.Flags & ((int)Parcel.ParcelFlags.AllowTerraform)) != 0) + return true; + + // Land owner can terraform too + if (parcel != null && GenericParcelPermission(user, parcel)) + return true; + + return false; + } + + /// + /// Check whether the specified user can view the given script + /// + /// + /// + /// + /// + /// + private bool CanViewScript(UUID script, UUID objectID, UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (objectID == UUID.Zero) // User inventory + { + CachedUserInfo userInfo = + scene.CommsManager.UserProfileCacheService.GetUserDetails(user); + + if (userInfo == null) + { + m_log.ErrorFormat("[PERMISSIONS]: Could not find user {0} for administrator check", user); + return false; + } + + if (userInfo.RootFolder == null) + return false; + + InventoryItemBase assetRequestItem = userInfo.RootFolder.FindItem(script); + if (assetRequestItem == null) // Library item + { + assetRequestItem = m_scene.CommsManager.UserProfileCacheService.LibraryRoot.FindItem(script); + + if (assetRequestItem != null) // Implicitly readable + return true; + } + + // SL is rather harebrained here. In SL, a script you + // have mod/copy no trans is readable. This subverts + // permissions, but is used in some products, most + // notably Hippo door plugin and HippoRent 5 networked + // prim counter. + // To enable this broken SL-ism, remove Transfer from + // the below expressions. + // Trying to improve on SL perms by making a script + // readable only if it's really full perms + // + if ((assetRequestItem.CurrentPermissions & + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy | + (uint)PermissionMask.Transfer)) != + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy | + (uint)PermissionMask.Transfer)) + return false; + } + else // Prim inventory + { + SceneObjectPart part = scene.GetSceneObjectPart(objectID); + + if (part == null) + return false; + + if (part.OwnerID != user) + return false; + + if ((part.OwnerMask & (uint)PermissionMask.Modify) == 0) + return false; + + TaskInventoryItem ti = part.Inventory.GetInventoryItem(script); + + if (ti == null) + return false; + + if (ti.OwnerID != user) + return false; + + // Require full perms + if ((ti.CurrentPermissions & + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy | + (uint)PermissionMask.Transfer)) != + ((uint)PermissionMask.Modify | + (uint)PermissionMask.Copy | + (uint)PermissionMask.Transfer)) + return false; + } + + return true; + } + + /// + /// Check whether the specified user can view the given notecard + /// + /// + /// + /// + /// + /// + private bool CanViewNotecard(UUID notecard, UUID objectID, UUID user, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if (objectID == UUID.Zero) // User inventory + { + CachedUserInfo userInfo = + scene.CommsManager.UserProfileCacheService.GetUserDetails(user); + + if (userInfo == null) + { + m_log.ErrorFormat("[PERMISSIONS]: Could not find user {0} for view notecard check", user); + return false; + } + + if (userInfo.RootFolder == null) + return false; + + InventoryItemBase assetRequestItem = userInfo.RootFolder.FindItem(notecard); + if (assetRequestItem == null) // Library item + { + assetRequestItem = m_scene.CommsManager.UserProfileCacheService.LibraryRoot.FindItem(notecard); + + if (assetRequestItem != null) // Implicitly readable + return true; + } + + // Notecards are always readable unless no copy + // + if ((assetRequestItem.CurrentPermissions & + (uint)PermissionMask.Copy) != + (uint)PermissionMask.Copy) + return false; + } + else // Prim inventory + { + SceneObjectPart part = scene.GetSceneObjectPart(objectID); + + if (part == null) + return false; + + if (part.OwnerID != user) + return false; + + if ((part.OwnerMask & (uint)PermissionMask.Modify) == 0) + return false; + + TaskInventoryItem ti = part.Inventory.GetInventoryItem(notecard); + + if (ti == null) + return false; + + if (ti.OwnerID != user) + return false; + + // Notecards are always readable unless no copy + // + if ((ti.CurrentPermissions & + (uint)PermissionMask.Copy) != + (uint)PermissionMask.Copy) + return false; + } + + return true; + } + + #endregion + + private bool CanLinkObject(UUID userID, UUID objectID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanDelinkObject(UUID userID, UUID objectID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanBuyLand(UUID userID, ILandObject parcel, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanCopyObjectInventory(UUID itemID, UUID objectID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanDeleteObjectInventory(UUID itemID, UUID objectID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + /// + /// Check whether the specified user is allowed to directly create the given inventory type in a prim's + /// inventory (e.g. the New Script button in the 1.21 Linden Lab client). + /// + /// + /// + /// + /// + private bool CanCreateObjectInventory(int invType, UUID objectID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if ((int)InventoryType.LSL == invType) + if (m_allowedScriptCreators == UserSet.Administrators && !IsAdministrator(userID)) + return false; + + return true; + } + + /// + /// Check whether the specified user is allowed to create the given inventory type in their inventory. + /// + /// + /// + /// + private bool CanCreateUserInventory(int invType, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + if ((int)InventoryType.LSL == invType) + if (m_allowedScriptCreators == UserSet.Administrators && !IsAdministrator(userID)) + return false; + + return true; + } + + /// + /// Check whether the specified user is allowed to copy the given inventory type in their inventory. + /// + /// + /// + /// + private bool CanCopyUserInventory(UUID itemID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + /// + /// Check whether the specified user is allowed to edit the given inventory item within their own inventory. + /// + /// + /// + /// + private bool CanEditUserInventory(UUID itemID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + /// + /// Check whether the specified user is allowed to delete the given inventory item from their own inventory. + /// + /// + /// + /// + private bool CanDeleteUserInventory(UUID itemID, UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanTeleport(UUID userID) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + return true; + } + + private bool CanResetScript(UUID prim, UUID script, UUID agentID, Scene scene) + { + DebugPermissionInformation(MethodInfo.GetCurrentMethod().Name); + if (m_bypassPermissions) return m_bypassPermissionsValue; + + SceneObjectPart part = m_scene.GetSceneObjectPart(prim); + + // If we selected a sub-prim to reset, prim won't represent the object, but only a part. + // We have to check the permissions of the object, though. + if (part.ParentID != 0) prim = part.ParentUUID; + + // You can reset the scripts in any object you can edit + return GenericObjectPermission(agentID, prim, false); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Serialiser/IFileSerialiser.cs b/OpenSim/Region/CoreModules/World/Serialiser/IFileSerialiser.cs new file mode 100644 index 0000000..acc7bb8 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Serialiser/IFileSerialiser.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Serialiser +{ + internal interface IFileSerialiser + { + string WriteToFile(Scene scene, string dir); + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs new file mode 100644 index 0000000..ed6448f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseObjects.cs @@ -0,0 +1,125 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Xml; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Serialiser +{ + internal class SerialiseObjects : IFileSerialiser + { + #region IFileSerialiser Members + + public string WriteToFile(Scene scene, string dir) + { + string targetFileName = dir + "objects.xml"; + + SaveSerialisedToFile(targetFileName, scene); + + return "objects.xml"; + } + + #endregion + + public void SaveSerialisedToFile(string fileName, Scene scene) + { + string xmlstream = GetObjectXml(scene); + + MemoryStream stream = ReformatXmlString(xmlstream); + + stream.Seek(0, SeekOrigin.Begin); + CreateXmlFile(stream, fileName); + + stream.Seek(0, SeekOrigin.Begin); + CreateCompressedXmlFile(stream, fileName); + } + + private static MemoryStream ReformatXmlString(string xmlstream) + { + MemoryStream stream = new MemoryStream(); + XmlTextWriter formatter = new XmlTextWriter(stream, Encoding.UTF8); + XmlDocument doc = new XmlDocument(); + + doc.LoadXml(xmlstream); + formatter.Formatting = Formatting.Indented; + doc.WriteContentTo(formatter); + formatter.Flush(); + return stream; + } + + private static string GetObjectXml(Scene scene) + { + string xmlstream = ""; + + List EntityList = scene.GetEntities(); + List EntityXml = new List(); + + foreach (EntityBase ent in EntityList) + { + if (ent is SceneObjectGroup) + { + EntityXml.Add(((SceneObjectGroup) ent).ToXmlString2()); + } + } + EntityXml.Sort(); + + foreach (string xml in EntityXml) + xmlstream += xml; + + xmlstream += ""; + return xmlstream; + } + + private static void CreateXmlFile(MemoryStream xmlStream, string fileName) + { + FileStream objectsFile = new FileStream(fileName, FileMode.Create); + + xmlStream.WriteTo(objectsFile); + objectsFile.Flush(); + objectsFile.Close(); + } + + private static void CreateCompressedXmlFile(MemoryStream xmlStream, string fileName) + { + #region GZip Compressed Version + + FileStream objectsFileCompressed = new FileStream(fileName + ".gzs", FileMode.Create); + MemoryStream gzipMSStream = new MemoryStream(); + GZipStream gzipStream = new GZipStream(gzipMSStream, CompressionMode.Compress); + xmlStream.WriteTo(gzipStream); + gzipMSStream.WriteTo(objectsFileCompressed); + objectsFileCompressed.Flush(); + objectsFileCompressed.Close(); + + #endregion + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Serialiser/SerialiseTerrain.cs b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseTerrain.cs new file mode 100644 index 0000000..924218a --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Serialiser/SerialiseTerrain.cs @@ -0,0 +1,53 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.CoreModules.World.Terrain.FileLoaders; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Serialiser +{ + internal class SerialiseTerrain : IFileSerialiser + { + #region IFileSerialiser Members + + public string WriteToFile(Scene scene, string dir) + { + ITerrainLoader fileSystemExporter = new RAW32(); + string targetFileName = dir + "heightmap.r32"; + + lock (scene.Heightmap) + { + fileSystemExporter.SaveFile(targetFileName, scene.Heightmap); + } + + return "heightmap.r32"; + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Serialiser/SerialiserModule.cs b/OpenSim/Region/CoreModules/World/Serialiser/SerialiserModule.cs new file mode 100644 index 0000000..7080d5f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Serialiser/SerialiserModule.cs @@ -0,0 +1,226 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; + +namespace OpenSim.Region.CoreModules.World.Serialiser +{ + public class SerialiserModule : IRegionModule, IRegionSerialiserModule + { + private Commander m_commander = new Commander("export"); + private List m_regions = new List(); + private string m_savedir = "exports" + "/"; + private List m_serialisers = new List(); + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource source) + { + scene.RegisterModuleCommander(m_commander); + scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + scene.RegisterModuleInterface(this); + + lock (m_regions) + { + m_regions.Add(scene); + } + } + + public void PostInitialise() + { + lock (m_serialisers) + { + m_serialisers.Add(new SerialiseTerrain()); + m_serialisers.Add(new SerialiseObjects()); + } + + LoadCommanderCommands(); + } + + public void Close() + { + m_regions.Clear(); + } + + public string Name + { + get { return "ExportSerialisationModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + #region IRegionSerialiser Members + + public void LoadPrimsFromXml(Scene scene, string fileName, bool newIDS, Vector3 loadOffset) + { + SceneXmlLoader.LoadPrimsFromXml(scene, fileName, newIDS, loadOffset); + } + + public void SavePrimsToXml(Scene scene, string fileName) + { + SceneXmlLoader.SavePrimsToXml(scene, fileName); + } + + public void LoadPrimsFromXml2(Scene scene, string fileName) + { + SceneXmlLoader.LoadPrimsFromXml2(scene, fileName); + } + + public void LoadPrimsFromXml2(Scene scene, TextReader reader, bool startScripts) + { + SceneXmlLoader.LoadPrimsFromXml2(scene, reader, startScripts); + } + + public void SavePrimsToXml2(Scene scene, string fileName) + { + SceneXmlLoader.SavePrimsToXml2(scene, fileName); + } + + public void SavePrimsToXml2(Scene scene, TextWriter stream, Vector3 min, Vector3 max) + { + SceneXmlLoader.SavePrimsToXml2(scene, stream, min, max); + } + + public void SaveNamedPrimsToXml2(Scene scene, string primName, string fileName) + { + SceneXmlLoader.SaveNamedPrimsToXml2(scene, primName, fileName); + } + + public SceneObjectGroup DeserializeGroupFromXml2(string xmlString) + { + return SceneXmlLoader.DeserializeGroupFromXml2(xmlString); + } + + public string SaveGroupToXml2(SceneObjectGroup grp) + { + return SceneXmlLoader.SaveGroupToXml2(grp); + } + + public void SavePrimListToXml2(List entityList, string fileName) + { + SceneXmlLoader.SavePrimListToXml2(entityList, fileName); + } + + public void SavePrimListToXml2(List entityList, TextWriter stream, Vector3 min, Vector3 max) + { + SceneXmlLoader.SavePrimListToXml2(entityList, stream, min, max); + } + + public List SerialiseRegion(Scene scene, string saveDir) + { + List results = new List(); + + if (!Directory.Exists(saveDir)) + { + Directory.CreateDirectory(saveDir); + } + + lock (m_serialisers) + { + foreach (IFileSerialiser serialiser in m_serialisers) + { + results.Add(serialiser.WriteToFile(scene, saveDir)); + } + } + + TextWriter regionInfoWriter = new StreamWriter(saveDir + "README.TXT"); + regionInfoWriter.WriteLine("Region Name: " + scene.RegionInfo.RegionName); + regionInfoWriter.WriteLine("Region ID: " + scene.RegionInfo.RegionID.ToString()); + regionInfoWriter.WriteLine("Backup Time: UTC " + DateTime.UtcNow.ToString()); + regionInfoWriter.WriteLine("Serialise Version: 0.1"); + regionInfoWriter.Close(); + + TextWriter manifestWriter = new StreamWriter(saveDir + "region.manifest"); + foreach (string line in results) + { + manifestWriter.WriteLine(line); + } + manifestWriter.Close(); + + return results; + } + + #endregion + + private void EventManager_OnPluginConsole(string[] args) + { + if (args[0] == "export") + { + string[] tmpArgs = new string[args.Length - 2]; + int i = 0; + for (i = 2; i < args.Length; i++) + tmpArgs[i - 2] = args[i]; + + m_commander.ProcessConsoleCommand(args[1], tmpArgs); + } + } + + private void InterfaceSaveRegion(Object[] args) + { + foreach (Scene region in m_regions) + { + if (region.RegionInfo.RegionName == (string) args[0]) + { + // List results = SerialiseRegion(region, m_savedir + region.RegionInfo.RegionID.ToString() + "/"); + SerialiseRegion(region, m_savedir + region.RegionInfo.RegionID.ToString() + "/"); + } + } + } + + private void InterfaceSaveAllRegions(Object[] args) + { + foreach (Scene region in m_regions) + { + // List results = SerialiseRegion(region, m_savedir + region.RegionInfo.RegionID.ToString() + "/"); + SerialiseRegion(region, m_savedir + region.RegionInfo.RegionID.ToString() + "/"); + } + } + + private void LoadCommanderCommands() + { + Command serialiseSceneCommand = new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveRegion, "Saves the named region into the exports directory."); + serialiseSceneCommand.AddArgument("region-name", "The name of the region you wish to export", "String"); + + Command serialiseAllScenesCommand = new Command("save-all",CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveAllRegions, "Saves all regions into the exports directory."); + + m_commander.RegisterCommand("save", serialiseSceneCommand); + m_commander.RegisterCommand("save-all", serialiseAllScenesCommand); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs b/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs new file mode 100644 index 0000000..ffd6b8d --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Sound/SoundModule.cs @@ -0,0 +1,97 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Reflection; +using Nini.Config; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Sound +{ + public class SoundModule : IRegionModule, ISoundModule + { + //private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + + m_scene.EventManager.OnNewClient += OnNewClient; + + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() {} + public void Close() {} + public string Name { get { return "Sound Module"; } } + public bool IsSharedModule { get { return false; } } + + private void OnNewClient(IClientAPI client) + { + client.OnSoundTrigger += TriggerSound; + } + + public virtual void PlayAttachedSound( + UUID soundID, UUID ownerID, UUID objectID, double gain, Vector3 position, byte flags) + { + foreach (ScenePresence p in m_scene.GetAvatars()) + { + double dis = Util.GetDistanceTo(p.AbsolutePosition, position); + if (dis > 100.0) // Max audio distance + continue; + + // Scale by distance + gain = (float)((double)gain*((100.0 - dis) / 100.0)); + + p.ControllingClient.SendPlayAttachedSound(soundID, objectID, ownerID, (float)gain, flags); + } + } + + public virtual void TriggerSound( + UUID soundId, UUID ownerID, UUID objectID, UUID parentID, double gain, Vector3 position, UInt64 handle) + { + foreach (ScenePresence p in m_scene.GetAvatars()) + { + double dis = Util.GetDistanceTo(p.AbsolutePosition, position); + if (dis > 100.0) // Max audio distance + continue; + + // Scale by distance + gain = (float)((double)gain*((100.0 - dis) / 100.0)); + + p.ControllingClient.SendTriggeredSound( + soundId, ownerID, objectID, parentID, handle, position, (float)gain); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Sun/SunModule.cs b/OpenSim/Region/CoreModules/World/Sun/SunModule.cs new file mode 100644 index 0000000..b36684c --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Sun/SunModule.cs @@ -0,0 +1,434 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules +{ + public class SunModule : IRegionModule + { + + private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private const double SeasonalTilt = 0.03 * Math.PI; // A daily shift of approximately 1.7188 degrees + private const double AverageTilt = -0.25 * Math.PI; // A 45 degree tilt + private const double SunCycle = 2.0D * Math.PI; // A perfect circle measured in radians + private const double SeasonalCycle = 2.0D * Math.PI; // Ditto + + // + // Per Region Values + // + + private bool ready = false; + + // Configurable values + private string m_mode = "SL"; + private int m_frame_mod = 0; + private double m_day_length = 0; + private int m_year_length = 0; + private double m_day_night = 0; + // private double m_longitude = 0; + // private double m_latitude = 0; + // Configurable defaults Defaults close to SL + private string d_mode = "SL"; + private int d_frame_mod = 100; // Every 10 seconds (actually less) + private double d_day_length = 4; // A VW day is 4 RW hours long + private int d_year_length = 60; // There are 60 VW days in a VW year + private double d_day_night = 0.45; // axis offset: ratio of light-to-dark, approx 1:3 + // private double d_longitude = -73.53; + // private double d_latitude = 41.29; + + // Frame counter + private uint m_frame = 0; + + // Cached Scene reference + private Scene m_scene = null; + + // Calculated Once in the lifetime of a region + private long TicksToEpoch; // Elapsed time for 1/1/1970 + private uint SecondsPerSunCycle; // Length of a virtual day in RW seconds + private uint SecondsPerYear; // Length of a virtual year in RW seconds + private double SunSpeed; // Rate of passage in radians/second + private double SeasonSpeed; // Rate of change for seasonal effects + // private double HoursToRadians; // Rate of change for seasonal effects + private long TicksOffset = 0; // seconds offset from UTC + // Calculated every update + private float OrbitalPosition; // Orbital placement at a point in time + private double HorizonShift; // Axis offset to skew day and night + private double TotalDistanceTravelled; // Distance since beginning of time (in radians) + private double SeasonalOffset; // Seaonal variation of tilt + private float Magnitude; // Normal tilt + // private double VWTimeRatio; // VW time as a ratio of real time + + // Working values + private Vector3 Position = Vector3.Zero; + private Vector3 Velocity = Vector3.Zero; + private Quaternion Tilt = new Quaternion(1.0f, 0.0f, 0.0f, 0.0f); + + private long LindenHourOffset = 0; + private bool sunFixed = false; + + private Dictionary m_rootAgents = new Dictionary(); + + // Current time in elapsed seconds since Jan 1st 1970 + private ulong CurrentTime + { + get { + return (ulong)(((System.DateTime.Now.Ticks) - TicksToEpoch + TicksOffset + LindenHourOffset)/10000000); + } + } + + private float GetLindenEstateHourFromCurrentTime() + { + float ticksleftover = ((float)CurrentTime) % ((float)SecondsPerSunCycle); + + float hour = (24 * (ticksleftover / SecondsPerSunCycle)) + 6; + + return hour; + } + + private void SetTimeByLindenHour(float LindenHour) + { + // Linden hour is 24 hours with a 6 hour offset. 6-30 + + if (LindenHour - 6 == 0) + { + LindenHourOffset = 0; + return; + } + + // Remove LindenHourOffset to calculate it from LocalTime + float ticksleftover = ((float)(((long)(CurrentTime * 10000000) - (long)LindenHourOffset)/ 10000000) % ((float)SecondsPerSunCycle)); + float hour = (24 * (ticksleftover / SecondsPerSunCycle)); + + float offsethours = 0; + + if (LindenHour - 6 > hour) + { + offsethours = hour + ((LindenHour-6) - hour); + } + else + { + offsethours = hour - (hour - (LindenHour - 6)); + } + //m_log.Debug("[OFFSET]: " + hour + " - " + LindenHour + " - " + offsethours.ToString()); + + LindenHourOffset = (long)((float)offsethours * (36000000000/m_day_length)); + m_log.Debug("[SUN]: Directive from the Estate Tools to set the sun phase to LindenHour " + GetLindenEstateHourFromCurrentTime().ToString()); + } + + // Called immediately after the module is loaded for a given region + // i.e. Immediately after instance creation. + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + + m_frame = 0; + + TimeZone local = TimeZone.CurrentTimeZone; + TicksOffset = local.GetUtcOffset(local.ToLocalTime(DateTime.Now)).Ticks; + m_log.Debug("[SUN]: localtime offset is " + TicksOffset); + + // Align ticks with Second Life + + TicksToEpoch = new System.DateTime(1970,1,1).Ticks; + + // Just in case they don't have the stanzas + try + { + // Mode: determines how the sun is handled + m_mode = config.Configs["Sun"].GetString("mode", d_mode); + // Mode: determines how the sun is handled + // m_latitude = config.Configs["Sun"].GetDouble("latitude", d_latitude); + // Mode: determines how the sun is handled + // m_longitude = config.Configs["Sun"].GetDouble("longitude", d_longitude); + // Year length in days + m_year_length = config.Configs["Sun"].GetInt("year_length", d_year_length); + // Day length in decimal hours + m_day_length = config.Configs["Sun"].GetDouble("day_length", d_day_length); + // Day to Night Ratio + m_day_night = config.Configs["Sun"].GetDouble("day_night_offset", d_day_night); + // Update frequency in frames + m_frame_mod = config.Configs["Sun"].GetInt("update_interval", d_frame_mod); + } + catch (Exception e) + { + m_log.Debug("[SUN]: Configuration access failed, using defaults. Reason: "+e.Message); + m_mode = d_mode; + m_year_length = d_year_length; + m_day_length = d_day_length; + m_day_night = d_day_night; + m_frame_mod = d_frame_mod; + // m_latitude = d_latitude; + // m_longitude = d_longitude; + } + + switch (m_mode) + { + case "T1": + default: + case "SL": + // Time taken to complete a cycle (day and season) + + SecondsPerSunCycle = (uint) (m_day_length * 60 * 60); + SecondsPerYear = (uint) (SecondsPerSunCycle*m_year_length); + + // Ration of real-to-virtual time + + // VWTimeRatio = 24/m_day_length; + + // Speed of rotation needed to complete a cycle in the + // designated period (day and season) + + SunSpeed = SunCycle/SecondsPerSunCycle; + SeasonSpeed = SeasonalCycle/SecondsPerYear; + + // Horizon translation + + HorizonShift = m_day_night; // Z axis translation + // HoursToRadians = (SunCycle/24)*VWTimeRatio; + + // Insert our event handling hooks + + scene.EventManager.OnFrame += SunUpdate; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.EventManager.OnEstateToolsTimeUpdate += EstateToolsTimeUpdate; + scene.EventManager.OnGetSunLindenHour += GetLindenEstateHourFromCurrentTime; + + ready = true; + + m_log.Debug("[SUN]: Mode is "+m_mode); + m_log.Debug("[SUN]: Initialization completed. Day is "+SecondsPerSunCycle+" seconds, and year is "+m_year_length+" days"); + m_log.Debug("[SUN]: Axis offset is "+m_day_night); + m_log.Debug("[SUN]: Positional data updated every "+m_frame_mod+" frames"); + + break; + } + } + + public void PostInitialise() + { + } + + public void Close() + { + ready = false; + + // Remove our hooks + m_scene.EventManager.OnFrame -= SunUpdate; + m_scene.EventManager.OnMakeChildAgent -= MakeChildAgent; + m_scene.EventManager.OnAvatarEnteringNewParcel -= AvatarEnteringParcel; + m_scene.EventManager.OnClientClosed -= ClientLoggedOut; + m_scene.EventManager.OnEstateToolsTimeUpdate -= EstateToolsTimeUpdate; + m_scene.EventManager.OnGetSunLindenHour -= GetLindenEstateHourFromCurrentTime; + } + + public string Name + { + get { return "SunModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public void SunToClient(IClientAPI client) + { + if (m_mode != "T1") + { + if (ready) + { + if (!sunFixed) + GenSunPos(); // Generate shared values once + client.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); + } + } + } + + public void SunUpdate() + { + if (((m_frame++%m_frame_mod) != 0) || !ready || sunFixed) + { + return; + } + + GenSunPos(); // Generate shared values once + + List avatars = m_scene.GetAvatars(); + foreach (ScenePresence avatar in avatars) + { + if (!avatar.IsChildAgent) + avatar.ControllingClient.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); + } + + // set estate settings for region access to sun position + m_scene.RegionInfo.RegionSettings.SunVector = Position; + //m_scene.RegionInfo.EstateSettings.sunHour = GetLindenEstateHourFromCurrentTime(); + } + + public void ForceSunUpdateToAllClients() + { + GenSunPos(); // Generate shared values once + + List avatars = m_scene.GetAvatars(); + foreach (ScenePresence avatar in avatars) + { + if (!avatar.IsChildAgent) + avatar.ControllingClient.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition); + } + + // set estate settings for region access to sun position + m_scene.RegionInfo.RegionSettings.SunVector = Position; + m_scene.RegionInfo.RegionSettings.SunPosition = GetLindenEstateHourFromCurrentTime(); + } + + /// + /// Calculate the sun's orbital position and its velocity. + /// + private void GenSunPos() + { + TotalDistanceTravelled = SunSpeed * CurrentTime; // distance measured in radians + OrbitalPosition = (float) (TotalDistanceTravelled%SunCycle); // position measured in radians + + // TotalDistanceTravelled += HoursToRadians-(0.25*Math.PI)*Math.Cos(HoursToRadians)-OrbitalPosition; + // OrbitalPosition = (float) (TotalDistanceTravelled%SunCycle); + + SeasonalOffset = SeasonSpeed * CurrentTime; // Present season determined as total radians travelled around season cycle + + Tilt.W = (float) (AverageTilt + (SeasonalTilt*Math.Sin(SeasonalOffset))); // Calculate seasonal orbital N/S tilt + + // m_log.Debug("[SUN] Total distance travelled = "+TotalDistanceTravelled+", present position = "+OrbitalPosition+"."); + // m_log.Debug("[SUN] Total seasonal progress = "+SeasonalOffset+", present tilt = "+Tilt.W+"."); + + // The sun rotates about the Z axis + + Position.X = (float) Math.Cos(-TotalDistanceTravelled); + Position.Y = (float) Math.Sin(-TotalDistanceTravelled); + Position.Z = 0; + + // For interest we rotate it slightly about the X access. + // Celestial tilt is a value that ranges .025 + + Position *= Tilt; + + // Finally we shift the axis so that more of the + // circle is above the horizon than below. This + // makes the nights shorter than the days. + + Position.Z = Position.Z + (float) HorizonShift; + Position = Vector3.Normalize(Position); + + // m_log.Debug("[SUN] Position("+Position.X+","+Position.Y+","+Position.Z+")"); + + Velocity.X = 0; + Velocity.Y = 0; + Velocity.Z = (float) SunSpeed; + + // Correct angular velocity to reflect the seasonal rotation + + Magnitude = Position.Length(); + if (sunFixed) + { + Velocity.X = 0; + Velocity.Y = 0; + Velocity.Z = 0; + return; + } + + Velocity = (Velocity * Tilt) * (1.0f / Magnitude); + + // m_log.Debug("[SUN] Velocity("+Velocity.X+","+Velocity.Y+","+Velocity.Z+")"); + } + + private void ClientLoggedOut(UUID AgentId) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + } + } + } + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + m_rootAgents[avatar.UUID] = avatar.RegionHandle; + } + else + { + m_rootAgents.Add(avatar.UUID, avatar.RegionHandle); + SunToClient(avatar.ControllingClient); + } + } + //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); + } + + private void MakeChildAgent(ScenePresence avatar) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) + { + m_rootAgents.Remove(avatar.UUID); + } + } + } + } + + public void EstateToolsTimeUpdate(ulong regionHandle, bool FixedTime, bool useEstateTime, float LindenHour) + { + if (m_scene.RegionInfo.RegionHandle == regionHandle) + { + SetTimeByLindenHour(LindenHour); + + //if (useEstateTime) + //LindenHourOffset = 0; + + ForceSunUpdateToAllClients(); + sunFixed = FixedTime; + if (sunFixed) + GenSunPos(); + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/DefaultEffects/ChannelDigger.cs b/OpenSim/Region/CoreModules/World/Terrain/DefaultEffects/ChannelDigger.cs new file mode 100644 index 0000000..f96ab88 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/DefaultEffects/ChannelDigger.cs @@ -0,0 +1,107 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes; + +namespace OpenSim.Region.Modules.Terrain.Extensions.DefaultEffects.Effects +{ + public class ChannelDigger : ITerrainEffect + { + private readonly int num_h = 4; + private readonly int num_w = 4; + + private readonly ITerrainFloodEffect raiseFunction = new RaiseArea(); + private readonly ITerrainFloodEffect smoothFunction = new SmoothArea(); + + #region ITerrainEffect Members + + public void RunEffect(ITerrainChannel map) + { + FillMap(map, 15); + BuildTiles(map, 7); + SmoothMap(map, 3); + } + + #endregion + + private void SmoothMap(ITerrainChannel map, int rounds) + { + Boolean[,] bitmap = new bool[map.Width,map.Height]; + for (int x = 0; x < map.Width; x++) + { + for (int y = 0; y < map.Height; y++) + { + bitmap[x, y] = true; + } + } + + for (int i = 0; i < rounds; i++) + { + smoothFunction.FloodEffect(map, bitmap, 1.0); + } + } + + private void FillMap(ITerrainChannel map, double val) + { + for (int x = 0; x < map.Width; x++) + for (int y = 0; y < map.Height; y++) + map[x, y] = val; + } + + private void BuildTiles(ITerrainChannel map, double height) + { + int channelWidth = (int) Math.Floor((map.Width / num_w) * 0.8); + int channelHeight = (int) Math.Floor((map.Height / num_h) * 0.8); + int channelXOffset = (map.Width / num_w) - channelWidth; + int channelYOffset = (map.Height / num_h) - channelHeight; + + for (int x = 0; x < num_w; x++) + { + for (int y = 0; y < num_h; y++) + { + int xoff = ((channelXOffset + channelWidth) * x) + (channelXOffset / 2); + int yoff = ((channelYOffset + channelHeight) * y) + (channelYOffset / 2); + + Boolean[,] bitmap = new bool[map.Width,map.Height]; + + for (int dx = 0; dx < channelWidth; dx++) + { + for (int dy = 0; dy < channelHeight; dy++) + { + bitmap[dx + xoff, dy + yoff] = true; + } + } + + raiseFunction.FloodEffect(map, bitmap, height); + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/Effects/CookieCutter.cs b/OpenSim/Region/CoreModules/World/Terrain/Effects/CookieCutter.cs new file mode 100644 index 0000000..cb8112c --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Effects/CookieCutter.cs @@ -0,0 +1,125 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; + +namespace OpenSim.Region.CoreModules.World.Terrain.Effects +{ + internal class CookieCutter : ITerrainEffect + { + #region ITerrainEffect Members + + public void RunEffect(ITerrainChannel map) + { + ITerrainPaintableEffect eroder = new WeatherSphere(); + + bool[,] cliffMask = new bool[map.Width,map.Height]; + bool[,] channelMask = new bool[map.Width,map.Height]; + bool[,] smoothMask = new bool[map.Width,map.Height]; + bool[,] allowMask = new bool[map.Width,map.Height]; + + Console.WriteLine("S1"); + + // Step one, generate rough mask + int x, y; + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + Console.Write("."); + smoothMask[x, y] = true; + allowMask[x,y] = true; + + // Start underwater + map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 3, 0.25) * 5; + // Add a little height. (terrain should now be above water, mostly.) + map[x, y] += 20; + + const int channelsX = 4; + int channelWidth = (map.Width / channelsX / 4); + const int channelsY = 4; + int channelHeight = (map.Height / channelsY / 4); + + SetLowerChannel(map, cliffMask, channelMask, x, y, channelsX, channelWidth, map.Width, x); + SetLowerChannel(map, cliffMask, channelMask, x, y, channelsY, channelHeight, map.Height, y); + } + } + + Console.WriteLine("S2"); + //smooth.FloodEffect(map, smoothMask, 4.0); + + Console.WriteLine("S3"); + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (cliffMask[x, y]) + eroder.PaintEffect(map, allowMask, x, y, -1, 4, 0.1); + } + } + + for (x = 0; x < map.Width; x += 2) + { + for (y = 0; y < map.Height; y += 2) + { + if (map[x, y] < 0.1) + map[x, y] = 0.1; + if (map[x, y] > 256) + map[x, y] = 256; + } + } + //smooth.FloodEffect(map, smoothMask, 4.0); + } + + #endregion + + private static void SetLowerChannel(ITerrainChannel map, bool[,] cliffMask, bool[,] channelMask, int x, int y, int numChannels, int channelWidth, + int mapSize, int rp) + { + for (int i = 0; i < numChannels; i++) + { + double distanceToLine = Math.Abs(rp - ((mapSize / numChannels) * i)); + + if (distanceToLine < channelWidth) + { + if (channelMask[x, y]) + return; + + // Remove channels + map[x, y] -= 10; + channelMask[x, y] = true; + } + if (distanceToLine < 1) + { + cliffMask[x, y] = true; + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs b/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs new file mode 100644 index 0000000..da6ee12 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Effects/DefaultTerrainGenerator.cs @@ -0,0 +1,56 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.Effects +{ + internal class DefaultTerrainGenerator : ITerrainEffect + { + #region ITerrainEffect Members + + public void RunEffect(ITerrainChannel map) + { + int x, y; + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 3, 0.25) * 10; + double spherFac = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2, Constants.RegionSize / 2, 50) * 0.01; + if (map[x, y] < spherFac) + { + map[x, y] = spherFac; + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs new file mode 100644 index 0000000..4f395b5 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs @@ -0,0 +1,76 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + /// + /// A generic windows bitmap loader. + /// Should be capable of handling 24-bit RGB images. + /// + /// Uses the System.Drawing filesystem loader. + /// + internal class BMP : GenericSystemDrawing + { + /// + /// Exports a file to a image on the disk using a System.Drawing exporter. + /// + /// The target filename + /// The terrain channel being saved + public override void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Bmp); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public override void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Png); + } + + /// + /// The human readable version of the file format(s) this loader handles + /// + /// + public override string ToString() + { + return "BMP"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs new file mode 100644 index 0000000..cff82d1 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + internal class GIF : GenericSystemDrawing + { + public override void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Gif); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public override void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Gif); + } + + public override string ToString() + { + return "GIF"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs new file mode 100644 index 0000000..477c73c --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + /// + /// A virtual class designed to have methods overloaded, + /// this class provides an interface for a generic image + /// saving and loading mechanism, but does not specify the + /// format. It should not be insubstantiated directly. + /// + public class GenericSystemDrawing : ITerrainLoader + { + #region ITerrainLoader Members + + public string FileExtension + { + get { return ".gsd"; } + } + + /// + /// Loads a file from a specified filename on the disk, + /// parses the image using the System.Drawing parsers + /// then returns a terrain channel. Values are + /// returned based on HSL brightness between 0m and 128m + /// + /// The target image to load + /// A terrain channel generated from the image. + public virtual ITerrainChannel LoadFile(string filename) + { + return LoadBitmap(new Bitmap(filename)); + } + + public ITerrainChannel LoadFile(string filename, int x, int y, int fileWidth, int fileHeight, int w, int h) + { + throw new NotImplementedException(); + } + + public virtual ITerrainChannel LoadStream(Stream stream) + { + return LoadBitmap(new Bitmap(stream)); + } + + protected virtual ITerrainChannel LoadBitmap(Bitmap bitmap) + { + ITerrainChannel retval = new TerrainChannel(bitmap.Width, bitmap.Height); + + int x; + for (x = 0; x < bitmap.Width; x++) + { + int y; + for (y = 0; y < bitmap.Height; y++) + { + retval[x, y] = bitmap.GetPixel(x, bitmap.Height - y - 1).GetBrightness() * 128; + } + } + + return retval; + } + + /// + /// Exports a file to a image on the disk using a System.Drawing exporter. + /// + /// The target filename + /// The terrain channel being saved + public virtual void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Png); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public virtual void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Png); + } + + #endregion + + public override string ToString() + { + return "SYS.DRAWING"; + } + + /// + /// Protected method, generates a grayscale bitmap + /// image from a specified terrain channel. + /// + /// The terrain channel to export to bitmap + /// A System.Drawing.Bitmap containing a grayscale image + protected static Bitmap CreateGrayscaleBitmapFromMap(ITerrainChannel map) + { + Bitmap bmp = new Bitmap(map.Width, map.Height); + + const int pallete = 256; + + Color[] grays = new Color[pallete]; + for (int i = 0; i < grays.Length; i++) + { + grays[i] = Color.FromArgb(i, i, i); + } + + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + // 512 is the largest possible height before colours clamp + int colorindex = (int) (Math.Max(Math.Min(1.0, map[x, y] / 128.0), 0.0) * (pallete - 1)); + + // Handle error conditions + if (colorindex > pallete - 1 || colorindex < 0) + bmp.SetPixel(x, map.Height - y - 1, Color.Red); + else + bmp.SetPixel(x, map.Height - y - 1, grays[colorindex]); + } + } + return bmp; + } + + /// + /// Protected method, generates a coloured bitmap + /// image from a specified terrain channel. + /// + /// The terrain channel to export to bitmap + /// A System.Drawing.Bitmap containing a coloured image + protected static Bitmap CreateBitmapFromMap(ITerrainChannel map) + { + Bitmap gradientmapLd = new Bitmap("defaultstripe.png"); + + int pallete = gradientmapLd.Height; + + Bitmap bmp = new Bitmap(map.Width, map.Height); + Color[] colours = new Color[pallete]; + + for (int i = 0; i < pallete; i++) + { + colours[i] = gradientmapLd.GetPixel(0, i); + } + + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + // 512 is the largest possible height before colours clamp + int colorindex = (int) (Math.Max(Math.Min(1.0, map[x, y] / 512.0), 0.0) * (pallete - 1)); + + // Handle error conditions + if (colorindex > pallete - 1 || colorindex < 0) + bmp.SetPixel(x, map.Height - y - 1, Color.Red); + else + bmp.SetPixel(x, map.Height - y - 1, colours[colorindex]); + } + } + return bmp; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs new file mode 100644 index 0000000..f8e31f8 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs @@ -0,0 +1,112 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + public class JPEG : ITerrainLoader + { + #region ITerrainLoader Members + + public string FileExtension + { + get { return ".jpg"; } + } + + public ITerrainChannel LoadFile(string filename) + { + throw new NotImplementedException(); + } + + public ITerrainChannel LoadFile(string filename, int x, int y, int fileWidth, int fileHeight, int w, int h) + { + throw new NotImplementedException(); + } + + public ITerrainChannel LoadStream(Stream stream) + { + throw new NotImplementedException(); + } + + public void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Jpeg); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Jpeg); + } + + #endregion + + public override string ToString() + { + return "JPEG"; + } + + private static Bitmap CreateBitmapFromMap(ITerrainChannel map) + { + Bitmap gradientmapLd = new Bitmap("defaultstripe.png"); + + int pallete = gradientmapLd.Height; + + Bitmap bmp = new Bitmap(map.Width, map.Height); + Color[] colours = new Color[pallete]; + + for (int i = 0; i < pallete; i++) + { + colours[i] = gradientmapLd.GetPixel(0, i); + } + + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + // 512 is the largest possible height before colours clamp + int colorindex = (int) (Math.Max(Math.Min(1.0, map[x, y] / 512.0), 0.0) * (pallete - 1)); + bmp.SetPixel(x, map.Height - y - 1, colours[colorindex]); + } + } + return bmp; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs new file mode 100644 index 0000000..a86ae00 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs @@ -0,0 +1,250 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + public class LLRAW : ITerrainLoader + { + public struct HeightmapLookupValue : IComparable + { + public int Index; + public double Value; + + public HeightmapLookupValue(int index, double value) + { + Index = index; + Value = value; + } + + public int CompareTo(HeightmapLookupValue val) + { + return Value.CompareTo(val.Value); + } + } + + /// Lookup table to speed up terrain exports + HeightmapLookupValue[] LookupHeightTable; + + public LLRAW() + { + LookupHeightTable = new HeightmapLookupValue[256 * 256]; + + for (int i = 0; i < 256; i++) + { + for (int j = 0; j < 256; j++) + { + LookupHeightTable[i + (j * 256)] = new HeightmapLookupValue(i + (j * 256), ((double)i * ((double)j / 128.0d))); + } + } + Array.Sort(LookupHeightTable); + } + + #region ITerrainLoader Members + + public ITerrainChannel LoadFile(string filename) + { + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Open, FileAccess.Read); + ITerrainChannel retval = LoadStream(s); + + s.Close(); + + return retval; + } + + public ITerrainChannel LoadFile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight) + { + TerrainChannel retval = new TerrainChannel(sectionWidth, sectionHeight); + + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Open, FileAccess.Read); + BinaryReader bs = new BinaryReader(s); + + int currFileYOffset = fileHeight - 1; + + // if our region isn't on the first Y section of the areas to be landscaped, then + // advance to our section of the file + while (currFileYOffset > offsetY) + { + // read a whole strip of regions + int heightsToRead = sectionHeight * (fileWidth * sectionWidth); + bs.ReadBytes(heightsToRead * 13); // because there are 13 fun channels + currFileYOffset--; + } + + // got to the Y start offset within the file of our region + // so read the file bits associated with our region + int y; + // for each Y within our Y offset + for (y = sectionHeight - 1; y >= 0; y--) + { + int currFileXOffset = 0; + + // if our region isn't the first X section of the areas to be landscaped, then + // advance the stream to the X start pos of our section in the file + // i.e. eat X upto where we start + while (currFileXOffset < offsetX) + { + bs.ReadBytes(sectionWidth * 13); + currFileXOffset++; + } + + // got to our X offset, so write our regions X line + int x; + for (x = 0; x < sectionWidth; x++) + { + // Read a strip and continue + retval[x, y] = bs.ReadByte() * (bs.ReadByte() / 128.0); + bs.ReadBytes(11); + } + // record that we wrote it + currFileXOffset++; + + // if our region isn't the last X section of the areas to be landscaped, then + // advance the stream to the end of this Y column + while (currFileXOffset < fileWidth) + { + // eat the next regions x line + bs.ReadBytes(sectionWidth * 13); //The 13 channels again + currFileXOffset++; + } + } + + bs.Close(); + s.Close(); + + return retval; + } + + public ITerrainChannel LoadStream(Stream s) + { + TerrainChannel retval = new TerrainChannel(); + + BinaryReader bs = new BinaryReader(s); + int y; + for (y = 0; y < retval.Height; y++) + { + int x; + for (x = 0; x < retval.Width; x++) + { + retval[x, (retval.Height - 1) - y] = bs.ReadByte() * (bs.ReadByte() / 128.0); + bs.ReadBytes(11); // Advance the stream to next bytes. + } + } + + bs.Close(); + + return retval; + } + + public void SaveFile(string filename, ITerrainChannel map) + { + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write); + SaveStream(s, map); + + s.Close(); + } + + public void SaveStream(Stream s, ITerrainChannel map) + { + BinaryWriter binStream = new BinaryWriter(s); + + // Output the calculated raw + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + double t = map[x, (map.Height - 1) - y]; + //if height is less than 0, set it to 0 as + //can't save -ve values in a LLRAW file + if (t < 0d) + { + t = 0d; + } + + int index = 0; + + // The lookup table is pre-sorted, so we either find an exact match or + // the next closest (smaller) match with a binary search + index = Array.BinarySearch(LookupHeightTable, new HeightmapLookupValue(0, t)); + if (index < 0) + index = ~index - 1; + + index = LookupHeightTable[index].Index; + + byte red = (byte) (index & 0xFF); + byte green = (byte) ((index >> 8) & 0xFF); + const byte blue = 20; + const byte alpha1 = 0; + const byte alpha2 = 0; + const byte alpha3 = 0; + const byte alpha4 = 0; + const byte alpha5 = 255; + const byte alpha6 = 255; + const byte alpha7 = 255; + const byte alpha8 = 255; + byte alpha9 = red; + byte alpha10 = green; + + binStream.Write(red); + binStream.Write(green); + binStream.Write(blue); + binStream.Write(alpha1); + binStream.Write(alpha2); + binStream.Write(alpha3); + binStream.Write(alpha4); + binStream.Write(alpha5); + binStream.Write(alpha6); + binStream.Write(alpha7); + binStream.Write(alpha8); + binStream.Write(alpha9); + binStream.Write(alpha10); + } + } + + binStream.Close(); + } + + public string FileExtension + { + get { return ".raw"; } + } + + #endregion + + public override string ToString() + { + return "LL/SL RAW"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs new file mode 100644 index 0000000..0dea282 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + internal class PNG : GenericSystemDrawing + { + public override void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Png); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public override void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Png); + } + + public override string ToString() + { + return "PNG"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs new file mode 100644 index 0000000..178104f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs @@ -0,0 +1,170 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.IO; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + public class RAW32 : ITerrainLoader + { + #region ITerrainLoader Members + + public string FileExtension + { + get { return ".r32"; } + } + + public ITerrainChannel LoadFile(string filename) + { + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Open, FileAccess.Read); + ITerrainChannel retval = LoadStream(s); + + s.Close(); + + return retval; + } + + public ITerrainChannel LoadFile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight) + { + TerrainChannel retval = new TerrainChannel(sectionWidth, sectionHeight); + + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Open, FileAccess.Read); + BinaryReader bs = new BinaryReader(s); + + int currFileYOffset = 0; + + // if our region isn't on the first Y section of the areas to be landscaped, then + // advance to our section of the file + while (currFileYOffset < offsetY) + { + // read a whole strip of regions + int heightsToRead = sectionHeight * (fileWidth * sectionWidth); + bs.ReadBytes(heightsToRead * 4); // because the floats are 4 bytes in the file + currFileYOffset++; + } + + // got to the Y start offset within the file of our region + // so read the file bits associated with our region + int y; + // for each Y within our Y offset + for (y = 0; y < sectionHeight; y++) + { + int currFileXOffset = 0; + + // if our region isn't the first X section of the areas to be landscaped, then + // advance the stream to the X start pos of our section in the file + // i.e. eat X upto where we start + while (currFileXOffset < offsetX) + { + bs.ReadBytes(sectionWidth * 4); // 4 bytes = single + currFileXOffset++; + } + + // got to our X offset, so write our regions X line + int x; + for (x = 0; x < sectionWidth; x++) + { + // Read a strip and continue + retval[x, y] = bs.ReadSingle(); + } + // record that we wrote it + currFileXOffset++; + + // if our region isn't the last X section of the areas to be landscaped, then + // advance the stream to the end of this Y column + while (currFileXOffset < fileWidth) + { + // eat the next regions x line + bs.ReadBytes(sectionWidth * 4); // 4 bytes = single + currFileXOffset++; + } + } + + bs.Close(); + s.Close(); + + return retval; + } + + public ITerrainChannel LoadStream(Stream s) + { + TerrainChannel retval = new TerrainChannel(); + + BinaryReader bs = new BinaryReader(s); + int y; + for (y = 0; y < retval.Height; y++) + { + int x; + for (x = 0; x < retval.Width; x++) + { + retval[x, y] = bs.ReadSingle(); + } + } + + bs.Close(); + + return retval; + } + + public void SaveFile(string filename, ITerrainChannel map) + { + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Create, FileAccess.Write); + SaveStream(s, map); + + s.Close(); + } + + public void SaveStream(Stream s, ITerrainChannel map) + { + BinaryWriter bs = new BinaryWriter(s); + + int y; + for (y = 0; y < map.Height; y++) + { + int x; + for (x = 0; x < map.Width; x++) + { + bs.Write((float) map[x, y]); + } + } + + bs.Close(); + } + + #endregion + + public override string ToString() + { + return "RAW32"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs new file mode 100644 index 0000000..220431f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + internal class TIFF : GenericSystemDrawing + { + public override void SaveFile(string filename, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(filename, ImageFormat.Tiff); + } + + /// + /// Exports a stream using a System.Drawing exporter. + /// + /// The target stream + /// The terrain channel being saved + public override void SaveStream(Stream stream, ITerrainChannel map) + { + Bitmap colours = CreateGrayscaleBitmapFromMap(map); + + colours.Save(stream, ImageFormat.Tiff); + } + + public override string ToString() + { + return "TIFF"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs new file mode 100644 index 0000000..426708d --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs @@ -0,0 +1,142 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Text; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders +{ + /// + /// Terragen File Format Loader + /// Built from specification at + /// http://www.planetside.co.uk/terragen/dev/tgterrain.html + /// + internal class Terragen : ITerrainLoader + { + #region ITerrainLoader Members + + public ITerrainChannel LoadFile(string filename) + { + FileInfo file = new FileInfo(filename); + FileStream s = file.Open(FileMode.Open, FileAccess.Read); + ITerrainChannel retval = LoadStream(s); + + s.Close(); + + return retval; + } + + public ITerrainChannel LoadStream(Stream s) + { + TerrainChannel retval = new TerrainChannel(); + + BinaryReader bs = new BinaryReader(s); + + bool eof = false; + if (Encoding.ASCII.GetString(bs.ReadBytes(16)) == "TERRAGENTERRAIN ") + { + int w = 256; + int h = 256; + + // Terragen file + while (eof == false) + { + string tmp = Encoding.ASCII.GetString(bs.ReadBytes(4)); + switch (tmp) + { + case "SIZE": + int sztmp = bs.ReadInt16() + 1; + w = sztmp; + h = sztmp; + bs.ReadInt16(); + break; + case "XPTS": + w = bs.ReadInt16(); + bs.ReadInt16(); + break; + case "YPTS": + h = bs.ReadInt16(); + bs.ReadInt16(); + break; + case "ALTW": + eof = true; + Int16 heightScale = bs.ReadInt16(); + Int16 baseHeight = bs.ReadInt16(); + retval = new TerrainChannel(w, h); + int x; + for (x = 0; x < w; x++) + { + int y; + for (y = 0; y < h; y++) + { + retval[x, y] = baseHeight + bs.ReadInt16() * (double) heightScale / 65536.0; + } + } + break; + default: + bs.ReadInt32(); + break; + } + } + } + + bs.Close(); + + return retval; + } + + public void SaveFile(string filename, ITerrainChannel map) + { + throw new NotImplementedException(); + } + + public void SaveStream(Stream stream, ITerrainChannel map) + { + throw new NotImplementedException(); + } + + public string FileExtension + { + get { return ".ter"; } + } + + public ITerrainChannel LoadFile(string filename, int x, int y, int fileWidth, int fileHeight, int w, int h) + { + throw new NotImplementedException(); + } + + #endregion + + public override string ToString() + { + return "Terragen"; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/FlattenArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/FlattenArea.cs new file mode 100644 index 0000000..fe79c0b --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/FlattenArea.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class FlattenArea : ITerrainFloodEffect + { + #region ITerrainFloodEffect Members + + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + double sum = 0.0; + double steps = 0.0; + + int x, y; + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + { + sum += map[x, y]; + steps += 1.0; + } + } + } + + double avg = sum / steps; + + double str = 0.1 * strength; // == 0.2 in the default client + + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + map[x, y] = (map[x, y] * (1.0 - str)) + (avg * str); + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/LowerArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/LowerArea.cs new file mode 100644 index 0000000..5c0aace --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/LowerArea.cs @@ -0,0 +1,54 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class LowerArea : ITerrainFloodEffect + { + #region ITerrainFloodEffect Members + + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + { + map[x, y] -= strength; + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs new file mode 100644 index 0000000..02f2b53 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/NoiseArea.cs @@ -0,0 +1,58 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class NoiseArea : ITerrainFloodEffect + { + #region ITerrainFloodEffect Members + + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + { + double noise = TerrainUtil.PerlinNoise2D((double) x / Constants.RegionSize, (double) y / Constants.RegionSize, 8, 1.0); + + map[x, y] += noise * strength; + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RaiseArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RaiseArea.cs new file mode 100644 index 0000000..768b31f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RaiseArea.cs @@ -0,0 +1,54 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class RaiseArea : ITerrainFloodEffect + { + #region ITerrainFloodEffect Members + + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + { + map[x, y] += strength; + } + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RevertArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RevertArea.cs new file mode 100644 index 0000000..66b9055 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/RevertArea.cs @@ -0,0 +1,67 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class RevertArea : ITerrainFloodEffect + { + private readonly ITerrainChannel m_revertmap; + + public RevertArea(ITerrainChannel revertmap) + { + m_revertmap = revertmap; + } + + #region ITerrainFloodEffect Members + + /// + /// reverts an area of the map to the heightfield stored in the revertmap + /// + /// the current heightmap + /// array indicating which sections of the map are to be reverted + /// unused + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (fillArea[x, y]) + { + map[x, y] = m_revertmap[x, y]; + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/SmoothArea.cs b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/SmoothArea.cs new file mode 100644 index 0000000..a75dde1 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/FloodBrushes/SmoothArea.cs @@ -0,0 +1,114 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.FloodBrushes +{ + public class SmoothArea : ITerrainFloodEffect + { + #region ITerrainFloodEffect Members + + public void FloodEffect(ITerrainChannel map, bool[,] fillArea, double strength) + { + double area = strength; + double step = strength / 4.0; + + double[,] manipulate = new double[map.Width,map.Height]; + int x, y; + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (!fillArea[x, y]) + continue; + + double average = 0.0; + int avgsteps = 0; + + double n; + for (n = 0.0 - area; n < area; n += step) + { + double l; + for (l = 0.0 - area; l < area; l += step) + { + avgsteps++; + average += GetBilinearInterpolate(x + n, y + l, map); + } + } + + manipulate[x, y] = average / avgsteps; + } + } + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (!fillArea[x, y]) + continue; + + map[x, y] = manipulate[x, y]; + } + } + } + + #endregion + + private static double GetBilinearInterpolate(double x, double y, ITerrainChannel map) + { + int w = map.Width; + int h = map.Height; + + if (x > w - 2.0) + x = w - 2.0; + if (y > h - 2.0) + y = h - 2.0; + if (x < 0.0) + x = 0.0; + if (y < 0.0) + y = 0.0; + + const int stepSize = 1; + double h00 = map[(int) x, (int) y]; + double h10 = map[(int) x + stepSize, (int) y]; + double h01 = map[(int) x, (int) y + stepSize]; + double h11 = map[(int) x + stepSize, (int) y + stepSize]; + double h1 = h00; + double h2 = h10; + double h3 = h01; + double h4 = h11; + double a00 = h1; + double a10 = h2 - h1; + double a01 = h3 - h1; + double a11 = h1 - h2 - h3 + h4; + double partialx = x - (int) x; + double partialz = y - (int) y; + double hi = a00 + (a10 * partialx) + (a01 * partialz) + (a11 * partialx * partialz); + return hi; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainEffect.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainEffect.cs new file mode 100644 index 0000000..40b9f5a --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainEffect.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainEffect + { + void RunEffect(ITerrainChannel map); + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainFloodEffect.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainFloodEffect.cs new file mode 100644 index 0000000..eee7a83 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainFloodEffect.cs @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainFloodEffect + { + void FloodEffect(ITerrainChannel map, Boolean[,] fillArea, double strength); + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs new file mode 100644 index 0000000..c62b897 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.IO; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainLoader + { + string FileExtension { get; } + ITerrainChannel LoadFile(string filename); + ITerrainChannel LoadFile(string filename, int fileStartX, int fileStartY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight); + ITerrainChannel LoadStream(Stream stream); + void SaveFile(string filename, ITerrainChannel map); + void SaveStream(Stream stream, ITerrainChannel map); + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainModule.cs new file mode 100644 index 0000000..8c5d1d9 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainModule.cs @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +using System.IO; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainModule + { + void LoadFromFile(string filename); + void SaveToFile(string filename); + void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId); + + /// + /// Load a terrain from a stream. + /// + /// + /// Only required here to identify the image type. Not otherwise used in the loading itself. + /// + /// + void LoadFromStream(string filename, Stream stream); + + /// + /// Save a terrain to a stream. + /// + /// + /// Only required here to identify the image type. Not otherwise used in the saving itself. + /// + /// + void SaveToStream(string filename, Stream stream); + + void InstallPlugin(string name, ITerrainEffect plug); + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainPaintableEffect.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainPaintableEffect.cs new file mode 100644 index 0000000..15d9f6e --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainPaintableEffect.cs @@ -0,0 +1,36 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public interface ITerrainPaintableEffect + { + void PaintEffect(ITerrainChannel map, bool[,] allowMask, double x, double y, double z, double strength, double duration); + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/ErodeSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/ErodeSphere.cs new file mode 100644 index 0000000..6ce6994 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/ErodeSphere.cs @@ -0,0 +1,318 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + /// + /// Hydraulic Erosion Brush + /// + public class ErodeSphere : ITerrainPaintableEffect + { + private const double rainHeight = 0.2; + private const int rounds = 10; + private const NeighbourSystem type = NeighbourSystem.Moore; + private const double waterSaturation = 0.30; + + #region Supporting Functions + + private static int[] Neighbours(NeighbourSystem neighbourType, int index) + { + int[] coord = new int[2]; + + index++; + + switch (neighbourType) + { + case NeighbourSystem.Moore: + switch (index) + { + case 1: + coord[0] = -1; + coord[1] = -1; + break; + + case 2: + coord[0] = -0; + coord[1] = -1; + break; + + case 3: + coord[0] = +1; + coord[1] = -1; + break; + + case 4: + coord[0] = -1; + coord[1] = -0; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + case 6: + coord[0] = +1; + coord[1] = -0; + break; + + case 7: + coord[0] = -1; + coord[1] = +1; + break; + + case 8: + coord[0] = -0; + coord[1] = +1; + break; + + case 9: + coord[0] = +1; + coord[1] = +1; + break; + + default: + break; + } + break; + + case NeighbourSystem.VonNeumann: + switch (index) + { + case 1: + coord[0] = 0; + coord[1] = -1; + break; + + case 2: + coord[0] = -1; + coord[1] = 0; + break; + + case 3: + coord[0] = +1; + coord[1] = 0; + break; + + case 4: + coord[0] = 0; + coord[1] = +1; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + default: + break; + } + break; + } + + return coord; + } + + private enum NeighbourSystem + { + Moore, + VonNeumann + } ; + + #endregion + + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x, y; + // Using one 'rain' round for this, so skipping a useless loop + // Will need to adapt back in for the Flood brush + + ITerrainChannel water = new TerrainChannel(map.Width, map.Height); + ITerrainChannel sediment = new TerrainChannel(map.Width, map.Height); + + // Fill with rain + for (x = 0; x < water.Width; x++) + for (y = 0; y < water.Height; y++) + water[x, y] = Math.Max(0.0, TerrainUtil.SphericalFactor(x, y, rx, ry, strength) * rainHeight * duration); + + for (int i = 0; i < rounds; i++) + { + // Erode underlying terrain + for (x = 0; x < water.Width; x++) + { + for (y = 0; y < water.Height; y++) + { + if (mask[x,y]) + { + const double solConst = (1.0 / rounds); + double sedDelta = water[x, y] * solConst; + map[x, y] -= sedDelta; + sediment[x, y] += sedDelta; + } + } + } + + // Move water + for (x = 0; x < water.Width; x++) + { + for (y = 0; y < water.Height; y++) + { + if (water[x, y] <= 0) + continue; + + // Step 1. Calculate average of neighbours + + int neighbours = 0; + double altitudeTotal = 0.0; + double altitudeMe = map[x, y] + water[x, y]; + + const int NEIGHBOUR_ME = 4; + const int NEIGHBOUR_MAX = 9; + + for (int j = 0; j < NEIGHBOUR_MAX; j++) + { + if (j != NEIGHBOUR_ME) + { + int[] coords = Neighbours(type, j); + + coords[0] += x; + coords[1] += y; + + if (coords[0] > map.Width - 1) + continue; + if (coords[1] > map.Height - 1) + continue; + if (coords[0] < 0) + continue; + if (coords[1] < 0) + continue; + + // Calculate total height of this neighbour + double altitudeNeighbour = water[coords[0], coords[1]] + map[coords[0], coords[1]]; + + // If it's greater than me... + if (altitudeNeighbour - altitudeMe < 0) + { + // Add it to our calculations + neighbours++; + altitudeTotal += altitudeNeighbour; + } + } + } + + if (neighbours == 0) + continue; + + double altitudeAvg = altitudeTotal / neighbours; + + // Step 2. Allocate water to neighbours. + for (int j = 0; j < NEIGHBOUR_MAX; j++) + { + if (j != NEIGHBOUR_ME) + { + int[] coords = Neighbours(type, j); + + coords[0] += x; + coords[1] += y; + + if (coords[0] > map.Width - 1) + continue; + if (coords[1] > map.Height - 1) + continue; + if (coords[0] < 0) + continue; + if (coords[1] < 0) + continue; + + // Skip if we dont have water to begin with. + if (water[x, y] < 0) + continue; + + // Calculate our delta average + double altitudeDelta = altitudeMe - altitudeAvg; + + if (altitudeDelta < 0) + continue; + + // Calculate how much water we can move + double waterMin = Math.Min(water[x, y], altitudeDelta); + double waterDelta = waterMin * ((water[coords[0], coords[1]] + map[coords[0], coords[1]]) + / altitudeTotal); + + double sedimentDelta = sediment[x, y] * (waterDelta / water[x, y]); + + if (sedimentDelta > 0) + { + sediment[x, y] -= sedimentDelta; + sediment[coords[0], coords[1]] += sedimentDelta; + } + } + } + } + } + + // Evaporate + + for (x = 0; x < water.Width; x++) + { + for (y = 0; y < water.Height; y++) + { + water[x, y] *= 1.0 - (rainHeight / rounds); + + double waterCapacity = waterSaturation * water[x, y]; + + double sedimentDeposit = sediment[x, y] - waterCapacity; + if (sedimentDeposit > 0) + { + if (mask[x,y]) + { + sediment[x, y] -= sedimentDeposit; + map[x, y] += sedimentDeposit; + } + } + } + } + } + + // Deposit any remainder (should be minimal) + for (x = 0; x < water.Width; x++) + for (y = 0; y < water.Height; y++) + if (mask[x,y] && sediment[x, y] > 0) + map[x, y] += sediment[x, y]; + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/FlattenSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/FlattenSphere.cs new file mode 100644 index 0000000..928a595 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/FlattenSphere.cs @@ -0,0 +1,101 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class FlattenSphere : ITerrainPaintableEffect + { + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x, y; + + if (rz < 0) { + double sum = 0.0; + double step2 = 0.0; + duration = 0.009; //MCP Should be read from ini file + + + // compute delta map + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength); + + if (z > 0) // add in non-zero amount + { + sum += map[x, y] * z; + step2 += z; + } + } + } + rz = sum / step2; + } + + + // blend in map + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength) * duration; + + if (z > 0) // add in non-zero amount + { + if (z > 1.0) + z = 1.0; + + map[x, y] = (map[x, y] * (1.0 - z)) + (rz * z); + } + + double delta = rz - map[x, y]; + if (Math.Abs(delta) > 0.1) + delta *= 0.25; + + if (delta != 0) // add in non-zero amount + { + map[x, y] += delta; + } + + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/LowerSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/LowerSphere.cs new file mode 100644 index 0000000..8c40088 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/LowerSphere.cs @@ -0,0 +1,84 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class LowerSphere : ITerrainPaintableEffect + { + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + int s = (int) (Math.Pow(2, strength) + 0.5); + + int x; + int xFrom = (int)(rx-s+0.5); + int xTo = (int)(rx+s+0.5) + 1; + int yFrom = (int)(ry-s+0.5); + int yTo = (int)(ry+s+0.5) + 1; + + if (xFrom < 0) + xFrom = 0; + + if (yFrom < 0) + yFrom = 0; + + if (xTo > map.Width) + xTo = map.Width; + + if (yTo > map.Width) + yTo = map.Width; + + for (x = xFrom; x < xTo; x++) + { + int y; + for (y = yFrom; y < yTo; y++) + { + if (!mask[x,y]) + continue; + + // Calculate a cos-sphere and add it to the heighmap + double r = Math.Sqrt((x-rx) * (x-rx) + ((y-ry) * (y-ry))); + double z = Math.Cos(r * Math.PI / (s * 2)); + if (z > 0.0) + { + double newz = map[x, y] - z * duration; + if (newz < 0.0) + map[x, y] = 0.0; + else + map[x, y] = newz; + } + } + } + + } + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs new file mode 100644 index 0000000..95a8c33 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/NoiseSphere.cs @@ -0,0 +1,67 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class NoiseSphere : ITerrainPaintableEffect + { + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + // Calculate a sphere and add it to the heighmap + double z = strength; + z *= z; + z -= ((x - rx) * (x - rx)) + ((y - ry) * (y - ry)); + + double noise = TerrainUtil.PerlinNoise2D(x / (double) Constants.RegionSize, y / (double) Constants.RegionSize, 8, 1.0); + + if (z > 0.0) + map[x, y] += noise * z * duration; + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/OlsenSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/OlsenSphere.cs new file mode 100644 index 0000000..1a2528a --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/OlsenSphere.cs @@ -0,0 +1,223 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + /// + /// Speed-Optimised Hybrid Erosion Brush + /// + /// As per Jacob Olsen's Paper + /// http://www.oddlabs.com/download/terrain_generation.pdf + /// + public class OlsenSphere : ITerrainPaintableEffect + { + private const double nConst = 1024.0; + private const NeighbourSystem type = NeighbourSystem.Moore; + + #region Supporting Functions + + private static int[] Neighbours(NeighbourSystem neighbourType, int index) + { + int[] coord = new int[2]; + + index++; + + switch (neighbourType) + { + case NeighbourSystem.Moore: + switch (index) + { + case 1: + coord[0] = -1; + coord[1] = -1; + break; + + case 2: + coord[0] = -0; + coord[1] = -1; + break; + + case 3: + coord[0] = +1; + coord[1] = -1; + break; + + case 4: + coord[0] = -1; + coord[1] = -0; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + case 6: + coord[0] = +1; + coord[1] = -0; + break; + + case 7: + coord[0] = -1; + coord[1] = +1; + break; + + case 8: + coord[0] = -0; + coord[1] = +1; + break; + + case 9: + coord[0] = +1; + coord[1] = +1; + break; + + default: + break; + } + break; + + case NeighbourSystem.VonNeumann: + switch (index) + { + case 1: + coord[0] = 0; + coord[1] = -1; + break; + + case 2: + coord[0] = -1; + coord[1] = 0; + break; + + case 3: + coord[0] = +1; + coord[1] = 0; + break; + + case 4: + coord[0] = 0; + coord[1] = +1; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + default: + break; + } + break; + } + + return coord; + } + + private enum NeighbourSystem + { + Moore, + VonNeumann + } ; + + #endregion + + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x; + + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength); + + if (z > 0) // add in non-zero amount + { + const int NEIGHBOUR_ME = 4; + const int NEIGHBOUR_MAX = 9; + + double max = Double.MinValue; + int loc = 0; + + + for (int j = 0; j < NEIGHBOUR_MAX; j++) + { + if (j != NEIGHBOUR_ME) + { + int[] coords = Neighbours(type, j); + + coords[0] += x; + coords[1] += y; + + if (coords[0] > map.Width - 1) + continue; + if (coords[1] > map.Height - 1) + continue; + if (coords[0] < 0) + continue; + if (coords[1] < 0) + continue; + + double cellmax = map[x, y] - map[coords[0], coords[1]]; + if (cellmax > max) + { + max = cellmax; + loc = j; + } + } + } + + double T = nConst / ((map.Width + map.Height) / 2.0); + // Apply results + if (0 < max && max <= T) + { + int[] maxCoords = Neighbours(type, loc); + double heightDelta = 0.5 * max * z * duration; + map[x, y] -= heightDelta; + map[x + maxCoords[0], y + maxCoords[1]] += heightDelta; + } + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RaiseSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RaiseSphere.cs new file mode 100644 index 0000000..c53bb7d --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RaiseSphere.cs @@ -0,0 +1,80 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class RaiseSphere : ITerrainPaintableEffect + { + #region ITerrainPaintableEffect Members + + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + int s = (int) (Math.Pow(2, strength) + 0.5); + + int x; + int xFrom = (int)(rx-s+0.5); + int xTo = (int)(rx+s+0.5) + 1; + int yFrom = (int)(ry-s+0.5); + int yTo = (int)(ry+s+0.5) + 1; + + if (xFrom < 0) + xFrom = 0; + + if (yFrom < 0) + yFrom = 0; + + if (xTo > map.Width) + xTo = map.Width; + + if (yTo > map.Width) + yTo = map.Width; + + for (x = xFrom; x < xTo; x++) + { + int y; + for (y = yFrom; y < yTo; y++) + { + if (!mask[x,y]) + continue; + + // Calculate a cos-sphere and add it to the heighmap + double r = Math.Sqrt((x-rx) * (x-rx) + ((y-ry) * (y-ry))); + double z = Math.Cos(r * Math.PI / (s * 2)); + if (z > 0.0) + map[x, y] += z * duration; + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RevertSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RevertSphere.cs new file mode 100644 index 0000000..4ed8a13 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/RevertSphere.cs @@ -0,0 +1,80 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class RevertSphere : ITerrainPaintableEffect + { + private readonly ITerrainChannel m_revertmap; + + public RevertSphere(ITerrainChannel revertmap) + { + m_revertmap = revertmap; + } + + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + duration = 0.03; //MCP Should be read from ini file + + if (duration > 1.0) + duration = 1.0; + if (duration < 0) + return; + + int x; + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + // Calculate a sphere and add it to the heighmap + double z = strength; + z *= z; + z -= ((x - rx) * (x - rx)) + ((y - ry) * (y - ry)); + + if (z > 0.0) + { + z *= duration; + map[x, y] = (map[x, y] * (1.0 - z)) + (m_revertmap[x, y] * z); + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/SmoothSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/SmoothSphere.cs new file mode 100644 index 0000000..6636d8f --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/SmoothSphere.cs @@ -0,0 +1,100 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + public class SmoothSphere : ITerrainPaintableEffect + { + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x, y; + double[,] tweak = new double[map.Width,map.Height]; + + double area = strength; + double step = strength / 4.0; + duration = 0.03; //MCP Should be read from ini file + + + // compute delta map + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength); + + if (z > 0) // add in non-zero amount + { + double average = 0.0; + int avgsteps = 0; + + double n; + for (n = 0.0 - area; n < area; n += step) + { + double l; + for (l = 0.0 - area; l < area; l += step) + { + avgsteps++; + average += TerrainUtil.GetBilinearInterpolate(x + n, y + l, map); + } + } + tweak[x, y] = average / avgsteps; + } + } + } + // blend in map + for (x = 0; x < map.Width; x++) + { + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength); + + if (z > 0) // add in non-zero amount + { + double da = z; + double a = (map[x, y] - tweak[x, y]) * da; + double newz = map[x, y] - (a * duration); + + if (newz > 0.0) + map[x, y] = newz; + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/WeatherSphere.cs b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/WeatherSphere.cs new file mode 100644 index 0000000..6b00cc8 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/PaintBrushes/WeatherSphere.cs @@ -0,0 +1,211 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.Terrain.PaintBrushes +{ + /// + /// Thermal Weathering Paint Brush + /// + public class WeatherSphere : ITerrainPaintableEffect + { + private const double talus = 0.2; + private const NeighbourSystem type = NeighbourSystem.Moore; + + #region Supporting Functions + + private static int[] Neighbours(NeighbourSystem neighbourType, int index) + { + int[] coord = new int[2]; + + index++; + + switch (neighbourType) + { + case NeighbourSystem.Moore: + switch (index) + { + case 1: + coord[0] = -1; + coord[1] = -1; + break; + + case 2: + coord[0] = -0; + coord[1] = -1; + break; + + case 3: + coord[0] = +1; + coord[1] = -1; + break; + + case 4: + coord[0] = -1; + coord[1] = -0; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + case 6: + coord[0] = +1; + coord[1] = -0; + break; + + case 7: + coord[0] = -1; + coord[1] = +1; + break; + + case 8: + coord[0] = -0; + coord[1] = +1; + break; + + case 9: + coord[0] = +1; + coord[1] = +1; + break; + + default: + break; + } + break; + + case NeighbourSystem.VonNeumann: + switch (index) + { + case 1: + coord[0] = 0; + coord[1] = -1; + break; + + case 2: + coord[0] = -1; + coord[1] = 0; + break; + + case 3: + coord[0] = +1; + coord[1] = 0; + break; + + case 4: + coord[0] = 0; + coord[1] = +1; + break; + + case 5: + coord[0] = -0; + coord[1] = -0; + break; + + default: + break; + } + break; + } + + return coord; + } + + private enum NeighbourSystem + { + Moore, + VonNeumann + } ; + + #endregion + + #region ITerrainPaintableEffect Members + + public void PaintEffect(ITerrainChannel map, bool[,] mask, double rx, double ry, double rz, double strength, double duration) + { + strength = TerrainUtil.MetersToSphericalStrength(strength); + + int x; + + for (x = 0; x < map.Width; x++) + { + int y; + for (y = 0; y < map.Height; y++) + { + if (!mask[x,y]) + continue; + + double z = TerrainUtil.SphericalFactor(x, y, rx, ry, strength); + + if (z > 0) // add in non-zero amount + { + const int NEIGHBOUR_ME = 4; + const int NEIGHBOUR_MAX = 9; + + for (int j = 0; j < NEIGHBOUR_MAX; j++) + { + if (j != NEIGHBOUR_ME) + { + int[] coords = Neighbours(type, j); + + coords[0] += x; + coords[1] += y; + + if (coords[0] > map.Width - 1) + continue; + if (coords[1] > map.Height - 1) + continue; + if (coords[0] < 0) + continue; + if (coords[1] < 0) + continue; + + double heightF = map[x, y]; + double target = map[coords[0], coords[1]]; + + if (target > heightF + talus) + { + double calc = duration * ((target - heightF) - talus) * z; + heightF += calc; + target -= calc; + } + + map[x, y] = heightF; + map[coords[0], coords[1]] = target; + } + } + } + } + } + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainException.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainException.cs new file mode 100644 index 0000000..ff9b8ec --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainException.cs @@ -0,0 +1,46 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public class TerrainException : Exception + { + public TerrainException() + { + } + + public TerrainException(string msg) : base(msg) + { + } + + public TerrainException(string msg, Exception e) : base(msg, e) + { + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs new file mode 100644 index 0000000..9de7338 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -0,0 +1,1001 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using OpenMetaverse; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Framework.InterfaceCommander; +using OpenSim.Region.CoreModules.World.Terrain.FileLoaders; +using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes; +using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; + +namespace OpenSim.Region.CoreModules.World.Terrain +{ + public class TerrainModule : IRegionModule, ICommandableModule, ITerrainModule + { + #region StandardTerrainEffects enum + + /// + /// A standard set of terrain brushes and effects recognised by viewers + /// + public enum StandardTerrainEffects : byte + { + Flatten = 0, + Raise = 1, + Lower = 2, + Smooth = 3, + Noise = 4, + Revert = 5, + + // Extended brushes + Erode = 255, + Weather = 254, + Olsen = 253 + } + + #endregion + + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private readonly Commander m_commander = new Commander("terrain"); + + private readonly Dictionary m_floodeffects = + new Dictionary(); + + private readonly Dictionary m_loaders = new Dictionary(); + + private readonly Dictionary m_painteffects = + new Dictionary(); + + private ITerrainChannel m_channel; + private Dictionary m_plugineffects; + private ITerrainChannel m_revert; + private Scene m_scene; + private bool m_tainted; + + #region ICommandableModule Members + + public ICommander CommandInterface + { + get { return m_commander; } + } + + #endregion + + #region IRegionModule Members + + /// + /// Creates and initialises a terrain module for a region + /// + /// Region initialising + /// Config for the region + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + + // Install terrain module in the simulator + if (m_scene.Heightmap == null) + { + lock (m_scene) + { + m_channel = new TerrainChannel(); + m_scene.Heightmap = m_channel; + m_revert = new TerrainChannel(); + UpdateRevertMap(); + } + } + else + { + m_channel = m_scene.Heightmap; + m_revert = new TerrainChannel(); + UpdateRevertMap(); + } + + m_scene.RegisterModuleInterface(this); + m_scene.EventManager.OnNewClient += EventManager_OnNewClient; + m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; + m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick; + } + + /// + /// Enables terrain module when called + /// + public void PostInitialise() + { + InstallDefaultEffects(); + InstallInterfaces(); + LoadPlugins(); + } + + public void Close() + { + } + + public string Name + { + get { return "TerrainModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + #region ITerrainModule Members + + /// + /// Loads a terrain file from disk and installs it in the scene. + /// + /// Filename to terrain file. Type is determined by extension. + public void LoadFromFile(string filename) + { + foreach (KeyValuePair loader in m_loaders) + { + if (filename.EndsWith(loader.Key)) + { + lock (m_scene) + { + try + { + ITerrainChannel channel = loader.Value.LoadFile(filename); + if (channel.Width != Constants.RegionSize || channel.Height != Constants.RegionSize) + { + // TerrainChannel expects a RegionSize x RegionSize map, currently + throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}", + Constants.RegionSize, Constants.RegionSize)); + } + m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height); + m_scene.Heightmap = channel; + m_channel = channel; + UpdateRevertMap(); + } + catch (NotImplementedException) + { + m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + + " parser does not support file loading. (May be save only)"); + throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); + } + catch (FileNotFoundException) + { + m_log.Error( + "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)"); + throw new TerrainException( + String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename)); + } + catch (ArgumentException e) + { + m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message); + throw new TerrainException( + String.Format("Unable to load heightmap: {0}", e.Message)); + } + } + CheckForTerrainUpdates(); + m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); + return; + } + } + + m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); + throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); + } + + /// + /// Saves the current heightmap to a specified file. + /// + /// The destination filename + public void SaveToFile(string filename) + { + try + { + foreach (KeyValuePair loader in m_loaders) + { + if (filename.EndsWith(loader.Key)) + { + loader.Value.SaveFile(filename, m_channel); + return; + } + } + } + catch (NotImplementedException) + { + m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented."); + throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented")); + } + } + + /// + /// Loads a terrain file from a stream and installs it in the scene. + /// + /// Filename to terrain file. Type is determined by extension. + /// + public void LoadFromStream(string filename, Stream stream) + { + foreach (KeyValuePair loader in m_loaders) + { + if (@filename.EndsWith(loader.Key)) + { + lock (m_scene) + { + try + { + ITerrainChannel channel = loader.Value.LoadStream(stream); + m_scene.Heightmap = channel; + m_channel = channel; + UpdateRevertMap(); + } + catch (NotImplementedException) + { + m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + + " parser does not support file loading. (May be save only)"); + throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); + } + } + + CheckForTerrainUpdates(); + m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); + return; + } + } + m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); + throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); + } + + /// + /// Modify Land + /// + /// Land-position (X,Y,0) + /// The size of the brush (0=small, 1=medium, 2=large) + /// 0=LAND_LEVEL, 1=LAND_RAISE, 2=LAND_LOWER, 3=LAND_SMOOTH, 4=LAND_NOISE, 5=LAND_REVERT + /// UUID of script-owner + public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId) + { + client_OnModifyTerrain(user, (float)pos.Z, (float)0.25, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId); + } + + /// + /// Saves the current heightmap to a specified stream. + /// + /// The destination filename. Used here only to identify the image type + /// + public void SaveToStream(string filename, Stream stream) + { + try + { + foreach (KeyValuePair loader in m_loaders) + { + if (filename.EndsWith(loader.Key)) + { + loader.Value.SaveStream(stream, m_channel); + return; + } + } + } + catch (NotImplementedException) + { + m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented."); + throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented")); + } + } + + #region Plugin Loading Methods + + private void LoadPlugins() + { + m_plugineffects = new Dictionary(); + // Load the files in the Terrain/ dir + string[] files = Directory.GetFiles("Terrain"); + foreach (string file in files) + { + m_log.Info("Loading effects in " + file); + try + { + Assembly library = Assembly.LoadFrom(file); + foreach (Type pluginType in library.GetTypes()) + { + try + { + if (pluginType.IsAbstract || pluginType.IsNotPublic) + continue; + + string typeName = pluginType.Name; + + if (pluginType.GetInterface("ITerrainEffect", false) != null) + { + ITerrainEffect terEffect = (ITerrainEffect) Activator.CreateInstance(library.GetType(pluginType.ToString())); + + InstallPlugin(typeName, terEffect); + } + else if (pluginType.GetInterface("ITerrainLoader", false) != null) + { + ITerrainLoader terLoader = (ITerrainLoader) Activator.CreateInstance(library.GetType(pluginType.ToString())); + m_loaders[terLoader.FileExtension] = terLoader; + m_log.Info("L ... " + typeName); + } + } + catch (AmbiguousMatchException) + { + } + } + } + catch (BadImageFormatException) + { + } + } + } + + public void InstallPlugin(string pluginName, ITerrainEffect effect) + { + lock (m_plugineffects) + { + if (!m_plugineffects.ContainsKey(pluginName)) + { + m_plugineffects.Add(pluginName, effect); + m_log.Info("E ... " + pluginName); + } + else + { + m_plugineffects[pluginName] = effect; + m_log.Warn("E ... " + pluginName + " (Replaced)"); + } + } + } + + #endregion + + #endregion + + /// + /// Installs into terrain module the standard suite of brushes + /// + private void InstallDefaultEffects() + { + // Draggable Paint Brush Effects + m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere(); + m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere(); + m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere(); + m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere(); + m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere(); + m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_revert); + m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere(); + m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere(); + m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere(); + + // Area of effect selection effects + m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea(); + m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea(); + m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea(); + m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea(); + m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea(); + m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert); + + // Filesystem load/save loaders + m_loaders[".r32"] = new RAW32(); + m_loaders[".f32"] = m_loaders[".r32"]; + m_loaders[".ter"] = new Terragen(); + m_loaders[".raw"] = new LLRAW(); + m_loaders[".jpg"] = new JPEG(); + m_loaders[".jpeg"] = m_loaders[".jpg"]; + m_loaders[".bmp"] = new BMP(); + m_loaders[".png"] = new PNG(); + m_loaders[".gif"] = new GIF(); + m_loaders[".tif"] = new TIFF(); + m_loaders[".tiff"] = m_loaders[".tif"]; + } + + /// + /// Saves the current state of the region into the revert map buffer. + /// + public void UpdateRevertMap() + { + int x; + for (x = 0; x < m_channel.Width; x++) + { + int y; + for (y = 0; y < m_channel.Height; y++) + { + m_revert[x, y] = m_channel[x, y]; + } + } + } + + /// + /// Loads a tile from a larger terrain file and installs it into the region. + /// + /// The terrain file to load + /// The width of the file in units + /// The height of the file in units + /// Where to begin our slice + /// Where to begin our slice + public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY) + { + int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX; + int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY; + + if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight) + { + // this region is included in the tile request + foreach (KeyValuePair loader in m_loaders) + { + if (filename.EndsWith(loader.Key)) + { + lock (m_scene) + { + ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY, + fileWidth, fileHeight, + (int) Constants.RegionSize, + (int) Constants.RegionSize); + m_scene.Heightmap = channel; + m_channel = channel; + UpdateRevertMap(); + } + return; + } + } + } + } + + /// + /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections + /// + private void EventManager_OnTerrainTick() + { + if (m_tainted) + { + m_tainted = false; + m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised()); + m_scene.SaveTerrain(); + + // Clients who look at the map will never see changes after they looked at the map, so i've commented this out. + //m_scene.CreateTerrainTexture(true); + } + } + + /// + /// Processes commandline input. Do not call directly. + /// + /// Commandline arguments + private void EventManager_OnPluginConsole(string[] args) + { + if (args[0] == "terrain") + { + if (args.Length == 1) + { + m_commander.ProcessConsoleCommand("help", new string[0]); + return; + } + + string[] tmpArgs = new string[args.Length - 2]; + int i; + for (i = 2; i < args.Length; i++) + tmpArgs[i - 2] = args[i]; + + m_commander.ProcessConsoleCommand(args[1], tmpArgs); + } + } + + /// + /// Installs terrain brush hook to IClientAPI + /// + /// + private void EventManager_OnNewClient(IClientAPI client) + { + client.OnModifyTerrain += client_OnModifyTerrain; + client.OnBakeTerrain += client_OnBakeTerrain; + } + + /// + /// Checks to see if the terrain has been modified since last check + /// but won't attempt to limit those changes to the limits specified in the estate settings + /// currently invoked by the command line operations in the region server only + /// + private void CheckForTerrainUpdates() + { + CheckForTerrainUpdates(false); + } + + /// + /// Checks to see if the terrain has been modified since last check. + /// If it has been modified, every all the terrain patches are sent to the client. + /// If the call is asked to respect the estate settings for terrain_raise_limit and + /// terrain_lower_limit, it will clamp terrain updates between these values + /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces + /// should height map deltas be limited to the estate settings limits + /// + private void CheckForTerrainUpdates(bool respectEstateSettings) + { + bool shouldTaint = false; + float[] serialised = m_channel.GetFloatsSerialised(); + int x; + for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize) + { + int y; + for (y = 0; y < m_channel.Height; y += Constants.TerrainPatchSize) + { + if (m_channel.Tainted(x, y)) + { + // if we should respect the estate settings then + // fixup and height deltas that don't respect them + if (respectEstateSettings && LimitChannelChanges(x, y)) + { + // this has been vetoed, so update + // what we are going to send to the client + serialised = m_channel.GetFloatsSerialised(); + } + + SendToClients(serialised, x, y); + shouldTaint = true; + } + } + } + if (shouldTaint) + { + m_tainted = true; + } + } + + /// + /// Checks to see height deltas in the tainted terrain patch at xStart ,yStart + /// are all within the current estate limits + /// true if changes were limited, false otherwise + /// + private bool LimitChannelChanges(int xStart, int yStart) + { + bool changesLimited = false; + double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; + double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; + + // loop through the height map for this patch and compare it against + // the revert map + for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++) + { + for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++) + { + + double requestedHeight = m_channel[x, y]; + double bakedHeight = m_revert[x, y]; + double requestedDelta = requestedHeight - bakedHeight; + + if (requestedDelta > maxDelta) + { + m_channel[x, y] = bakedHeight + maxDelta; + changesLimited = true; + } + else if (requestedDelta < minDelta) + { + m_channel[x, y] = bakedHeight + minDelta; //as lower is a -ve delta + changesLimited = true; + } + } + } + + return changesLimited; + } + + /// + /// Sends a copy of the current terrain to the scenes clients + /// + /// A copy of the terrain as a 1D float array of size w*h + /// The patch corner to send + /// The patch corner to send + private void SendToClients(float[] serialised, int x, int y) + { + m_scene.ForEachClient( + delegate(IClientAPI controller) + { controller.SendLayerData( + x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised); + } + ); + } + + private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action, + float north, float west, float south, float east, UUID agentId) + { + bool god = m_scene.Permissions.IsGod(user); + bool allowed = false; + if (north == south && east == west) + { + if (m_painteffects.ContainsKey((StandardTerrainEffects) action)) + { + bool[,] allowMask = new bool[m_channel.Width,m_channel.Height]; + allowMask.Initialize(); + int n = size + 1; + if (n > 2) + n = 4; + + int zx = (int) (west + 0.5); + int zy = (int) (north + 0.5); + + int dx; + for (dx=-n; dx<=n; dx++) + { + int dy; + for (dy=-n; dy<=n; dy++) + { + int x = zx + dx; + int y = zy + dy; + if (x>=0 && y>=0 && x west) + { + if (y < north && y > south) + { + if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0))) + { + fillArea[x, y] = true; + allowed = true; + } + } + } + } + } + + if (allowed) + { + m_floodeffects[(StandardTerrainEffects) action].FloodEffect( + m_channel, fillArea, size); + + CheckForTerrainUpdates(!god); //revert changes outside estate limits + } + } + else + { + m_log.Debug("Unknown terrain flood type " + action); + } + } + } + + private void client_OnBakeTerrain(IClientAPI remoteClient) + { + // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area. + // for now check a point in the centre of the region + + if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true)) + { + InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter + } + } + + #region Console Commands + + private void InterfaceLoadFile(Object[] args) + { + LoadFromFile((string) args[0]); + CheckForTerrainUpdates(); + } + + private void InterfaceLoadTileFile(Object[] args) + { + LoadFromFile((string) args[0], + (int) args[1], + (int) args[2], + (int) args[3], + (int) args[4]); + CheckForTerrainUpdates(); + } + + private void InterfaceSaveFile(Object[] args) + { + SaveToFile((string) args[0]); + } + + private void InterfaceBakeTerrain(Object[] args) + { + UpdateRevertMap(); + } + + private void InterfaceRevertTerrain(Object[] args) + { + int x, y; + for (x = 0; x < m_channel.Width; x++) + for (y = 0; y < m_channel.Height; y++) + m_channel[x, y] = m_revert[x, y]; + + CheckForTerrainUpdates(); + } + + private void InterfaceFlipTerrain(Object[] args) + { + String direction = (String)args[0]; + + if (direction.ToLower().StartsWith("y")) + { + for (int x = 0; x < Constants.RegionSize; x++) + { + for (int y = 0; y < Constants.RegionSize / 2; y++) + { + double height = m_channel[x, y]; + double flippedHeight = m_channel[x, (int)Constants.RegionSize - 1 - y]; + m_channel[x, y] = flippedHeight; + m_channel[x, (int)Constants.RegionSize - 1 - y] = height; + + } + } + } + else if (direction.ToLower().StartsWith("x")) + { + for (int y = 0; y < Constants.RegionSize; y++) + { + for (int x = 0; x < Constants.RegionSize / 2; x++) + { + double height = m_channel[x, y]; + double flippedHeight = m_channel[(int)Constants.RegionSize - 1 - x, y]; + m_channel[x, y] = flippedHeight; + m_channel[(int)Constants.RegionSize - 1 - x, y] = height; + + } + } + } + else + { + m_log.Error("Unrecognised direction - need x or y"); + } + + + CheckForTerrainUpdates(); + } + + private void InterfaceElevateTerrain(Object[] args) + { + int x, y; + for (x = 0; x < m_channel.Width; x++) + for (y = 0; y < m_channel.Height; y++) + m_channel[x, y] += (double) args[0]; + CheckForTerrainUpdates(); + } + + private void InterfaceMultiplyTerrain(Object[] args) + { + int x, y; + for (x = 0; x < m_channel.Width; x++) + for (y = 0; y < m_channel.Height; y++) + m_channel[x, y] *= (double) args[0]; + CheckForTerrainUpdates(); + } + + private void InterfaceLowerTerrain(Object[] args) + { + int x, y; + for (x = 0; x < m_channel.Width; x++) + for (y = 0; y < m_channel.Height; y++) + m_channel[x, y] -= (double) args[0]; + CheckForTerrainUpdates(); + } + + private void InterfaceFillTerrain(Object[] args) + { + int x, y; + + for (x = 0; x < m_channel.Width; x++) + for (y = 0; y < m_channel.Height; y++) + m_channel[x, y] = (double) args[0]; + CheckForTerrainUpdates(); + } + + private void InterfaceShowDebugStats(Object[] args) + { + double max = Double.MinValue; + double min = double.MaxValue; + double sum = 0; + + int x; + for (x = 0; x < m_channel.Width; x++) + { + int y; + for (y = 0; y < m_channel.Height; y++) + { + sum += m_channel[x, y]; + if (max < m_channel[x, y]) + max = m_channel[x, y]; + if (min > m_channel[x, y]) + min = m_channel[x, y]; + } + } + + double avg = sum / (m_channel.Height * m_channel.Width); + + m_log.Info("Channel " + m_channel.Width + "x" + m_channel.Height); + m_log.Info("max/min/avg/sum: " + max + "/" + min + "/" + avg + "/" + sum); + } + + private void InterfaceEnableExperimentalBrushes(Object[] args) + { + if ((bool) args[0]) + { + m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere(); + m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere(); + m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere(); + } + else + { + InstallDefaultEffects(); + } + } + + private void InterfaceRunPluginEffect(Object[] args) + { + if ((string) args[0] == "list") + { + m_log.Info("List of loaded plugins"); + foreach (KeyValuePair kvp in m_plugineffects) + { + m_log.Info(kvp.Key); + } + return; + } + if ((string) args[0] == "reload") + { + LoadPlugins(); + return; + } + if (m_plugineffects.ContainsKey((string) args[0])) + { + m_plugineffects[(string) args[0]].RunEffect(m_channel); + CheckForTerrainUpdates(); + } + else + { + m_log.Warn("No such plugin effect loaded."); + } + } + + private void InstallInterfaces() + { + // Load / Save + string supportedFileExtensions = ""; + foreach (KeyValuePair loader in m_loaders) + supportedFileExtensions += " " + loader.Key + " (" + loader.Value + ")"; + + Command loadFromFileCommand = + new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file."); + loadFromFileCommand.AddArgument("filename", + "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + + supportedFileExtensions, "String"); + + Command saveToFileCommand = + new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file."); + saveToFileCommand.AddArgument("filename", + "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " + + supportedFileExtensions, "String"); + + Command loadFromTileCommand = + new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file."); + loadFromTileCommand.AddArgument("filename", + "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + + supportedFileExtensions, "String"); + loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer"); + loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer"); + loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file", + "Integer"); + loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file", + "Integer"); + + // Terrain adjustments + Command fillRegionCommand = + new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value."); + fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.", + "Double"); + + Command elevateCommand = + new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount."); + elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double"); + + Command lowerCommand = + new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount."); + lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double"); + + Command multiplyCommand = + new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified."); + multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double"); + + Command bakeRegionCommand = + new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map."); + Command revertRegionCommand = + new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap."); + + Command flipCommand = + new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis"); + flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String"); + + // Debug + Command showDebugStatsCommand = + new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats, + "Shows some information about the regions heightmap for debugging purposes."); + + Command experimentalBrushesCommand = + new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes, + "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time."); + experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean"); + + //Plugins + Command pluginRunCommand = + new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect"); + pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String"); + + m_commander.RegisterCommand("load", loadFromFileCommand); + m_commander.RegisterCommand("load-tile", loadFromTileCommand); + m_commander.RegisterCommand("save", saveToFileCommand); + m_commander.RegisterCommand("fill", fillRegionCommand); + m_commander.RegisterCommand("elevate", elevateCommand); + m_commander.RegisterCommand("lower", lowerCommand); + m_commander.RegisterCommand("multiply", multiplyCommand); + m_commander.RegisterCommand("bake", bakeRegionCommand); + m_commander.RegisterCommand("revert", revertRegionCommand); + m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand); + m_commander.RegisterCommand("stats", showDebugStatsCommand); + m_commander.RegisterCommand("effect", pluginRunCommand); + m_commander.RegisterCommand("flip", flipCommand); + + // Add this to our scene so scripts can call these functions + m_scene.RegisterModuleCommander(m_commander); + } + + #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs new file mode 100644 index 0000000..e7f92d7 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Terrain/Tests/TerrainTest.cs @@ -0,0 +1,118 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; + +namespace OpenSim.Region.CoreModules.World.Terrain.Tests +{ + [TestFixture] + public class TerrainTest + { + [Test] + public void BrushTest() + { + bool[,] allowMask = new bool[256, 256]; + int x; + int y; + for (x=0; x<128; x++) + { + for (y=0; y<256; y++) + { + allowMask[x,y] = true; + } + } + + // + // Test RaiseSphere + // + TerrainChannel map = new TerrainChannel(256, 256); + ITerrainPaintableEffect effect = new RaiseSphere(); + + effect.PaintEffect(map, allowMask, 128.0, 128.0, -1.0, 2, 0.1); + Assert.That(map[127, 128] > 0.0, "Raise brush should raising value at this point (127,128)."); + Assert.That(map[124, 128] > 0.0, "Raise brush should raising value at this point (124,128)."); + Assert.That(map[123, 128] == 0.0, "Raise brush should not change value at this point (123,128)."); + Assert.That(map[128, 128] == 0.0, "Raise brush should not change value at this point (128,128)."); + Assert.That(map[0, 128] == 0.0, "Raise brush should not change value at this point (0,128)."); + + // + // Test LowerSphere + // + map = new TerrainChannel(256, 256); + for (x=0; x= 0.0, "Lower should not lowering value below 0.0 at this point (127,128)."); + Assert.That(map[127, 128] == 0.0, "Lower brush should lowering value to 0.0 at this point (127,128)."); + Assert.That(map[124, 128] < 1.0, "Lower brush should lowering value at this point (124,128)."); + Assert.That(map[123, 128] == 1.0, "Lower brush should not change value at this point (123,128)."); + Assert.That(map[128, 128] == 1.0, "Lower brush should not change value at this point (128,128)."); + Assert.That(map[0, 128] == 1.0, "Lower brush should not change value at this point (0,128)."); + } + + [Test] + public void TerrainChannelTest() + { + TerrainChannel x = new TerrainChannel(256, 256); + Assert.That(x[0, 0] == 0.0, "Terrain not initialising correctly."); + + x[0, 0] = 1.0; + Assert.That(x[0, 0] == 1.0, "Terrain not setting values correctly."); + + x[0, 0] = 0; + x[0, 0] += 5.0; + x[0, 0] -= 1.0; + Assert.That(x[0, 0] == 4.0, "Terrain addition/subtraction error."); + + x[0, 0] = Math.PI; + double[,] doublesExport = x.GetDoubles(); + Assert.That(doublesExport[0, 0] == Math.PI, "Export to double[,] array not working correctly."); + + x[0, 0] = 1.0; + float[] floatsExport = x.GetFloatsSerialised(); + Assert.That(floatsExport[0] == 1.0f, "Export to float[] not working correctly."); + + x[0, 0] = 1.0; + Assert.That(x.Tainted(0, 0), "Terrain channel tainting not working correctly."); + Assert.That(!x.Tainted(0, 0), "Terrain channel tainting not working correctly."); + + TerrainChannel y = x.Copy(); + Assert.That(!ReferenceEquals(x, y), "Terrain copy not duplicating correctly."); + Assert.That(!ReferenceEquals(x.GetDoubles(), y.GetDoubles()), "Terrain array not duplicating correctly."); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Vegetation/VegetationModule.cs b/OpenSim/Region/CoreModules/World/Vegetation/VegetationModule.cs new file mode 100644 index 0000000..8b2bb1e --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Vegetation/VegetationModule.cs @@ -0,0 +1,118 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Reflection; +using log4net; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Communications; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatar.Vegetation +{ + public class VegetationModule : IRegionModule, IVegetationModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Scene m_scene; + + protected static readonly PCode[] creationCapabilities = new PCode[] { PCode.Grass, PCode.NewTree, PCode.Tree }; + public PCode[] CreationCapabilities { get { return creationCapabilities; } } + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() {} + public void Close() {} + public string Name { get { return "Vegetation Module"; } } + public bool IsSharedModule { get { return false; } } + + public SceneObjectGroup AddTree( + UUID uuid, UUID groupID, Vector3 scale, Quaternion rotation, Vector3 position, Tree treeType, bool newTree) + { + PrimitiveBaseShape treeShape = new PrimitiveBaseShape(); + treeShape.PathCurve = 16; + treeShape.PathEnd = 49900; + treeShape.PCode = newTree ? (byte)PCode.NewTree : (byte)PCode.Tree; + treeShape.Scale = scale; + treeShape.State = (byte)treeType; + + return m_scene.AddNewPrim(uuid, groupID, position, rotation, treeShape); + } + + public SceneObjectGroup CreateEntity( + UUID ownerID, UUID groupID, Vector3 pos, Quaternion rot, PrimitiveBaseShape shape) + { + if (Array.IndexOf(creationCapabilities, (PCode)shape.PCode) < 0) + { + m_log.DebugFormat("[VEGETATION]: PCode {0} not handled by {1}", shape.PCode, Name); + return null; + } + + SceneObjectGroup sceneObject = new SceneObjectGroup(ownerID, pos, rot, shape); + SceneObjectPart rootPart = sceneObject.GetChildPart(sceneObject.UUID); + + // if grass or tree, make phantom + //rootPart.TrimPermissions(); + rootPart.AddFlag(PrimFlags.Phantom); + if (rootPart.Shape.PCode != (byte)PCode.Grass) + AdaptTree(ref shape); + + m_scene.AddNewSceneObject(sceneObject, true); + sceneObject.SetGroup(groupID, null); + + return sceneObject; + } + + protected void AdaptTree(ref PrimitiveBaseShape tree) + { + // Tree size has to be adapted depending on its type + switch ((Tree)tree.State) + { + case Tree.Cypress1: + case Tree.Cypress2: + tree.Scale = new Vector3(4, 4, 10); + break; + + // case... other tree types + // tree.Scale = new Vector3(?, ?, ?); + // break; + + default: + tree.Scale = new Vector3(4, 4, 4); + break; + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/Wind/WindModule.cs b/OpenSim/Region/CoreModules/World/Wind/WindModule.cs new file mode 100644 index 0000000..b8bd605 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/Wind/WindModule.cs @@ -0,0 +1,207 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules +{ + public class WindModule : IWindModule + { +// private static readonly log4net.ILog m_log +// = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private int m_frame = 0; + private int m_frame_mod = 150; + private Random rndnums = new Random(System.Environment.TickCount); + private Scene m_scene = null; + private bool ready = false; + private Vector2[] windSpeeds = new Vector2[16 * 16]; + private Dictionary m_rootAgents = new Dictionary(); + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + m_frame = 0; + + scene.EventManager.OnFrame += WindUpdate; + scene.EventManager.OnMakeChildAgent += MakeChildAgent; + scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; + scene.EventManager.OnClientClosed += ClientLoggedOut; + scene.RegisterModuleInterface(this); + + GenWindPos(); + + ready = true; + } + + public void PostInitialise() + { + } + + public void Close() + { + ready = false; + // Remove our hooks + m_scene.EventManager.OnFrame -= WindUpdate; + // m_scene.EventManager.OnNewClient -= SunToClient; + m_scene.EventManager.OnMakeChildAgent -= MakeChildAgent; + m_scene.EventManager.OnAvatarEnteringNewParcel -= AvatarEnteringParcel; + m_scene.EventManager.OnClientClosed -= ClientLoggedOut; + } + + public string Name + { + get { return "WindModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + public Vector2[] WindSpeeds + { + get { return windSpeeds; } + } + + public void WindToClient(IClientAPI client) + { + if (ready) + { + //if (!sunFixed) + //GenWindPos(); // Generate shared values once + client.SendWindData(windSpeeds); + } + } + + public void WindUpdate() + { + if (((m_frame++ % m_frame_mod) != 0) || !ready) + { + return; + } + //m_log.Debug("[WIND]:Regenerating..."); + GenWindPos(); // Generate shared values once + + //int spotxp = 0; + //int spotyp = 0; + //int spotxm = 0; + //int spotym = 0; + List avatars = m_scene.GetAvatars(); + foreach (ScenePresence avatar in avatars) + { + if (!avatar.IsChildAgent) + { + avatar.ControllingClient.SendWindData(windSpeeds); + } + } + + // set estate settings for region access to sun position + //m_scene.RegionInfo.RegionSettings.SunVector = Position; + //m_scene.RegionInfo.EstateSettings.sunHour = GetLindenEstateHourFromCurrentTime(); + } + + public void ForceWindUpdateToAllClients() + { + GenWindPos(); // Generate shared values once + + List avatars = m_scene.GetAvatars(); + foreach (ScenePresence avatar in avatars) + { + if (!avatar.IsChildAgent) + avatar.ControllingClient.SendWindData(windSpeeds); + } + + // set estate settings for region access to sun position + //m_scene.RegionInfo.RegionSettings.SunVector = Position; + //m_scene.RegionInfo.RegionSettings.SunPosition = GetLindenEstateHourFromCurrentTime(); + } + /// + /// Calculate the sun's orbital position and its velocity. + /// + + private void GenWindPos() + { + for (int y = 0; y < 16; y++) + { + for (int x = 0; x < 16; x++) + { + windSpeeds[y * 16 + x].X = (float)(rndnums.NextDouble() * 2d - 1d); + windSpeeds[y * 16 + x].Y = (float)(rndnums.NextDouble() * 2d - 1d); + } + } + } + + private void ClientLoggedOut(UUID AgentId) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(AgentId)) + { + m_rootAgents.Remove(AgentId); + } + } + } + + private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + m_rootAgents[avatar.UUID] = avatar.RegionHandle; + } + else + { + m_rootAgents.Add(avatar.UUID, avatar.RegionHandle); + WindToClient(avatar.ControllingClient); + } + } + //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); + } + + private void MakeChildAgent(ScenePresence avatar) + { + lock (m_rootAgents) + { + if (m_rootAgents.ContainsKey(avatar.UUID)) + { + if (m_rootAgents[avatar.UUID] == avatar.RegionHandle) + { + m_rootAgents.Remove(avatar.UUID); + } + } + } + } + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/IMapTileTerrainRenderer.cs b/OpenSim/Region/CoreModules/World/WorldMap/IMapTileTerrainRenderer.cs new file mode 100644 index 0000000..3684df0 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/IMapTileTerrainRenderer.cs @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Drawing; +using OpenSim.Region.Framework.Scenes; +using Nini.Config; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + public interface IMapTileTerrainRenderer + { + void Initialise(Scene scene, IConfigSource config); + void TerrainToBitmap(Bitmap mapbmp); + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/MapImageModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/MapImageModule.cs new file mode 100644 index 0000000..eea6cf6 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/MapImageModule.cs @@ -0,0 +1,586 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using Nini.Config; +using OpenMetaverse.Imaging; +using log4net; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenMetaverse; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + public enum DrawRoutine + { + Rectangle, + Polygon, + Ellipse + } + + public struct face + { + public Point[] pts; + } + + public struct DrawStruct + { + public DrawRoutine dr; + public Rectangle rect; + public SolidBrush brush; + public face[] trns; + } + + public class MapImageModule : IMapImageGenerator, IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + private IConfigSource m_config; + private IMapTileTerrainRenderer terrainRenderer; + + #region IMapImageGenerator Members + + public byte[] WriteJpeg2000Image(string gradientmap) + { + byte[] imageData = null; + + bool drawPrimVolume = true; + bool textureTerrain = true; + + try + { + IConfig startupConfig = m_config.Configs["Startup"]; + drawPrimVolume = startupConfig.GetBoolean("DrawPrimOnMapTile", drawPrimVolume); + textureTerrain = startupConfig.GetBoolean("TextureOnMapTile", textureTerrain); + } + catch + { + m_log.Warn("[MAPTILE]: Failed to load StartupConfig"); + } + + if (textureTerrain) + { + terrainRenderer = new TexturedMapTileRenderer(); + } + else + { + terrainRenderer = new ShadedMapTileRenderer(); + } + terrainRenderer.Initialise(m_scene, m_config); + + Bitmap mapbmp = new Bitmap(256, 256); + //long t = System.Environment.TickCount; + //for (int i = 0; i < 10; ++i) { + terrainRenderer.TerrainToBitmap(mapbmp); + //} + //t = System.Environment.TickCount - t; + //m_log.InfoFormat("[MAPTILE] generation of 10 maptiles needed {0} ms", t); + + + if (drawPrimVolume) + { + DrawObjectVolume(m_scene, mapbmp); + } + + try + { + imageData = OpenJPEG.EncodeFromImage(mapbmp, true); + } + catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke + { + Console.WriteLine("Failed generating terrain map: " + e); + } + + return imageData; + } + + #endregion + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + m_config = source; + + IConfig startupConfig = m_config.Configs["Startup"]; + if (startupConfig.GetString("MapImageModule", "MapImageModule") != + "MapImageModule") + return; + + m_scene.RegisterModuleInterface(this); + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public string Name + { + get { return "MapImageModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + +// TODO: unused: +// private void ShadeBuildings(Bitmap map) +// { +// lock (map) +// { +// lock (m_scene.Entities) +// { +// foreach (EntityBase entity in m_scene.Entities.Values) +// { +// if (entity is SceneObjectGroup) +// { +// SceneObjectGroup sog = (SceneObjectGroup) entity; +// +// foreach (SceneObjectPart primitive in sog.Children.Values) +// { +// int x = (int) (primitive.AbsolutePosition.X - (primitive.Scale.X / 2)); +// int y = (int) (primitive.AbsolutePosition.Y - (primitive.Scale.Y / 2)); +// int w = (int) primitive.Scale.X; +// int h = (int) primitive.Scale.Y; +// +// int dx; +// for (dx = x; dx < x + w; dx++) +// { +// int dy; +// for (dy = y; dy < y + h; dy++) +// { +// if (x < 0 || y < 0) +// continue; +// if (x >= map.Width || y >= map.Height) +// continue; +// +// map.SetPixel(dx, dy, Color.DarkGray); +// } +// } +// } +// } +// } +// } +// } +// } + + private Bitmap DrawObjectVolume(Scene whichScene, Bitmap mapbmp) + { + int tc = 0; + double[,] hm = whichScene.Heightmap.GetDoubles(); + tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile"); + List objs = whichScene.GetEntities(); + Dictionary z_sort = new Dictionary(); + //SortedList z_sort = new SortedList(); + List z_sortheights = new List(); + List z_localIDs = new List(); + + lock (objs) + { + foreach (EntityBase obj in objs) + { + // Only draw the contents of SceneObjectGroup + if (obj is SceneObjectGroup) + { + SceneObjectGroup mapdot = (SceneObjectGroup)obj; + Color mapdotspot = Color.Gray; // Default color when prim color is white + // Loop over prim in group + foreach (SceneObjectPart part in mapdot.Children.Values) + { + if (part == null) + continue; + + // Draw if the object is at least 1 meter wide in any direction + if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f) + { + // Try to get the RGBA of the default texture entry.. + // + try + { + // get the null checks out of the way + // skip the ones that break + if (part == null) + continue; + + if (part.Shape == null) + continue; + + if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass) + continue; // eliminates trees from this since we don't really have a good tree representation + // if you want tree blocks on the map comment the above line and uncomment the below line + //mapdotspot = Color.PaleGreen; + + if (part.Shape.Textures == null) + continue; + + if (part.Shape.Textures.DefaultTexture == null) + continue; + + Color4 texcolor = part.Shape.Textures.DefaultTexture.RGBA; + + // Not sure why some of these are null, oh well. + + int colorr = 255 - (int)(texcolor.R * 255f); + int colorg = 255 - (int)(texcolor.G * 255f); + int colorb = 255 - (int)(texcolor.B * 255f); + + if (!(colorr == 255 && colorg == 255 && colorb == 255)) + { + //Try to set the map spot color + try + { + // If the color gets goofy somehow, skip it *shakes fist at Color4 + mapdotspot = Color.FromArgb(colorr, colorg, colorb); + } + catch (ArgumentException) + { + } + } + } + catch (IndexOutOfRangeException) + { + // Windows Array + } + catch (ArgumentOutOfRangeException) + { + // Mono Array + } + + Vector3 pos = part.GetWorldPosition(); + + // skip prim outside of retion + if (pos.X < 0f || pos.X > 256f || pos.Y < 0f || pos.Y > 256f) + continue; + + // skip prim in non-finite position + if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) || + Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y)) + continue; + + // Figure out if object is under 256m above the height of the terrain + bool isBelow256AboveTerrain = false; + + try + { + isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f)); + } + catch (Exception) + { + } + + if (isBelow256AboveTerrain) + { + // Translate scale by rotation so scale is represented properly when object is rotated + Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z); + Vector3 scale = new Vector3(); + Vector3 tScale = new Vector3(); + Vector3 axPos = new Vector3(pos.X,pos.Y,pos.Z); + + Quaternion llrot = part.GetWorldRotation(); + Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z); + scale = lscale * rot; + + // negative scales don't work in this situation + scale.X = Math.Abs(scale.X); + scale.Y = Math.Abs(scale.Y); + scale.Z = Math.Abs(scale.Z); + + // This scaling isn't very accurate and doesn't take into account the face rotation :P + int mapdrawstartX = (int)(pos.X - scale.X); + int mapdrawstartY = (int)(pos.Y - scale.Y); + int mapdrawendX = (int)(pos.X + scale.X); + int mapdrawendY = (int)(pos.Y + scale.Y); + + // If object is beyond the edge of the map, don't draw it to avoid errors + if (mapdrawstartX < 0 || mapdrawstartX > 255 || mapdrawendX < 0 || mapdrawendX > 255 + || mapdrawstartY < 0 || mapdrawstartY > 255 || mapdrawendY < 0 + || mapdrawendY > 255) + continue; + +#region obb face reconstruction part duex + Vector3[] vertexes = new Vector3[8]; + + // float[] distance = new float[6]; + Vector3[] FaceA = new Vector3[6]; // vertex A for Facei + Vector3[] FaceB = new Vector3[6]; // vertex B for Facei + Vector3[] FaceC = new Vector3[6]; // vertex C for Facei + Vector3[] FaceD = new Vector3[6]; // vertex D for Facei + + tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + // vertexes[0].x = pos.X + vertexes[0].x; + //vertexes[0].y = pos.Y + vertexes[0].y; + //vertexes[0].z = pos.Z + vertexes[0].z; + + FaceA[0] = vertexes[0]; + FaceB[3] = vertexes[0]; + FaceA[4] = vertexes[0]; + + tScale = lscale; + scale = ((tScale * rot)); + vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[1].x = pos.X + vertexes[1].x; + // vertexes[1].y = pos.Y + vertexes[1].y; + //vertexes[1].z = pos.Z + vertexes[1].z; + + FaceB[0] = vertexes[1]; + FaceA[1] = vertexes[1]; + FaceC[4] = vertexes[1]; + + tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + + vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[2].x = pos.X + vertexes[2].x; + //vertexes[2].y = pos.Y + vertexes[2].y; + //vertexes[2].z = pos.Z + vertexes[2].z; + + FaceC[0] = vertexes[2]; + FaceD[3] = vertexes[2]; + FaceC[5] = vertexes[2]; + + tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + //vertexes[3].x = pos.X + vertexes[3].x; + // vertexes[3].y = pos.Y + vertexes[3].y; + // vertexes[3].z = pos.Z + vertexes[3].z; + + FaceD[0] = vertexes[3]; + FaceC[1] = vertexes[3]; + FaceA[5] = vertexes[3]; + + tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[4].x = pos.X + vertexes[4].x; + // vertexes[4].y = pos.Y + vertexes[4].y; + // vertexes[4].z = pos.Z + vertexes[4].z; + + FaceB[1] = vertexes[4]; + FaceA[2] = vertexes[4]; + FaceD[4] = vertexes[4]; + + tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[5].x = pos.X + vertexes[5].x; + // vertexes[5].y = pos.Y + vertexes[5].y; + // vertexes[5].z = pos.Z + vertexes[5].z; + + FaceD[1] = vertexes[5]; + FaceC[2] = vertexes[5]; + FaceB[5] = vertexes[5]; + + tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z); + scale = ((tScale * rot)); + vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[6].x = pos.X + vertexes[6].x; + // vertexes[6].y = pos.Y + vertexes[6].y; + // vertexes[6].z = pos.Z + vertexes[6].z; + + FaceB[2] = vertexes[6]; + FaceA[3] = vertexes[6]; + FaceB[4] = vertexes[6]; + + tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z); + scale = ((tScale * rot)); + vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z))); + + // vertexes[7].x = pos.X + vertexes[7].x; + // vertexes[7].y = pos.Y + vertexes[7].y; + // vertexes[7].z = pos.Z + vertexes[7].z; + + FaceD[2] = vertexes[7]; + FaceC[3] = vertexes[7]; + FaceD[5] = vertexes[7]; +#endregion + + //int wy = 0; + + //bool breakYN = false; // If we run into an error drawing, break out of the + // loop so we don't lag to death on error handling + DrawStruct ds = new DrawStruct(); + ds.brush = new SolidBrush(mapdotspot); + //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY); + + ds.trns = new face[FaceA.Length]; + + for (int i = 0; i < FaceA.Length; i++) + { + Point[] working = new Point[5]; + working[0] = project(FaceA[i], axPos); + working[1] = project(FaceB[i], axPos); + working[2] = project(FaceD[i], axPos); + working[3] = project(FaceC[i], axPos); + working[4] = project(FaceA[i], axPos); + + face workingface = new face(); + workingface.pts = working; + + ds.trns[i] = workingface; + } + + z_sort.Add(part.LocalId, ds); + z_localIDs.Add(part.LocalId); + z_sortheights.Add(pos.Z); + + //for (int wx = mapdrawstartX; wx < mapdrawendX; wx++) + //{ + //for (wy = mapdrawstartY; wy < mapdrawendY; wy++) + //{ + //m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy); + //try + //{ + // Remember, flip the y! + // mapbmp.SetPixel(wx, (255 - wy), mapdotspot); + //} + //catch (ArgumentException) + //{ + // breakYN = true; + //} + + //if (breakYN) + // break; + //} + + //if (breakYN) + // break; + //} + } // Object is within 256m Z of terrain + } // object is at least a meter wide + } // loop over group children + } // entitybase is sceneobject group + } // foreach loop over entities + + float[] sortedZHeights = z_sortheights.ToArray(); + uint[] sortedlocalIds = z_localIDs.ToArray(); + + // Sort prim by Z position + Array.Sort(sortedZHeights, sortedlocalIds); + + Graphics g = Graphics.FromImage(mapbmp); + + for (int s = 0; s < sortedZHeights.Length; s++) + { + if (z_sort.ContainsKey(sortedlocalIds[s])) + { + DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]]; + for (int r = 0; r < rectDrawStruct.trns.Length; r++ ) + { + g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts); + } + //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect); + } + } + + g.Dispose(); + } // lock entities objs + + m_log.Info("[MAPTILE]: Generating Maptile Step 2: Done in " + (System.Environment.TickCount - tc) + " ms"); + return mapbmp; + } + + private Point project(Vector3 point3d, Vector3 originpos) + { + Point returnpt = new Point(); + //originpos = point3d; + //int d = (int)(256f / 1.5f); + + //Vector3 topos = new Vector3(0, 0, 0); + // float z = -point3d.z - topos.z; + + returnpt.X = (int)point3d.X;//(int)((topos.x - point3d.x) / z * d); + returnpt.Y = (int)(255 - point3d.Y);//(int)(255 - (((topos.y - point3d.y) / z * d))); + + return returnpt; + } + +// TODO: unused: +// #region Deprecated Maptile Generation. Adam may update this +// private Bitmap TerrainToBitmap(string gradientmap) +// { +// Bitmap gradientmapLd = new Bitmap(gradientmap); +// +// int pallete = gradientmapLd.Height; +// +// Bitmap bmp = new Bitmap(m_scene.Heightmap.Width, m_scene.Heightmap.Height); +// Color[] colours = new Color[pallete]; +// +// for (int i = 0; i < pallete; i++) +// { +// colours[i] = gradientmapLd.GetPixel(0, i); +// } +// +// lock (m_scene.Heightmap) +// { +// ITerrainChannel copy = m_scene.Heightmap; +// for (int y = 0; y < copy.Height; y++) +// { +// for (int x = 0; x < copy.Width; x++) +// { +// // 512 is the largest possible height before colours clamp +// int colorindex = (int) (Math.Max(Math.Min(1.0, copy[x, y] / 512.0), 0.0) * (pallete - 1)); +// +// // Handle error conditions +// if (colorindex > pallete - 1 || colorindex < 0) +// bmp.SetPixel(x, copy.Height - y - 1, Color.Red); +// else +// bmp.SetPixel(x, copy.Height - y - 1, colours[colorindex]); +// } +// } +// ShadeBuildings(bmp); +// return bmp; +// } +// } +// #endregion + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs new file mode 100644 index 0000000..45a99a9 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs @@ -0,0 +1,172 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Net; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Hypergrid; +using OpenMetaverse; +using log4net; +using Nini.Config; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + public class MapSearchModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + Scene m_scene = null; // only need one for communication with GridService + List m_scenes = new List(); + + #region IRegionModule Members + public void Initialise(Scene scene, IConfigSource source) + { + if (m_scene == null) + { + m_scene = scene; + } + + m_scenes.Add(scene); + scene.EventManager.OnNewClient += OnNewClient; + } + + public void PostInitialise() + { + } + + public void Close() + { + m_scene = null; + m_scenes.Clear(); + } + + public string Name + { + get { return "MapSearchModule"; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + private void OnNewClient(IClientAPI client) + { + client.OnMapNameRequest += OnMapNameRequest; + } + + private void OnMapNameRequest(IClientAPI remoteClient, string mapName) + { + if (mapName.Length < 3) + { + remoteClient.SendAlertMessage("Use a search string with at least 3 characters"); + return; + } + + // try to fetch from GridServer + List regionInfos = m_scene.SceneGridService.RequestNamedRegions(mapName, 20); + if (regionInfos == null) + { + m_log.Warn("[MAPSEARCHMODULE]: RequestNamedRegions returned null. Old gridserver?"); + // service wasn't available; maybe still an old GridServer. Try the old API, though it will return only one region + regionInfos = new List(); + RegionInfo info = m_scene.SceneGridService.RequestClosestRegion(mapName); + if (info != null) regionInfos.Add(info); + } + + if ((regionInfos.Count == 0) && IsHypergridOn()) + { + // OK, we tried but there are no regions matching that name. + // Let's check quickly if this is a domain name, and if so link to it + if (mapName.Contains(".") && mapName.Contains(":")) + { + // It probably is a domain name. Try to link to it. + RegionInfo regInfo; + Scene cScene = GetClientScene(remoteClient); + regInfo = HGHyperlink.TryLinkRegion(cScene, remoteClient, mapName); + if (regInfo != null) + regionInfos.Add(regInfo); + } + } + + List blocks = new List(); + + MapBlockData data; + if (regionInfos.Count > 0) + { + foreach (RegionInfo info in regionInfos) + { + data = new MapBlockData(); + data.Agents = 0; + data.Access = 21; // TODO what's this? + data.MapImageId = info.RegionSettings.TerrainImageID; + data.Name = info.RegionName; + data.RegionFlags = 0; // TODO not used? + data.WaterHeight = 0; // not used + data.X = (ushort)info.RegionLocX; + data.Y = (ushort)info.RegionLocY; + blocks.Add(data); + } + } + + // final block, closing the search result + data = new MapBlockData(); + data.Agents = 0; + data.Access = 255; + data.MapImageId = UUID.Zero; + data.Name = mapName; + data.RegionFlags = 0; + data.WaterHeight = 0; // not used + data.X = 0; + data.Y = 0; + blocks.Add(data); + + remoteClient.SendMapBlock(blocks, 0); + } + + private bool IsHypergridOn() + { + return (m_scene.SceneGridService is HGSceneCommunicationService); + } + + private Scene GetClientScene(IClientAPI client) + { + foreach (Scene s in m_scenes) + { + if (client.Scene.RegionInfo.RegionHandle == s.RegionInfo.RegionHandle) + return s; + } + return m_scene; + } + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/ShadedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/WorldMap/ShadedMapTileRenderer.cs new file mode 100644 index 0000000..b783d7c --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/ShadedMapTileRenderer.cs @@ -0,0 +1,249 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using Nini.Config; +using log4net; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + public class ShadedMapTileRenderer : IMapTileTerrainRenderer + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private Scene m_scene; + //private IConfigSource m_config; // not used currently + + public void Initialise(Scene scene, IConfigSource config) + { + m_scene = scene; + // m_config = config; // not used currently + } + + public void TerrainToBitmap(Bitmap mapbmp) + { + int tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + + double[,] hm = m_scene.Heightmap.GetDoubles(); + bool ShadowDebugContinue = true; + + bool terraincorruptedwarningsaid = false; + + float low = 255; + float high = 0; + for (int x = 0; x < 256; x++) + { + for (int y = 0; y < 256; y++) + { + float hmval = (float)hm[x, y]; + if (hmval < low) + low = hmval; + if (hmval > high) + high = hmval; + } + } + + float waterHeight = (float)m_scene.RegionInfo.RegionSettings.WaterHeight; + + for (int x = 0; x < 256; x++) + { + for (int y = 0; y < 256; y++) + { + // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left + int yr = 255 - y; + + float heightvalue = (float)hm[x, y]; + + if (heightvalue > waterHeight) + { + // scale height value + // No, that doesn't scale it: + // heightvalue = low + mid * (heightvalue - low) / mid; => low + (heightvalue - low) * mid / mid = low + (heightvalue - low) * 1 = low + heightvalue - low = heightvalue + + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0; + else if (heightvalue > 255f) + heightvalue = 255f; + else if (heightvalue < 0f) + heightvalue = 0f; + + Color color = Color.FromArgb((int)heightvalue, 100, (int)heightvalue); + + mapbmp.SetPixel(x, yr, color); + + try + { + //X + // . + // + // Shade the terrain for shadows + if (x < 255 && yr < 255) + { + float hfvalue = (float)hm[x, y]; + float hfvaluecompare = 0f; + + if ((x + 1 < 256) && (y + 1 < 256)) + { + hfvaluecompare = (float)hm[x + 1, y + 1]; // light from north-east => look at land height there + } + if (Single.IsInfinity(hfvalue) || Single.IsNaN(hfvalue)) + hfvalue = 0f; + + if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) + hfvaluecompare = 0f; + + float hfdiff = hfvalue - hfvaluecompare; // => positive if NE is lower, negative if here is lower + + int hfdiffi = 0; + int hfdiffihighlight = 0; + float highlightfactor = 0.18f; + + try + { + // hfdiffi = Math.Abs((int)((hfdiff * 4) + (hfdiff * 0.5))) + 1; + hfdiffi = Math.Abs((int)(hfdiff * 4.5f)) + 1; + if (hfdiff % 1f != 0) + { + // hfdiffi = hfdiffi + Math.Abs((int)(((hfdiff % 1) * 0.5f) * 10f) - 1); + hfdiffi = hfdiffi + Math.Abs((int)((hfdiff % 1f) * 5f) - 1); + } + + hfdiffihighlight = Math.Abs((int)((hfdiff * highlightfactor) * 4.5f)) + 1; + if (hfdiff % 1f != 0) + { + // hfdiffi = hfdiffi + Math.Abs((int)(((hfdiff % 1) * 0.5f) * 10f) - 1); + hfdiffihighlight = hfdiffihighlight + Math.Abs((int)(((hfdiff * highlightfactor) % 1f) * 5f) - 1); + } + } + catch (System.OverflowException) + { + m_log.Debug("[MAPTILE]: Shadow failed at value: " + hfdiff.ToString()); + ShadowDebugContinue = false; + } + + if (hfdiff > 0.3f) + { + // NE is lower than here + // We have to desaturate and lighten the land at the same time + // we use floats, colors use bytes, so shrink are space down to + // 0-255 + + if (ShadowDebugContinue) + { + int r = color.R; + int g = color.G; + int b = color.B; + color = Color.FromArgb((r + hfdiffihighlight < 255) ? r + hfdiffihighlight : 255, + (g + hfdiffihighlight < 255) ? g + hfdiffihighlight : 255, + (b + hfdiffihighlight < 255) ? b + hfdiffihighlight : 255); + } + } + else if (hfdiff < -0.3f) + { + // here is lower than NE: + // We have to desaturate and blacken the land at the same time + // we use floats, colors use bytes, so shrink are space down to + // 0-255 + + if (ShadowDebugContinue) + { + if ((x - 1 > 0) && (yr + 1 < 256)) + { + color = mapbmp.GetPixel(x - 1, yr + 1); + int r = color.R; + int g = color.G; + int b = color.B; + color = Color.FromArgb((r - hfdiffi > 0) ? r - hfdiffi : 0, + (g - hfdiffi > 0) ? g - hfdiffi : 0, + (b - hfdiffi > 0) ? b - hfdiffi : 0); + + mapbmp.SetPixel(x-1, yr+1, color); + } + } + } + } + } + catch (System.ArgumentException) + { + if (!terraincorruptedwarningsaid) + { + m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + terraincorruptedwarningsaid = true; + } + color = Color.Black; + mapbmp.SetPixel(x, yr, color); + } + } + else + { + // We're under the water level with the terrain, so paint water instead of land + + // Y flip the cordinates + heightvalue = waterHeight - heightvalue; + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0f; + else if (heightvalue > 19f) + heightvalue = 19f; + else if (heightvalue < 0f) + heightvalue = 0f; + + heightvalue = 100f - (heightvalue * 100f) / 19f; + + try + { + Color water = Color.FromArgb((int)heightvalue, (int)heightvalue, 255); + mapbmp.SetPixel(x, yr, water); + } + catch (System.ArgumentException) + { + if (!terraincorruptedwarningsaid) + { + m_log.WarnFormat("[MAPIMAGE]: Your terrain is corrupted in region {0}, it might take a few minutes to generate the map image depending on the corruption level", m_scene.RegionInfo.RegionName); + terraincorruptedwarningsaid = true; + } + Color black = Color.Black; + mapbmp.SetPixel(x, (256 - y) - 1, black); + } + } + } + } + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (System.Environment.TickCount - tc) + " ms"); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/TexturedMapTileRenderer.cs b/OpenSim/Region/CoreModules/World/WorldMap/TexturedMapTileRenderer.cs new file mode 100644 index 0000000..537644a --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/TexturedMapTileRenderer.cs @@ -0,0 +1,411 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using OpenMetaverse; +using Nini.Config; +using log4net; +using OpenMetaverse.Imaging; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Terrain; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + // Hue, Saturation, Value; used for color-interpolation + struct HSV { + public float h; + public float s; + public float v; + + public HSV(float h, float s, float v) + { + this.h = h; + this.s = s; + this.v = v; + } + + // (for info about algorithm, see http://en.wikipedia.org/wiki/HSL_and_HSV) + public HSV(Color c) + { + float r = c.R / 255f; + float g = c.G / 255f; + float b = c.B / 255f; + float max = Math.Max(Math.Max(r, g), b); + float min = Math.Min(Math.Min(r, g), b); + float diff = max - min; + + if (max == min) h = 0f; + else if (max == r) h = (g - b) / diff * 60f; + else if (max == g) h = (b - r) / diff * 60f + 120f; + else h = (r - g) / diff * 60f + 240f; + if (h < 0f) h += 360f; + + if (max == 0f) s = 0f; + else s = diff / max; + + v = max; + } + + // (for info about algorithm, see http://en.wikipedia.org/wiki/HSL_and_HSV) + public Color toColor() + { + if (s < 0f) Console.WriteLine("S < 0: " + s); + else if (s > 1f) Console.WriteLine("S > 1: " + s); + if (v < 0f) Console.WriteLine("V < 0: " + v); + else if (v > 1f) Console.WriteLine("V > 1: " + v); + + float f = h / 60f; + int sector = (int)f % 6; + f = f - (int)f; + int pi = (int)(v * (1f - s) * 255f); + int qi = (int)(v * (1f - s * f) * 255f); + int ti = (int)(v * (1f - (1f - f) * s) * 255f); + int vi = (int)(v * 255f); + + switch (sector) + { + case 0: + return Color.FromArgb(vi, ti, pi); + case 1: + return Color.FromArgb(qi, vi, pi); + case 2: + return Color.FromArgb(pi, vi, ti); + case 3: + return Color.FromArgb(pi, qi, vi); + case 4: + return Color.FromArgb(ti, pi, vi); + default: + return Color.FromArgb(vi, pi, qi); + } + } + } + + public class TexturedMapTileRenderer : IMapTileTerrainRenderer + { + #region Constants + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + // some hardcoded terrain UUIDs that work with SL 1.20 (the four default textures and "Blank"). + // The color-values were choosen because they "look right" (at least to me) ;-) + private static readonly UUID defaultTerrainTexture1 = new UUID("0bc58228-74a0-7e83-89bc-5c23464bcec5"); + private static readonly Color defaultColor1 = Color.FromArgb(165, 137, 118); + private static readonly UUID defaultTerrainTexture2 = new UUID("63338ede-0037-c4fd-855b-015d77112fc8"); + private static readonly Color defaultColor2 = Color.FromArgb(69, 89, 49); + private static readonly UUID defaultTerrainTexture3 = new UUID("303cd381-8560-7579-23f1-f0a880799740"); + private static readonly Color defaultColor3 = Color.FromArgb(162, 154, 141); + private static readonly UUID defaultTerrainTexture4 = new UUID("53a2f406-4895-1d13-d541-d2e3b86bc19c"); + private static readonly Color defaultColor4 = Color.FromArgb(200, 200, 200); + + #endregion + + + private Scene m_scene; + // private IConfigSource m_config; // not used currently + + // mapping from texture UUIDs to averaged color. This will contain 5-9 values, in general; new values are only + // added when the terrain textures are changed in the estate dialog and a new map is generated (and will stay in + // that map until the region-server restarts. This could be considered a memory-leak, but it's a *very* small one. + // TODO does it make sense to use a "real" cache and regenerate missing entries on fetch? + private Dictionary m_mapping; + + + public void Initialise(Scene scene, IConfigSource source) + { + m_scene = scene; + // m_config = source; // not used currently + m_mapping = new Dictionary(); + m_mapping.Add(defaultTerrainTexture1, defaultColor1); + m_mapping.Add(defaultTerrainTexture2, defaultColor2); + m_mapping.Add(defaultTerrainTexture3, defaultColor3); + m_mapping.Add(defaultTerrainTexture4, defaultColor4); + m_mapping.Add(Util.BLANK_TEXTURE_UUID, Color.White); + } + + #region Helpers + // This fetches the texture from the asset server synchroneously. That should be ok, as we + // call map-creation only in those places: + // - on start: We can wait here until the asset server returns the texture + // TODO (- on "map" command: We are in the command-line thread, we will wait for completion anyway) + // TODO (- on "automatic" update after some change: We are called from the mapUpdateTimer here and + // will wait anyway) + private Bitmap fetchTexture(UUID id) + { + AssetBase asset = m_scene.AssetCache.GetAsset(id, true); + m_log.DebugFormat("Fetched texture {0}, found: {1}", id, asset != null); + if (asset == null) return null; + + ManagedImage managedImage; + Image image; + + try + { + if (OpenJPEG.DecodeToImage(asset.Data, out managedImage, out image)) + return new Bitmap(image); + else + return null; + } + catch (DllNotFoundException) + { + m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg is not installed correctly on this system. Asset Data is emtpy for {0}", id); + + } + catch (IndexOutOfRangeException) + { + m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is emtpy for {0}", id); + + } + catch (Exception) + { + m_log.ErrorFormat("[TexturedMapTileRenderer]: OpenJpeg was unable to encode this. Asset Data is emtpy for {0}", id); + + } + return null; + + } + + // Compute the average color of a texture. + private Color computeAverageColor(Bitmap bmp) + { + // we have 256 x 256 pixel, each with 256 possible color-values per + // color-channel, so 2^24 is the maximum value we can get, adding everything. + // int is be big enough for that. + int r = 0, g = 0, b = 0; + for (int y = 0; y < bmp.Height; ++y) + { + for (int x = 0; x < bmp.Width; ++x) + { + Color c = bmp.GetPixel(x, y); + r += (int)c.R & 0xff; + g += (int)c.G & 0xff; + b += (int)c.B & 0xff; + } + } + + int pixels = bmp.Width * bmp.Height; + return Color.FromArgb(r / pixels, g / pixels, b / pixels); + } + + // return either the average color of the texture, or the defaultColor if the texturID is invalid + // or the texture couldn't be found + private Color computeAverageColor(UUID textureID, Color defaultColor) { + if (textureID == UUID.Zero) return defaultColor; // not set + if (m_mapping.ContainsKey(textureID)) return m_mapping[textureID]; // one of the predefined textures + + Bitmap bmp = fetchTexture(textureID); + Color color = bmp == null ? defaultColor : computeAverageColor(bmp); + // store it for future reference + m_mapping[textureID] = color; + + return color; + } + + // S-curve: f(x) = 3x² - 2x³: + // f(0) = 0, f(0.5) = 0.5, f(1) = 1, + // f'(x) = 0 at x = 0 and x = 1; f'(0.5) = 1.5, + // f''(0.5) = 0, f''(x) != 0 for x != 0.5 + private float S(float v) { + return (v * v * (3f - 2f * v)); + } + + // interpolate two colors in HSV space and return the resulting color + private HSV interpolateHSV(ref HSV c1, ref HSV c2, float ratio) { + if (ratio <= 0f) return c1; + if (ratio >= 1f) return c2; + + // make sure we are on the same side on the hue-circle for interpolation + // We change the hue of the parameters here, but we don't change the color + // represented by that value + if (c1.h - c2.h > 180f) c1.h -= 360f; + else if (c2.h - c1.h > 180f) c1.h += 360f; + + return new HSV(c1.h * (1f - ratio) + c2.h * ratio, + c1.s * (1f - ratio) + c2.s * ratio, + c1.v * (1f - ratio) + c2.v * ratio); + } + + // the heigthfield might have some jumps in values. Rendered land is smooth, though, + // as a slope is rendered at that place. So average 4 neighbour values to emulate that. + private float getHeight(double[,] hm, int x, int y) { + if (x < 255 && y < 255) + return (float)(hm[x, y] * .444 + (hm[x + 1, y] + hm[x, y + 1]) * .222 + hm[x + 1, y +1] * .112); + else + return (float)hm[x, y]; + } + #endregion + + public void TerrainToBitmap(Bitmap mapbmp) + { + int tc = System.Environment.TickCount; + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Terrain"); + + // These textures should be in the AssetCache anyway, as every client conneting to this + // region needs them. Except on start, when the map is recreated (before anyone connected), + // and on change of the estate settings (textures and terrain values), when the map should + // be recreated. + RegionSettings settings = m_scene.RegionInfo.RegionSettings; + + // the four terrain colors as HSVs for interpolation + HSV hsv1 = new HSV(computeAverageColor(settings.TerrainTexture1, defaultColor1)); + HSV hsv2 = new HSV(computeAverageColor(settings.TerrainTexture2, defaultColor2)); + HSV hsv3 = new HSV(computeAverageColor(settings.TerrainTexture3, defaultColor3)); + HSV hsv4 = new HSV(computeAverageColor(settings.TerrainTexture4, defaultColor4)); + + float levelNElow = (float)settings.Elevation1NE; + float levelNEhigh = (float)settings.Elevation2NE; + + float levelNWlow = (float)settings.Elevation1NW; + float levelNWhigh = (float)settings.Elevation2NW; + + float levelSElow = (float)settings.Elevation1SE; + float levelSEhigh = (float)settings.Elevation2SE; + + float levelSWlow = (float)settings.Elevation1SW; + float levelSWhigh = (float)settings.Elevation2SW; + + float waterHeight = (float)settings.WaterHeight; + + double[,] hm = m_scene.Heightmap.GetDoubles(); + + for (int x = 0; x < 256; x++) + { + float columnRatio = x / 255f; // 0 - 1, for interpolation + for (int y = 0; y < 256; y++) + { + float rowRatio = y / 255f; // 0 - 1, for interpolation + + // Y flip the cordinates for the bitmap: hf origin is lower left, bm origin is upper left + int yr = 255 - y; + + float heightvalue = getHeight(hm, x, y); + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0; + + if (heightvalue > waterHeight) + { + // add a bit noise for breaking up those flat colors: + // - a large-scale noise, for the "patches" (using an doubled s-curve for sharper contrast) + // - a small-scale noise, for bringing in some small scale variation + //float bigNoise = (float)TerrainUtil.InterpolatedNoise(x / 8.0, y / 8.0) * .5f + .5f; // map to 0.0 - 1.0 + //float smallNoise = (float)TerrainUtil.InterpolatedNoise(x + 33, y + 43) * .5f + .5f; + //float hmod = heightvalue + smallNoise * 3f + S(S(bigNoise)) * 10f; + float hmod = + heightvalue + + (float)TerrainUtil.InterpolatedNoise(x + 33, y + 43) * 1.5f + 1.5f + // 0 - 3 + S(S((float)TerrainUtil.InterpolatedNoise(x / 8.0, y / 8.0) * .5f + .5f)) * 10f; // 0 - 10 + + // find the low/high values for this point (interpolated bilinearily) + // (and remember, x=0,y=0 is SW) + float low = levelSWlow * (1f - rowRatio) * (1f - columnRatio) + + levelSElow * (1f - rowRatio) * columnRatio + + levelNWlow * rowRatio * (1f - columnRatio) + + levelNElow * rowRatio * columnRatio; + float high = levelSWhigh * (1f - rowRatio) * (1f - columnRatio) + + levelSEhigh * (1f - rowRatio) * columnRatio + + levelNWhigh * rowRatio * (1f - columnRatio) + + levelNEhigh * rowRatio * columnRatio; + if (high < low) + { + // someone tried to fool us. High value should be higher than low every time + float tmp = high; + high = low; + low = tmp; + } + + HSV hsv; + if (hmod <= low) hsv = hsv1; // too low + else if (hmod >= high) hsv = hsv4; // too high + else + { + // HSV-interpolate along the colors + // first, rescale h to 0.0 - 1.0 + hmod = (hmod - low) / (high - low); + // now we have to split: 0.00 => color1, 0.33 => color2, 0.67 => color3, 1.00 => color4 + if (hmod < 1f/3f) hsv = interpolateHSV(ref hsv1, ref hsv2, hmod * 3f); + else if (hmod < 2f/3f) hsv = interpolateHSV(ref hsv2, ref hsv3, (hmod * 3f) - 1f); + else hsv = interpolateHSV(ref hsv3, ref hsv4, (hmod * 3f) - 2f); + } + + // Shade the terrain for shadows + if (x < 255 && y < 255) + { + float hfvaluecompare = getHeight(hm, x + 1, y + 1); // light from north-east => look at land height there + if (Single.IsInfinity(hfvaluecompare) || Single.IsNaN(hfvaluecompare)) + hfvaluecompare = 0f; + + float hfdiff = heightvalue - hfvaluecompare; // => positive if NE is lower, negative if here is lower + hfdiff *= 0.06f; // some random factor so "it looks good" + if (hfdiff > 0.02f) + { + float highlightfactor = 0.18f; + // NE is lower than here + // We have to desaturate and lighten the land at the same time + hsv.s = (hsv.s - (hfdiff * highlightfactor) > 0f) ? hsv.s - (hfdiff * highlightfactor) : 0f; + hsv.v = (hsv.v + (hfdiff * highlightfactor) < 1f) ? hsv.v + (hfdiff * highlightfactor) : 1f; + } + else if (hfdiff < -0.02f) + { + // here is lower than NE: + // We have to desaturate and blacken the land at the same time + hsv.s = (hsv.s + hfdiff > 0f) ? hsv.s + hfdiff : 0f; + hsv.v = (hsv.v + hfdiff > 0f) ? hsv.v + hfdiff : 0f; + } + } + mapbmp.SetPixel(x, yr, hsv.toColor()); + } + else + { + // We're under the water level with the terrain, so paint water instead of land + + heightvalue = waterHeight - heightvalue; + if (Single.IsInfinity(heightvalue) || Single.IsNaN(heightvalue)) + heightvalue = 0f; + else if (heightvalue > 19f) + heightvalue = 19f; + else if (heightvalue < 0f) + heightvalue = 0f; + + heightvalue = 100f - (heightvalue * 100f) / 19f; // 0 - 19 => 100 - 0 + + Color water = Color.FromArgb((int)heightvalue, (int)heightvalue, 255); + mapbmp.SetPixel(x, yr, water); + } + } + } + m_log.Info("[MAPTILE]: Generating Maptile Step 1: Done in " + (System.Environment.TickCount - tc) + " ms"); + } + } +} diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs new file mode 100644 index 0000000..376e365 --- /dev/null +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -0,0 +1,905 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Net; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using OpenMetaverse.Imaging; +using OpenMetaverse.StructuredData; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Framework.Communications.Cache; +using OpenSim.Framework.Communications.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Types; +using Caps = OpenSim.Framework.Communications.Capabilities.Caps; + +using OSD = OpenMetaverse.StructuredData.OSD; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; + +namespace OpenSim.Region.CoreModules.World.WorldMap +{ + public class WorldMapModule : IRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private static readonly string m_mapLayerPath = "0001/"; + + private OpenSim.Framework.BlockingQueue requests = new OpenSim.Framework.BlockingQueue(); + + //private IConfig m_config; + protected Scene m_scene; + private List cachedMapBlocks = new List(); + private int cachedTime = 0; + private byte[] myMapImageJPEG; + protected bool m_Enabled = false; + private Dictionary m_openRequests = new Dictionary(); + private Dictionary m_blacklistedurls = new Dictionary(); + private Dictionary m_blacklistedregions = new Dictionary(); + private Dictionary m_cachedRegionMapItemsAddress = new Dictionary(); + private List m_rootAgents = new List(); + private Thread mapItemReqThread; + private volatile bool threadrunning = false; + + //private int CacheRegionsDistance = 256; + + #region IRegionModule Members + + public virtual void Initialise(Scene scene, IConfigSource config) + { + IConfig startupConfig = config.Configs["Startup"]; + if (startupConfig.GetString("WorldMapModule", "WorldMap") == + "WorldMap") + m_Enabled = true; + + if (!m_Enabled) + return; + + m_scene = scene; + } + + public virtual void PostInitialise() + { + if (m_Enabled) + AddHandlers(); + } + + public virtual void Close() + { + } + + public virtual string Name + { + get { return "WorldMapModule"; } + } + + public bool IsSharedModule + { + get { return false; } + } + + #endregion + + protected virtual void AddHandlers() + { + myMapImageJPEG = new byte[0]; + + string regionimage = "regionImage" + m_scene.RegionInfo.RegionID.ToString(); + regionimage = regionimage.Replace("-", ""); + m_log.Info("[WORLD MAP]: JPEG Map location: http://" + m_scene.RegionInfo.ExternalEndPoint.Address.ToString() + ":" + m_scene.RegionInfo.HttpPort.ToString() + "/index.php?method=" + regionimage); + + m_scene.CommsManager.HttpServer.AddHTTPHandler(regionimage, OnHTTPGetMapImage); + m_scene.CommsManager.HttpServer.AddLLSDHandler( + "/MAP/MapItems/" + m_scene.RegionInfo.RegionHandle.ToString(), HandleRemoteMapItemRequest); + + m_scene.EventManager.OnRegisterCaps += OnRegisterCaps; + m_scene.EventManager.OnNewClient += OnNewClient; + m_scene.EventManager.OnClientClosed += ClientLoggedOut; + m_scene.EventManager.OnMakeChildAgent += MakeChildAgent; + m_scene.EventManager.OnMakeRootAgent += MakeRootAgent; + } + + public void OnRegisterCaps(UUID agentID, Caps caps) + { + //m_log.DebugFormat("[WORLD MAP]: OnRegisterCaps: agentID {0} caps {1}", agentID, caps); + string capsBase = "/CAPS/" + caps.CapsObjectPath; + caps.RegisterHandler("MapLayer", + new RestStreamHandler("POST", capsBase + m_mapLayerPath, + delegate(string request, string path, string param, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + return MapLayerRequest(request, path, param, + agentID, caps); + })); + } + + /// + /// Callback for a map layer request + /// + /// + /// + /// + /// + /// + /// + public string MapLayerRequest(string request, string path, string param, + UUID agentID, Caps caps) + { + //try + //{ + //m_log.DebugFormat("[MAPLAYER]: request: {0}, path: {1}, param: {2}, agent:{3}", + //request, path, param,agentID.ToString()); + + // this is here because CAPS map requests work even beyond the 10,000 limit. + ScenePresence avatarPresence = null; + + m_scene.TryGetAvatar(agentID, out avatarPresence); + + if (avatarPresence != null) + { + bool lookup = false; + + lock (cachedMapBlocks) + { + if (cachedMapBlocks.Count > 0 && ((cachedTime + 1800) > Util.UnixTimeSinceEpoch())) + { + List mapBlocks; + + mapBlocks = cachedMapBlocks; + avatarPresence.ControllingClient.SendMapBlock(mapBlocks, 0); + } + else + { + lookup = true; + } + } + if (lookup) + { + List mapBlocks; + + mapBlocks = m_scene.SceneGridService.RequestNeighbourMapBlocks((int)m_scene.RegionInfo.RegionLocX - 8, (int)m_scene.RegionInfo.RegionLocY - 8, (int)m_scene.RegionInfo.RegionLocX + 8, (int)m_scene.RegionInfo.RegionLocY + 8); + avatarPresence.ControllingClient.SendMapBlock(mapBlocks,0); + + lock (cachedMapBlocks) + cachedMapBlocks = mapBlocks; + + cachedTime = Util.UnixTimeSinceEpoch(); + } + } + LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse(); + mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse()); + return mapResponse.ToString(); + } + + /// + /// + /// + /// + /// + public LLSDMapLayerResponse GetMapLayer(LLSDMapRequest mapReq) + { + m_log.Debug("[WORLD MAP]: MapLayer Request in region: " + m_scene.RegionInfo.RegionName); + LLSDMapLayerResponse mapResponse = new LLSDMapLayerResponse(); + mapResponse.LayerData.Array.Add(GetOSDMapLayerResponse()); + return mapResponse; + } + + /// + /// + /// + /// + protected static OSDMapLayer GetOSDMapLayerResponse() + { + OSDMapLayer mapLayer = new OSDMapLayer(); + mapLayer.Right = 5000; + mapLayer.Top = 5000; + mapLayer.ImageID = new UUID("00000000-0000-1111-9999-000000000006"); + + return mapLayer; + } + #region EventHandlers + + /// + /// Registered for event + /// + /// + private void OnNewClient(IClientAPI client) + { + client.OnRequestMapBlocks += RequestMapBlocks; + client.OnMapItemRequest += HandleMapItemRequest; + } + + /// + /// Client logged out, check to see if there are any more root agents in the simulator + /// If not, stop the mapItemRequest Thread + /// Event handler + /// + /// AgentID that logged out + private void ClientLoggedOut(UUID AgentId) + { + List presences = m_scene.GetAvatars(); + int rootcount = 0; + for (int i=0;i + /// Starts the MapItemRequest Thread + /// Note that this only gets started when there are actually agents in the region + /// Additionally, it gets stopped when there are none. + /// + /// + private void StartThread(object o) + { + if (threadrunning) return; + threadrunning = true; + m_log.Debug("[WORLD MAP]: Starting remote MapItem request thread"); + mapItemReqThread = new Thread(new ThreadStart(process)); + mapItemReqThread.IsBackground = true; + mapItemReqThread.Name = "MapItemRequestThread"; + mapItemReqThread.Priority = ThreadPriority.BelowNormal; + mapItemReqThread.SetApartmentState(ApartmentState.MTA); + mapItemReqThread.Start(); + ThreadTracker.Add(mapItemReqThread); + } + + /// + /// Enqueues a 'stop thread' MapRequestState. Causes the MapItemRequest thread to end + /// + private void StopThread() + { + MapRequestState st = new MapRequestState(); + st.agentID=UUID.Zero; + st.EstateID=0; + st.flags=0; + st.godlike=false; + st.itemtype=0; + st.regionhandle=0; + + requests.Enqueue(st); + } + + public virtual void HandleMapItemRequest(IClientAPI remoteClient, uint flags, + uint EstateID, bool godlike, uint itemtype, ulong regionhandle) + { + lock (m_rootAgents) + { + if (!m_rootAgents.Contains(remoteClient.AgentId)) + return; + } + uint xstart = 0; + uint ystart = 0; + Utils.LongToUInts(m_scene.RegionInfo.RegionHandle, out xstart, out ystart); + if (itemtype == 6) // we only sevice 6 right now (avatar green dots) + { + if (regionhandle == 0 || regionhandle == m_scene.RegionInfo.RegionHandle) + { + // Local Map Item Request + List avatars = m_scene.GetAvatars(); + int tc = System.Environment.TickCount; + List mapitems = new List(); + mapItemReply mapitem = new mapItemReply(); + if (avatars.Count == 0 || avatars.Count == 1) + { + mapitem = new mapItemReply(); + mapitem.x = (uint)(xstart + 1); + mapitem.y = (uint)(ystart + 1); + mapitem.id = UUID.Zero; + mapitem.name = Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()); + mapitem.Extra = 0; + mapitem.Extra2 = 0; + mapitems.Add(mapitem); + } + else + { + foreach (ScenePresence av in avatars) + { + // Don't send a green dot for yourself + if (av.UUID != remoteClient.AgentId) + { + mapitem = new mapItemReply(); + mapitem.x = (uint)(xstart + av.AbsolutePosition.X); + mapitem.y = (uint)(ystart + av.AbsolutePosition.Y); + mapitem.id = UUID.Zero; + mapitem.name = Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString()); + mapitem.Extra = 1; + mapitem.Extra2 = 0; + mapitems.Add(mapitem); + } + } + } + remoteClient.SendMapItemReply(mapitems.ToArray(), itemtype, flags); + } + else + { + // Remote Map Item Request + + // ensures that the blockingqueue doesn't get borked if the GetAgents() timing changes. + // Note that we only start up a remote mapItem Request thread if there's users who could + // be making requests + if (!threadrunning) + { + m_log.Warn("[WORLD MAP]: Starting new remote request thread manually. This means that AvatarEnteringParcel never fired! This needs to be fixed! Don't Mantis this, as the developers can see it in this message"); + StartThread(new object()); + } + + RequestMapItems("",remoteClient.AgentId,flags,EstateID,godlike,itemtype,regionhandle); + } + } + } + + /// + /// Processing thread main() loop for doing remote mapitem requests + /// + public void process() + { + try + { + while (true) + { + MapRequestState st = requests.Dequeue(); + + // end gracefully + if (st.agentID == UUID.Zero) + { + ThreadTracker.Remove(mapItemReqThread); + break; + } + + bool dorequest = true; + lock (m_rootAgents) + { + if (!m_rootAgents.Contains(st.agentID)) + dorequest = false; + } + + if (dorequest) + { + OSDMap response = RequestMapItemsAsync("", st.agentID, st.flags, st.EstateID, st.godlike, st.itemtype, st.regionhandle); + RequestMapItemsCompleted(response); + } + } + } + catch (Exception e) + { + m_log.ErrorFormat("[WORLD MAP]: Map item request thread terminated abnormally with exception {0}", e); + } + + threadrunning = false; + } + + /// + /// Enqueues the map item request into the processing thread + /// + /// + public void EnqueueMapItemRequest(MapRequestState state) + { + requests.Enqueue(state); + } + + /// + /// Sends the mapitem response to the IClientAPI + /// + /// The OSDMap Response for the mapitem + private void RequestMapItemsCompleted(OSDMap response) + { + UUID requestID = response["requestID"].AsUUID(); + + if (requestID != UUID.Zero) + { + MapRequestState mrs = new MapRequestState(); + mrs.agentID = UUID.Zero; + lock (m_openRequests) + { + if (m_openRequests.ContainsKey(requestID)) + { + mrs = m_openRequests[requestID]; + m_openRequests.Remove(requestID); + } + } + + if (mrs.agentID != UUID.Zero) + { + ScenePresence av = null; + m_scene.TryGetAvatar(mrs.agentID, out av); + if (av != null) + { + if (response.ContainsKey(mrs.itemtype.ToString())) + { + List returnitems = new List(); + OSDArray itemarray = (OSDArray)response[mrs.itemtype.ToString()]; + for (int i = 0; i < itemarray.Count; i++) + { + OSDMap mapitem = (OSDMap)itemarray[i]; + mapItemReply mi = new mapItemReply(); + mi.x = (uint)mapitem["X"].AsInteger(); + mi.y = (uint)mapitem["Y"].AsInteger(); + mi.id = mapitem["ID"].AsUUID(); + mi.Extra = mapitem["Extra"].AsInteger(); + mi.Extra2 = mapitem["Extra2"].AsInteger(); + mi.name = mapitem["Name"].AsString(); + returnitems.Add(mi); + } + av.ControllingClient.SendMapItemReply(returnitems.ToArray(), mrs.itemtype, mrs.flags); + } + } + } + } + } + + /// + /// Enqueue the MapItem request for remote processing + /// + /// blank string, we discover this in the process + /// Agent ID that we are making this request on behalf + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// Region we're looking up + public void RequestMapItems(string httpserver, UUID id, uint flags, + uint EstateID, bool godlike, uint itemtype, ulong regionhandle) + { + MapRequestState st = new MapRequestState(); + st.agentID = id; + st.flags = flags; + st.EstateID = EstateID; + st.godlike = godlike; + st.itemtype = itemtype; + st.regionhandle = regionhandle; + EnqueueMapItemRequest(st); + } + + /// + /// Does the actual remote mapitem request + /// This should be called from an asynchronous thread + /// Request failures get blacklisted until region restart so we don't + /// continue to spend resources trying to contact regions that are down. + /// + /// blank string, we discover this in the process + /// Agent ID that we are making this request on behalf + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// passed in from packet + /// Region we're looking up + /// + private OSDMap RequestMapItemsAsync(string httpserver, UUID id, uint flags, + uint EstateID, bool godlike, uint itemtype, ulong regionhandle) + { + bool blacklisted = false; + lock (m_blacklistedregions) + { + if (m_blacklistedregions.ContainsKey(regionhandle)) + blacklisted = true; + } + + if (blacklisted) + return new OSDMap(); + + UUID requestID = UUID.Random(); + lock (m_cachedRegionMapItemsAddress) + { + if (m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) + httpserver = m_cachedRegionMapItemsAddress[regionhandle]; + } + if (httpserver.Length == 0) + { + RegionInfo mreg = m_scene.SceneGridService.RequestNeighbouringRegionInfo(regionhandle); + + if (mreg != null) + { + httpserver = "http://" + mreg.ExternalEndPoint.Address.ToString() + ":" + mreg.HttpPort + "/MAP/MapItems/" + regionhandle.ToString(); + lock (m_cachedRegionMapItemsAddress) + { + if (!m_cachedRegionMapItemsAddress.ContainsKey(regionhandle)) + m_cachedRegionMapItemsAddress.Add(regionhandle, httpserver); + } + } + else + { + lock (m_blacklistedregions) + { + if (!m_blacklistedregions.ContainsKey(regionhandle)) + m_blacklistedregions.Add(regionhandle, System.Environment.TickCount); + } + m_log.InfoFormat("[WORLD MAP]: Blacklisted region {0}", regionhandle.ToString()); + } + } + + blacklisted = false; + lock (m_blacklistedurls) + { + if (m_blacklistedurls.ContainsKey(httpserver)) + blacklisted = true; + } + + // Can't find the http server + if (httpserver.Length == 0 || blacklisted) + return new OSDMap(); + + MapRequestState mrs = new MapRequestState(); + mrs.agentID = id; + mrs.EstateID = EstateID; + mrs.flags = flags; + mrs.godlike = godlike; + mrs.itemtype=itemtype; + mrs.regionhandle = regionhandle; + + lock (m_openRequests) + m_openRequests.Add(requestID, mrs); + + WebRequest mapitemsrequest = WebRequest.Create(httpserver); + mapitemsrequest.Method = "POST"; + mapitemsrequest.ContentType = "application/xml+llsd"; + OSDMap RAMap = new OSDMap(); + + // string RAMapString = RAMap.ToString(); + OSD LLSDofRAMap = RAMap; // RENAME if this works + + byte[] buffer = OSDParser.SerializeLLSDXmlBytes(LLSDofRAMap); + OSDMap responseMap = new OSDMap(); + responseMap["requestID"] = OSD.FromUUID(requestID); + + Stream os = null; + try + { // send the Post + mapitemsrequest.ContentLength = buffer.Length; //Count bytes to send + os = mapitemsrequest.GetRequestStream(); + os.Write(buffer, 0, buffer.Length); //Send it + os.Close(); + //m_log.DebugFormat("[WORLD MAP]: Getting MapItems from Sim {0}", httpserver); + } + catch (WebException ex) + { + m_log.WarnFormat("[WORLD MAP]: Bad send on GetMapItems {0}", ex.Message); + responseMap["connect"] = OSD.FromBoolean(false); + lock (m_blacklistedurls) + { + if (!m_blacklistedurls.ContainsKey(httpserver)) + m_blacklistedurls.Add(httpserver, System.Environment.TickCount); + } + + m_log.WarnFormat("[WORLD MAP]: Blacklisted {0}", httpserver); + + return responseMap; + } + + string response_mapItems_reply = null; + { // get the response + try + { + WebResponse webResponse = mapitemsrequest.GetResponse(); + if (webResponse != null) + { + StreamReader sr = new StreamReader(webResponse.GetResponseStream()); + response_mapItems_reply = sr.ReadToEnd().Trim(); + } + else + { + return new OSDMap(); + } + } + catch (WebException) + { + responseMap["connect"] = OSD.FromBoolean(false); + lock (m_blacklistedurls) + { + if (!m_blacklistedurls.ContainsKey(httpserver)) + m_blacklistedurls.Add(httpserver, System.Environment.TickCount); + } + + m_log.WarnFormat("[WORLD MAP]: Blacklisted {0}", httpserver); + + return responseMap; + } + OSD rezResponse = null; + try + { + rezResponse = OSDParser.DeserializeLLSDXml(response_mapItems_reply); + + responseMap = (OSDMap)rezResponse; + responseMap["requestID"] = OSD.FromUUID(requestID); + } + catch (Exception) + { + //m_log.InfoFormat("[OGP]: exception on parse of rez reply {0}", ex.Message); + responseMap["connect"] = OSD.FromBoolean(false); + + return responseMap; + } + } + return responseMap; + } + + /// + /// Requests map blocks in area of minX, maxX, minY, MaxY in world cordinates + /// + /// + /// + /// + /// + public virtual void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) + { + List mapBlocks; + if ((flag & 0x10000) != 0) // user clicked on the map a tile that isn't visible + { + List response = new List(); + + // this should return one mapblock at most. But make sure: Look whether the one we requested is in there + mapBlocks = m_scene.SceneGridService.RequestNeighbourMapBlocks(minX, minY, maxX, maxY); + if (mapBlocks != null) + { + foreach (MapBlockData block in mapBlocks) + { + if (block.X == minX && block.Y == minY) + { + // found it => add it to response + response.Add(block); + break; + } + } + } + + if (response.Count == 0) + { + // response still empty => couldn't find the map-tile the user clicked on => tell the client + MapBlockData block = new MapBlockData(); + block.X = (ushort)minX; + block.Y = (ushort)minY; + block.Access = 254; // == not there + response.Add(block); + } + remoteClient.SendMapBlock(response, 0); + } + else + { + // normal mapblock request. Use the provided values + mapBlocks = m_scene.SceneGridService.RequestNeighbourMapBlocks(minX - 4, minY - 4, maxX + 4, maxY + 4); + remoteClient.SendMapBlock(mapBlocks, flag); + } + } + + public Hashtable OnHTTPGetMapImage(Hashtable keysvals) + { + m_log.Debug("[WORLD MAP]: Sending map image jpeg"); + Hashtable reply = new Hashtable(); + int statuscode = 200; + byte[] jpeg = new byte[0]; + + if (myMapImageJPEG.Length == 0) + { + MemoryStream imgstream = new MemoryStream(); + Bitmap mapTexture = new Bitmap(1,1); + ManagedImage managedImage; + Image image = (Image)mapTexture; + + try + { + // Taking our jpeg2000 data, decoding it, then saving it to a byte array with regular jpeg data + + imgstream = new MemoryStream(); + + // non-async because we know we have the asset immediately. + AssetBase mapasset = m_scene.AssetCache.GetAsset(m_scene.RegionInfo.lastMapUUID, true); + + // Decode image to System.Drawing.Image + if (OpenJPEG.DecodeToImage(mapasset.Data, out managedImage, out image)) + { + // Save to bitmap + mapTexture = new Bitmap(image); + + EncoderParameters myEncoderParameters = new EncoderParameters(); + myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 95L); + + // Save bitmap to stream + mapTexture.Save(imgstream, GetEncoderInfo("image/jpeg"), myEncoderParameters); + + // Write the stream to a byte array for output + jpeg = imgstream.ToArray(); + myMapImageJPEG = jpeg; + } + } + catch (Exception) + { + // Dummy! + m_log.Warn("[WORLD MAP]: Unable to generate Map image"); + } + finally + { + // Reclaim memory, these are unmanaged resources + mapTexture.Dispose(); + image.Dispose(); + imgstream.Close(); + imgstream.Dispose(); + } + } + else + { + // Use cached version so we don't have to loose our mind + jpeg = myMapImageJPEG; + } + + reply["str_response_string"] = Convert.ToBase64String(jpeg); + reply["int_response_code"] = statuscode; + reply["content_type"] = "image/jpeg"; + + return reply; + } + + // From msdn + private static ImageCodecInfo GetEncoderInfo(String mimeType) + { + ImageCodecInfo[] encoders; + encoders = ImageCodecInfo.GetImageEncoders(); + for (int j = 0; j < encoders.Length; ++j) + { + if (encoders[j].MimeType == mimeType) + return encoders[j]; + } + return null; + } + + public OSD HandleRemoteMapItemRequest(string path, OSD request, string endpoint) + { + uint xstart = 0; + uint ystart = 0; + + Utils.LongToUInts(m_scene.RegionInfo.RegionHandle,out xstart,out ystart); + + OSDMap responsemap = new OSDMap(); + List avatars = m_scene.GetAvatars(); + OSDArray responsearr = new OSDArray(avatars.Count); + OSDMap responsemapdata = new OSDMap(); + int tc = System.Environment.TickCount; + /* + foreach (ScenePresence av in avatars) + { + responsemapdata = new OSDMap(); + responsemapdata["X"] = OSD.FromInteger((int)(xstart + av.AbsolutePosition.X)); + responsemapdata["Y"] = OSD.FromInteger((int)(ystart + av.AbsolutePosition.Y)); + responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); + responsemapdata["Name"] = OSD.FromString("TH"); + responsemapdata["Extra"] = OSD.FromInteger(0); + responsemapdata["Extra2"] = OSD.FromInteger(0); + responsearr.Add(responsemapdata); + } + responsemap["1"] = responsearr; + */ + if (avatars.Count == 0) + { + responsemapdata = new OSDMap(); + responsemapdata["X"] = OSD.FromInteger((int)(xstart + 1)); + responsemapdata["Y"] = OSD.FromInteger((int)(ystart + 1)); + responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); + responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString())); + responsemapdata["Extra"] = OSD.FromInteger(0); + responsemapdata["Extra2"] = OSD.FromInteger(0); + responsearr.Add(responsemapdata); + + responsemap["6"] = responsearr; + } + else + { + responsearr = new OSDArray(avatars.Count); + foreach (ScenePresence av in avatars) + { + responsemapdata = new OSDMap(); + responsemapdata["X"] = OSD.FromInteger((int)(xstart + av.AbsolutePosition.X)); + responsemapdata["Y"] = OSD.FromInteger((int)(ystart + av.AbsolutePosition.Y)); + responsemapdata["ID"] = OSD.FromUUID(UUID.Zero); + responsemapdata["Name"] = OSD.FromString(Util.Md5Hash(m_scene.RegionInfo.RegionName + tc.ToString())); + responsemapdata["Extra"] = OSD.FromInteger(1); + responsemapdata["Extra2"] = OSD.FromInteger(0); + responsearr.Add(responsemapdata); + } + responsemap["6"] = responsearr; + } + return responsemap; + } + + private void MakeRootAgent(ScenePresence avatar) + { + // You may ask, why this is in a threadpool to start with.. + // The reason is so we don't cause the thread to freeze waiting + // for the 1 second it costs to start a thread manually. + if (!threadrunning) + ThreadPool.QueueUserWorkItem(new WaitCallback(this.StartThread)); + + lock (m_rootAgents) + { + if (!m_rootAgents.Contains(avatar.UUID)) + { + m_rootAgents.Add(avatar.UUID); + } + } + } + + private void MakeChildAgent(ScenePresence avatar) + { + List presences = m_scene.GetAvatars(); + int rootcount = 0; + for (int i = 0; i < presences.Count; i++) + { + if (presences[i] != null) + { + if (!presences[i].IsChildAgent) + rootcount++; + } + } + if (rootcount <= 1) + StopThread(); + + lock (m_rootAgents) + { + if (m_rootAgents.Contains(avatar.UUID)) + { + m_rootAgents.Remove(avatar.UUID); + } + } + } + } + + public struct MapRequestState + { + public UUID agentID; + public uint flags; + public uint EstateID; + public bool godlike; + public uint itemtype; + public ulong regionhandle; + } +} -- cgit v1.1