diff --git a/libsecondlife-cs/AvatarManager.cs b/libsecondlife-cs/AvatarManager.cs index 0c903722..b56bb381 100644 --- a/libsecondlife-cs/AvatarManager.cs +++ b/libsecondlife-cs/AvatarManager.cs @@ -1,474 +1,474 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Threading; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Retrieve friend status notifications, and retrieve avatar names and - /// profiles - /// - public class AvatarManager - { - /// - /// Triggered after friend request packet is sent out - /// - /// - /// - public delegate void FriendNotificationCallback(LLUUID agentID, bool online); - /// - /// Triggered when a UUIDNameReply is received - /// - /// - public delegate void AvatarNamesCallback(Dictionary names); - /// - /// Triggered when a response for avatar statistics (ratings) is returned - /// - /// - /// - public delegate void AvatarStatisticsCallback(LLUUID avatarID, Avatar.Statistics statistics); - /// - /// Triggered when a response for avatar interests is returned - /// - /// - /// - public delegate void AvatarInterestsCallback(LLUUID avatarID, Avatar.Interests interests); - /// - /// Triggered when avatar properties are received (AvatarPropertiesReply) - /// - /// - /// - public delegate void AvatarPropertiesCallback(LLUUID avatarID, Avatar.AvatarProperties properties); - /// - /// Triggered when an avatar group list is received (AvatarGroupsReply) - /// - /// - /// - public delegate void AvatarGroupsCallback(LLUUID avatarID, AvatarGroupsReplyPacket.GroupDataBlock[] groups); - /// - /// Triggered when a name search reply is received (AvatarPickerReply) - /// - /// - /// - public delegate void AvatarNameSearchCallback(LLUUID queryID, Dictionary avatars); - - - /// Triggered whenever a friend comes online or goes offline - public event FriendNotificationCallback OnFriendNotification; - /// - public event AvatarNamesCallback OnAvatarNames; - /// - public event AvatarStatisticsCallback OnAvatarStatistics; - /// - public event AvatarInterestsCallback OnAvatarInterests; - /// - public event AvatarPropertiesCallback OnAvatarProperties; - /// - public event AvatarGroupsCallback OnAvatarGroups; - /// - public event AvatarNameSearchCallback OnAvatarNameSearch; - - private SecondLife Client; - - /// - /// Represents other avatars - /// - /// - public AvatarManager(SecondLife client) - { - Client = client; - - // Friend notification callback - NetworkManager.PacketCallback callback = new NetworkManager.PacketCallback(FriendNotificationHandler); - Client.Network.RegisterCallback(PacketType.OnlineNotification, callback); - Client.Network.RegisterCallback(PacketType.OfflineNotification, callback); - - // Avatar profile callbacks - Client.Network.RegisterCallback(PacketType.AvatarPropertiesReply, new NetworkManager.PacketCallback(AvatarPropertiesHandler)); - Client.Network.RegisterCallback(PacketType.AvatarStatisticsReply, new NetworkManager.PacketCallback(AvatarStatisticsHandler)); - Client.Network.RegisterCallback(PacketType.AvatarInterestsReply, new NetworkManager.PacketCallback(AvatarInterestsHandler)); - - // Avatar group callback - Client.Network.RegisterCallback(PacketType.AvatarGroupsReply, new NetworkManager.PacketCallback(AvatarGroupsHandler)); - - // Viewer effect callback - Client.Network.RegisterCallback(PacketType.ViewerEffect, new NetworkManager.PacketCallback(ViewerEffectHandler)); - - // Other callbacks - Client.Network.RegisterCallback(PacketType.UUIDNameReply, new NetworkManager.PacketCallback(AvatarNameHandler)); - Client.Network.RegisterCallback(PacketType.AvatarPickerReply, new NetworkManager.PacketCallback(AvatarPickerReplyHandler)); - } - - /// - /// Request a single avatar name - /// - /// The avatar key to retrieve a name for - public void RequestAvatarName(LLUUID id) - { - UUIDNameRequestPacket request = new UUIDNameRequestPacket(); - request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[1]; - request.UUIDNameBlock[0] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); - request.UUIDNameBlock[0].ID = id; - - Client.Network.SendPacket(request); - } - - /// - /// Request a list of avatar names - /// - /// The avatar keys to retrieve names for - public void RequestAvatarNames(List ids) - { - UUIDNameRequestPacket request = new UUIDNameRequestPacket(); - request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[ids.Count]; - - for (int i = 0; i < ids.Count; i++) - { - request.UUIDNameBlock[i] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); - request.UUIDNameBlock[i].ID = ids[i]; - } - - Client.Network.SendPacket(request); - } - - /// - /// Start a request for Avatar Properties - /// - /// - public void RequestAvatarProperties(LLUUID avatarid) - { - AvatarPropertiesRequestPacket aprp = new AvatarPropertiesRequestPacket(); - - aprp.AgentData.AgentID = Client.Network.AgentID; - aprp.AgentData.SessionID = Client.Network.SessionID; - aprp.AgentData.AvatarID = avatarid; - - Client.Network.SendPacket(aprp); - } - - /// - /// Search for an avatar (first name, last name, and uuid) - /// - /// The name to search for - /// An ID to associate with this query - public void RequestAvatarNameSearch(string name, LLUUID queryID) - { - AvatarPickerRequestPacket aprp = new AvatarPickerRequestPacket(); - - aprp.AgentData.AgentID = Client.Network.AgentID; - aprp.AgentData.SessionID = Client.Network.SessionID; - aprp.AgentData.QueryID = queryID; - aprp.Data.Name = Helpers.StringToField(name); - - Client.Network.SendPacket(aprp); - } - - /// - /// Process an incoming UUIDNameReply Packet and insert Full Names into the Avatars Dictionary - /// - /// Incoming Packet to process - /// Unused - private void AvatarNameHandler(Packet packet, Simulator simulator) - { - if (OnAvatarNames != null) - { - Dictionary names = new Dictionary(); - UUIDNameReplyPacket reply = (UUIDNameReplyPacket)packet; - - foreach (UUIDNameReplyPacket.UUIDNameBlockBlock block in reply.UUIDNameBlock) - { - names[block.ID] = Helpers.FieldToUTF8String(block.FirstName) + - " " + Helpers.FieldToUTF8String(block.LastName); - } - - OnAvatarNames(names); - } - } - - /// - /// Handle incoming friend notifications - /// - /// - /// - private void FriendNotificationHandler(Packet packet, Simulator simulator) - { - if (OnFriendNotification != null) - { - if (packet.Type == PacketType.OnlineNotification) - { - // If the agent is online - foreach (OnlineNotificationPacket.AgentBlockBlock block in ((OnlineNotificationPacket)packet).AgentBlock) - OnFriendNotification(block.AgentID, true); - } - else if (packet.Type == PacketType.OfflineNotification) - { - // If the agent is offline - foreach (OfflineNotificationPacket.AgentBlockBlock block in ((OfflineNotificationPacket)packet).AgentBlock) - OnFriendNotification(block.AgentID, true); - } - } - } - - /// - /// Handles incoming avatar statistics, such as ratings - /// - /// - /// - private void AvatarStatisticsHandler(Packet packet, Simulator simulator) - { - if (OnAvatarStatistics != null) - { - AvatarStatisticsReplyPacket asr = (AvatarStatisticsReplyPacket)packet; - Avatar.Statistics stats = new Avatar.Statistics(); - - foreach (AvatarStatisticsReplyPacket.StatisticsDataBlock b in asr.StatisticsData) - { - string n = Helpers.FieldToUTF8String(b.Name); - - switch (n) - { - case "Behavior": - stats.BehaviorPositive = b.Positive; - stats.BehaviorNegative = b.Negative; - break; - case "Appearance": - stats.AppearancePositive = b.Positive; - stats.AppearanceNegative = b.Negative; - break; - case "Building": - stats.AppearancePositive = b.Positive; - stats.AppearanceNegative = b.Negative; - break; - case "Given": - stats.GivenPositive = b.Positive; - stats.GivenNegative = b.Negative; - break; - default: - Client.Log("Got an AvatarStatistics block with the name " + n, Helpers.LogLevel.Warning); - break; - } - } - - OnAvatarStatistics(asr.AvatarData.AvatarID, stats); - } - } - - /// - /// Process incoming avatar properties (profile data) - /// - /// - /// - private void AvatarPropertiesHandler(Packet packet, Simulator sim) - { - if (OnAvatarProperties != null) - { - AvatarPropertiesReplyPacket reply = (AvatarPropertiesReplyPacket)packet; - Avatar.AvatarProperties properties = new Avatar.AvatarProperties(); - - properties.ProfileImage = reply.PropertiesData.ImageID; - properties.FirstLifeImage = reply.PropertiesData.FLImageID; - properties.Partner = reply.PropertiesData.PartnerID; - properties.AboutText = Helpers.FieldToUTF8String(reply.PropertiesData.AboutText); - properties.FirstLifeText = Helpers.FieldToUTF8String(reply.PropertiesData.FLAboutText); - properties.BornOn = Helpers.FieldToUTF8String(reply.PropertiesData.BornOn); - properties.CharterMember = Helpers.FieldToUTF8String(reply.PropertiesData.CharterMember); - // FIXME: These have been converted in to a Flags field, build an enum and fix this! - //properties.AllowPublish = reply.PropertiesData.AllowPublish; - //properties.MaturePublish = reply.PropertiesData.MaturePublish; - //properties.Identified = reply.PropertiesData.Identified; - //properties.Transacted = reply.PropertiesData.Transacted; - properties.ProfileURL = Helpers.FieldToUTF8String(reply.PropertiesData.ProfileURL); - - OnAvatarProperties(reply.AgentData.AvatarID, properties); - } - } - - /// - /// Process incoming Avatar Interests information - /// - private void AvatarInterestsHandler(Packet packet, Simulator simulator) - { - if (OnAvatarInterests != null) - { - AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; - Avatar.Interests interests = new Avatar.Interests(); - - interests.WantToMask = airp.PropertiesData.WantToMask; - interests.WantToText = Helpers.FieldToUTF8String(airp.PropertiesData.WantToText); - interests.SkillsMask = airp.PropertiesData.SkillsMask; - interests.SkillsText = Helpers.FieldToUTF8String(airp.PropertiesData.SkillsText); - interests.LanguagesText = Helpers.FieldToUTF8String(airp.PropertiesData.LanguagesText); - - OnAvatarInterests(airp.AgentData.AvatarID, interests); - } - } - - private void AvatarGroupsHandler(Packet packet, Simulator simulator) - { - if (OnAvatarGroups != null) - { - AvatarGroupsReplyPacket groups = (AvatarGroupsReplyPacket)packet; - - // FIXME: Build a little struct to represent the groups.GroupData blocks so we keep - // libsecondlife.Packets abstracted away - OnAvatarGroups(groups.AgentData.AvatarID, groups.GroupData); - } - } - - private void AvatarPickerReplyHandler(Packet packet, Simulator simulator) - { - if (OnAvatarNameSearch != null) - { - AvatarPickerReplyPacket reply = (AvatarPickerReplyPacket)packet; - Dictionary avatars = new Dictionary(); - - foreach (AvatarPickerReplyPacket.DataBlock block in reply.Data) - { - avatars[block.AvatarID] = Helpers.FieldToUTF8String(block.FirstName) + - " " + Helpers.FieldToUTF8String(block.LastName); - } - - OnAvatarNameSearch(reply.AgentData.QueryID, avatars); - } - } - - /// - /// Process an incoming effect - /// - private void ViewerEffectHandler(Packet packet, Simulator simulator) - { - ViewerEffectPacket effect = (ViewerEffectPacket)packet; - - foreach (ViewerEffectPacket.EffectBlock block in effect.Effect) - { - MainAvatar.EffectType type; - - try - { - type = (MainAvatar.EffectType)block.Type; - } - catch (Exception) - { - Client.Log("Received a ViewerEffect block with an unknown type " + block.Type, - Helpers.LogLevel.Warning); - continue; - } - - //LLColor color; - //if (block.Color.Length == 4) - //{ - // color = new LLColor(block.Color, 0); - //} - //else - //{ - // Client.Log("Received a ViewerEffect.EffectBlock.Color array with " + block.Color.Length + " bytes", - // Helpers.LogLevel.Warning); - // color = new LLColor(); - //} - - // Each ViewerEffect type uses it's own custom binary format for additional data. Fun eh? - switch (type) - { - case MainAvatar.EffectType.Text: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Icon: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Connector: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.FlexibleObject: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.AnimalControls: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.AnimationObject: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Cloth: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Beam: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Glow: - Client.Log("Received a Glow ViewerEffect which is not implemented yet", - Helpers.LogLevel.Warning); - break; - case MainAvatar.EffectType.Point: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Trail: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Sphere: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Spiral: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.Edit: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.LookAt: - //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); - break; - case MainAvatar.EffectType.PointAt: - if (block.TypeData.Length == 57) - { - LLUUID sourceAvatar = new LLUUID(block.TypeData, 0); - LLUUID targetObject = new LLUUID(block.TypeData, 16); - LLVector3d targetPos = new LLVector3d(block.TypeData, 32); - MainAvatar.PointAtType pointAt; - try - { - pointAt = (MainAvatar.PointAtType)block.TypeData[56]; - } - catch (Exception) - { - Client.Log("Unrecognized PointAtType " + block.TypeData[56], Helpers.LogLevel.Warning); - pointAt = MainAvatar.PointAtType.Clear; - } - - // TODO: Create a OnAvatarPointAt event and call it here - } - else - { - Client.Log("Received a PointAt ViewerEffect with an incorrect TypeData size of " + - block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); - } - break; - } - } - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Threading; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Retrieve friend status notifications, and retrieve avatar names and + /// profiles + /// + public class AvatarManager + { + /// + /// Triggered after friend request packet is sent out + /// + /// + /// + public delegate void FriendNotificationCallback(LLUUID agentID, bool online); + /// + /// Triggered when a UUIDNameReply is received + /// + /// + public delegate void AvatarNamesCallback(Dictionary names); + /// + /// Triggered when a response for avatar statistics (ratings) is returned + /// + /// + /// + public delegate void AvatarStatisticsCallback(LLUUID avatarID, Avatar.Statistics statistics); + /// + /// Triggered when a response for avatar interests is returned + /// + /// + /// + public delegate void AvatarInterestsCallback(LLUUID avatarID, Avatar.Interests interests); + /// + /// Triggered when avatar properties are received (AvatarPropertiesReply) + /// + /// + /// + public delegate void AvatarPropertiesCallback(LLUUID avatarID, Avatar.AvatarProperties properties); + /// + /// Triggered when an avatar group list is received (AvatarGroupsReply) + /// + /// + /// + public delegate void AvatarGroupsCallback(LLUUID avatarID, AvatarGroupsReplyPacket.GroupDataBlock[] groups); + /// + /// Triggered when a name search reply is received (AvatarPickerReply) + /// + /// + /// + public delegate void AvatarNameSearchCallback(LLUUID queryID, Dictionary avatars); + + + /// Triggered whenever a friend comes online or goes offline + public event FriendNotificationCallback OnFriendNotification; + /// + public event AvatarNamesCallback OnAvatarNames; + /// + public event AvatarStatisticsCallback OnAvatarStatistics; + /// + public event AvatarInterestsCallback OnAvatarInterests; + /// + public event AvatarPropertiesCallback OnAvatarProperties; + /// + public event AvatarGroupsCallback OnAvatarGroups; + /// + public event AvatarNameSearchCallback OnAvatarNameSearch; + + private SecondLife Client; + + /// + /// Represents other avatars + /// + /// + public AvatarManager(SecondLife client) + { + Client = client; + + // Friend notification callback + NetworkManager.PacketCallback callback = new NetworkManager.PacketCallback(FriendNotificationHandler); + Client.Network.RegisterCallback(PacketType.OnlineNotification, callback); + Client.Network.RegisterCallback(PacketType.OfflineNotification, callback); + + // Avatar profile callbacks + Client.Network.RegisterCallback(PacketType.AvatarPropertiesReply, new NetworkManager.PacketCallback(AvatarPropertiesHandler)); + Client.Network.RegisterCallback(PacketType.AvatarStatisticsReply, new NetworkManager.PacketCallback(AvatarStatisticsHandler)); + Client.Network.RegisterCallback(PacketType.AvatarInterestsReply, new NetworkManager.PacketCallback(AvatarInterestsHandler)); + + // Avatar group callback + Client.Network.RegisterCallback(PacketType.AvatarGroupsReply, new NetworkManager.PacketCallback(AvatarGroupsHandler)); + + // Viewer effect callback + Client.Network.RegisterCallback(PacketType.ViewerEffect, new NetworkManager.PacketCallback(ViewerEffectHandler)); + + // Other callbacks + Client.Network.RegisterCallback(PacketType.UUIDNameReply, new NetworkManager.PacketCallback(AvatarNameHandler)); + Client.Network.RegisterCallback(PacketType.AvatarPickerReply, new NetworkManager.PacketCallback(AvatarPickerReplyHandler)); + } + + /// + /// Request a single avatar name + /// + /// The avatar key to retrieve a name for + public void RequestAvatarName(LLUUID id) + { + UUIDNameRequestPacket request = new UUIDNameRequestPacket(); + request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[1]; + request.UUIDNameBlock[0] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); + request.UUIDNameBlock[0].ID = id; + + Client.Network.SendPacket(request); + } + + /// + /// Request a list of avatar names + /// + /// The avatar keys to retrieve names for + public void RequestAvatarNames(List ids) + { + UUIDNameRequestPacket request = new UUIDNameRequestPacket(); + request.UUIDNameBlock = new UUIDNameRequestPacket.UUIDNameBlockBlock[ids.Count]; + + for (int i = 0; i < ids.Count; i++) + { + request.UUIDNameBlock[i] = new UUIDNameRequestPacket.UUIDNameBlockBlock(); + request.UUIDNameBlock[i].ID = ids[i]; + } + + Client.Network.SendPacket(request); + } + + /// + /// Start a request for Avatar Properties + /// + /// + public void RequestAvatarProperties(LLUUID avatarid) + { + AvatarPropertiesRequestPacket aprp = new AvatarPropertiesRequestPacket(); + + aprp.AgentData.AgentID = Client.Network.AgentID; + aprp.AgentData.SessionID = Client.Network.SessionID; + aprp.AgentData.AvatarID = avatarid; + + Client.Network.SendPacket(aprp); + } + + /// + /// Search for an avatar (first name, last name, and uuid) + /// + /// The name to search for + /// An ID to associate with this query + public void RequestAvatarNameSearch(string name, LLUUID queryID) + { + AvatarPickerRequestPacket aprp = new AvatarPickerRequestPacket(); + + aprp.AgentData.AgentID = Client.Network.AgentID; + aprp.AgentData.SessionID = Client.Network.SessionID; + aprp.AgentData.QueryID = queryID; + aprp.Data.Name = Helpers.StringToField(name); + + Client.Network.SendPacket(aprp); + } + + /// + /// Process an incoming UUIDNameReply Packet and insert Full Names into the Avatars Dictionary + /// + /// Incoming Packet to process + /// Unused + private void AvatarNameHandler(Packet packet, Simulator simulator) + { + if (OnAvatarNames != null) + { + Dictionary names = new Dictionary(); + UUIDNameReplyPacket reply = (UUIDNameReplyPacket)packet; + + foreach (UUIDNameReplyPacket.UUIDNameBlockBlock block in reply.UUIDNameBlock) + { + names[block.ID] = Helpers.FieldToUTF8String(block.FirstName) + + " " + Helpers.FieldToUTF8String(block.LastName); + } + + OnAvatarNames(names); + } + } + + /// + /// Handle incoming friend notifications + /// + /// + /// + private void FriendNotificationHandler(Packet packet, Simulator simulator) + { + if (OnFriendNotification != null) + { + if (packet.Type == PacketType.OnlineNotification) + { + // If the agent is online + foreach (OnlineNotificationPacket.AgentBlockBlock block in ((OnlineNotificationPacket)packet).AgentBlock) + OnFriendNotification(block.AgentID, true); + } + else if (packet.Type == PacketType.OfflineNotification) + { + // If the agent is offline + foreach (OfflineNotificationPacket.AgentBlockBlock block in ((OfflineNotificationPacket)packet).AgentBlock) + OnFriendNotification(block.AgentID, true); + } + } + } + + /// + /// Handles incoming avatar statistics, such as ratings + /// + /// + /// + private void AvatarStatisticsHandler(Packet packet, Simulator simulator) + { + if (OnAvatarStatistics != null) + { + AvatarStatisticsReplyPacket asr = (AvatarStatisticsReplyPacket)packet; + Avatar.Statistics stats = new Avatar.Statistics(); + + foreach (AvatarStatisticsReplyPacket.StatisticsDataBlock b in asr.StatisticsData) + { + string n = Helpers.FieldToUTF8String(b.Name); + + switch (n) + { + case "Behavior": + stats.BehaviorPositive = b.Positive; + stats.BehaviorNegative = b.Negative; + break; + case "Appearance": + stats.AppearancePositive = b.Positive; + stats.AppearanceNegative = b.Negative; + break; + case "Building": + stats.AppearancePositive = b.Positive; + stats.AppearanceNegative = b.Negative; + break; + case "Given": + stats.GivenPositive = b.Positive; + stats.GivenNegative = b.Negative; + break; + default: + Client.Log("Got an AvatarStatistics block with the name " + n, Helpers.LogLevel.Warning); + break; + } + } + + OnAvatarStatistics(asr.AvatarData.AvatarID, stats); + } + } + + /// + /// Process incoming avatar properties (profile data) + /// + /// + /// + private void AvatarPropertiesHandler(Packet packet, Simulator sim) + { + if (OnAvatarProperties != null) + { + AvatarPropertiesReplyPacket reply = (AvatarPropertiesReplyPacket)packet; + Avatar.AvatarProperties properties = new Avatar.AvatarProperties(); + + properties.ProfileImage = reply.PropertiesData.ImageID; + properties.FirstLifeImage = reply.PropertiesData.FLImageID; + properties.Partner = reply.PropertiesData.PartnerID; + properties.AboutText = Helpers.FieldToUTF8String(reply.PropertiesData.AboutText); + properties.FirstLifeText = Helpers.FieldToUTF8String(reply.PropertiesData.FLAboutText); + properties.BornOn = Helpers.FieldToUTF8String(reply.PropertiesData.BornOn); + properties.CharterMember = Helpers.FieldToUTF8String(reply.PropertiesData.CharterMember); + // FIXME: These have been converted in to a Flags field, build an enum and fix this! + //properties.AllowPublish = reply.PropertiesData.AllowPublish; + //properties.MaturePublish = reply.PropertiesData.MaturePublish; + //properties.Identified = reply.PropertiesData.Identified; + //properties.Transacted = reply.PropertiesData.Transacted; + properties.ProfileURL = Helpers.FieldToUTF8String(reply.PropertiesData.ProfileURL); + + OnAvatarProperties(reply.AgentData.AvatarID, properties); + } + } + + /// + /// Process incoming Avatar Interests information + /// + private void AvatarInterestsHandler(Packet packet, Simulator simulator) + { + if (OnAvatarInterests != null) + { + AvatarInterestsReplyPacket airp = (AvatarInterestsReplyPacket)packet; + Avatar.Interests interests = new Avatar.Interests(); + + interests.WantToMask = airp.PropertiesData.WantToMask; + interests.WantToText = Helpers.FieldToUTF8String(airp.PropertiesData.WantToText); + interests.SkillsMask = airp.PropertiesData.SkillsMask; + interests.SkillsText = Helpers.FieldToUTF8String(airp.PropertiesData.SkillsText); + interests.LanguagesText = Helpers.FieldToUTF8String(airp.PropertiesData.LanguagesText); + + OnAvatarInterests(airp.AgentData.AvatarID, interests); + } + } + + private void AvatarGroupsHandler(Packet packet, Simulator simulator) + { + if (OnAvatarGroups != null) + { + AvatarGroupsReplyPacket groups = (AvatarGroupsReplyPacket)packet; + + // FIXME: Build a little struct to represent the groups.GroupData blocks so we keep + // libsecondlife.Packets abstracted away + OnAvatarGroups(groups.AgentData.AvatarID, groups.GroupData); + } + } + + private void AvatarPickerReplyHandler(Packet packet, Simulator simulator) + { + if (OnAvatarNameSearch != null) + { + AvatarPickerReplyPacket reply = (AvatarPickerReplyPacket)packet; + Dictionary avatars = new Dictionary(); + + foreach (AvatarPickerReplyPacket.DataBlock block in reply.Data) + { + avatars[block.AvatarID] = Helpers.FieldToUTF8String(block.FirstName) + + " " + Helpers.FieldToUTF8String(block.LastName); + } + + OnAvatarNameSearch(reply.AgentData.QueryID, avatars); + } + } + + /// + /// Process an incoming effect + /// + private void ViewerEffectHandler(Packet packet, Simulator simulator) + { + ViewerEffectPacket effect = (ViewerEffectPacket)packet; + + foreach (ViewerEffectPacket.EffectBlock block in effect.Effect) + { + MainAvatar.EffectType type; + + try + { + type = (MainAvatar.EffectType)block.Type; + } + catch (Exception) + { + Client.Log("Received a ViewerEffect block with an unknown type " + block.Type, + Helpers.LogLevel.Warning); + continue; + } + + //LLColor color; + //if (block.Color.Length == 4) + //{ + // color = new LLColor(block.Color, 0); + //} + //else + //{ + // Client.Log("Received a ViewerEffect.EffectBlock.Color array with " + block.Color.Length + " bytes", + // Helpers.LogLevel.Warning); + // color = new LLColor(); + //} + + // Each ViewerEffect type uses it's own custom binary format for additional data. Fun eh? + switch (type) + { + case MainAvatar.EffectType.Text: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Icon: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Connector: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.FlexibleObject: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.AnimalControls: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.AnimationObject: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Cloth: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Beam: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Glow: + Client.Log("Received a Glow ViewerEffect which is not implemented yet", + Helpers.LogLevel.Warning); + break; + case MainAvatar.EffectType.Point: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Trail: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Sphere: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Spiral: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.Edit: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.LookAt: + //Client.DebugLog("Received a ViewerEffect of type " + type.ToString() + ", implement me!"); + break; + case MainAvatar.EffectType.PointAt: + if (block.TypeData.Length == 57) + { + LLUUID sourceAvatar = new LLUUID(block.TypeData, 0); + LLUUID targetObject = new LLUUID(block.TypeData, 16); + LLVector3d targetPos = new LLVector3d(block.TypeData, 32); + MainAvatar.PointAtType pointAt; + try + { + pointAt = (MainAvatar.PointAtType)block.TypeData[56]; + } + catch (Exception) + { + Client.Log("Unrecognized PointAtType " + block.TypeData[56], Helpers.LogLevel.Warning); + pointAt = MainAvatar.PointAtType.Clear; + } + + // TODO: Create a OnAvatarPointAt event and call it here + } + else + { + Client.Log("Received a PointAt ViewerEffect with an incorrect TypeData size of " + + block.TypeData.Length + " bytes", Helpers.LogLevel.Warning); + } + break; + } + } + } + } +} diff --git a/libsecondlife-cs/BakeLayer.cs b/libsecondlife-cs/BakeLayer.cs index 7a3a497a..70ea4386 100644 --- a/libsecondlife-cs/BakeLayer.cs +++ b/libsecondlife-cs/BakeLayer.cs @@ -1,230 +1,230 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -namespace libsecondlife -{ - /// - /// A set of textures that are layered on top of each other and "baked" - /// in to a single texture, for avatar appearances - /// - public class BakeLayer - { - public enum BakeOrder - { - Unknown = -1, - HeadBodypaint = 0, - Hair, - EyesIris, - UpperBodypaint, - UpperUndershirt, - UpperShirt, - UpperJacket, - LowerBodypaint, - LowerUnderpants, - LowerSocks, - LowerShoes, - LowerPants, - LowerJacket, - Skirt - } - - /// Maximum number of wearables for any baked layer - public const int WEARABLES_PER_LAYER = 7; - - /// Final compressed JPEG2000 data - public byte[] FinalData = new byte[0]; - /// Whether this bake is complete or not - public bool Finished = false; - - /// Reference to the SecondLife client - protected SecondLife Client; - /// Total number of textures in this bake - protected int TotalLayers; - /// Appearance parameters the drive the baking process - protected Dictionary ParamValues; - /// GDI+ image that textures are composited to - protected Bitmap Scratchpad; - /// List of textures sorted by their baking order - protected SortedList Textures = new SortedList(WEARABLES_PER_LAYER); - /// Width of the final baked image and scratchpad - protected int BakeWidth = 512; - /// Height of the final baked image and scratchpad - protected int BakeHeight = 512; - - - private Assembly assembly = null; - - - /// - /// Default constructor - /// - /// Reference to the SecondLife client - /// Total number of layers this bake set is - /// composed of - /// Appearance parameters the drive the - /// baking process - public BakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) - { - Client = client; - TotalLayers = totalLayers; - ParamValues = paramValues; - } - - /// - /// Default constructor - /// - /// Reference to the SecondLife client - /// Total number of layers this bake set is - /// composed of - /// Appearance parameters the drive the - /// baking process - /// Width of the final baked image - /// Height of the final baked image - public BakeLayer(SecondLife client, int totalLayers, Dictionary paramValues, int width, int height) - { - Client = client; - TotalLayers = totalLayers; - ParamValues = paramValues; - BakeWidth = width; - BakeHeight = height; - } - - /// - /// Adds an image to this baking layer and potentially processes it, or - /// stores it for processing later - /// - /// The baking layer index of the image to be added - /// JPEG2000 compressed image to be added to the - /// baking layer - /// True if this layer is completely baked and JPEG2000 data - /// is available, otherwise false - public bool AddImage(BakeOrder index, byte[] jp2data) - { - lock (Textures) - { - Textures.Add(index, jp2data); - - if (Textures.Count == TotalLayers) - { - // All of the layers are in place, we can bake - Bake(); - Finished = true; - return true; - } - } - - return false; - } - - /// - /// Create the various dynamic alpha masks, apply them to the affected - /// textures, and composite all of the textures in to the final scratch - /// pad - /// - protected void Bake() - { - if (TotalLayers == 1) - { - // FIXME: Create a properly formatted JP2 bake (5 comps) - } - else - { - Client.Log("Too many layers for the null baking code!", Helpers.LogLevel.Error); - } - } - - private StreamReader GetResource(string resourceName) - { - if (assembly == null) assembly = Assembly.GetExecutingAssembly(); - - return new StreamReader(assembly.GetManifestResourceStream(String.Format("libsecondlife.Resources.{0}", - resourceName))); - } - } - - /// - /// - /// - public class UpperBakeLayer : BakeLayer - { - /// - /// Default constructor - /// - /// Reference to the SecondLife client - /// Total number of layers this bake set is - /// composed of - /// Appearance parameters the drive the - /// baking process - public UpperBakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) - : base(client, totalLayers, paramValues) - { - } - - /// - /// - /// - protected new void Bake() - { - // FIXME: Iterate through each texture, generate the alpha masks and apply them, - // and combine the masked texture in to the final scratch pad - } - } - - /// - /// - /// - public class LowerBakeLayer : BakeLayer - { - /// - /// Default constructor - /// - /// Reference to the SecondLife client - /// Total number of layers this bake set is - /// composed of - /// Appearance parameters the drive the - /// baking process - public LowerBakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) - : base(client, totalLayers, paramValues) - { - } - - /// - /// - /// - protected new void Bake() - { - // FIXME: Iterate through each texture, generate the alpha masks and apply them, - // and combine the masked texture in to the final scratch pad - } - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace libsecondlife +{ + /// + /// A set of textures that are layered on top of each other and "baked" + /// in to a single texture, for avatar appearances + /// + public class BakeLayer + { + public enum BakeOrder + { + Unknown = -1, + HeadBodypaint = 0, + Hair, + EyesIris, + UpperBodypaint, + UpperUndershirt, + UpperShirt, + UpperJacket, + LowerBodypaint, + LowerUnderpants, + LowerSocks, + LowerShoes, + LowerPants, + LowerJacket, + Skirt + } + + /// Maximum number of wearables for any baked layer + public const int WEARABLES_PER_LAYER = 7; + + /// Final compressed JPEG2000 data + public byte[] FinalData = new byte[0]; + /// Whether this bake is complete or not + public bool Finished = false; + + /// Reference to the SecondLife client + protected SecondLife Client; + /// Total number of textures in this bake + protected int TotalLayers; + /// Appearance parameters the drive the baking process + protected Dictionary ParamValues; + /// GDI+ image that textures are composited to + protected Bitmap Scratchpad; + /// List of textures sorted by their baking order + protected SortedList Textures = new SortedList(WEARABLES_PER_LAYER); + /// Width of the final baked image and scratchpad + protected int BakeWidth = 512; + /// Height of the final baked image and scratchpad + protected int BakeHeight = 512; + + + private Assembly assembly = null; + + + /// + /// Default constructor + /// + /// Reference to the SecondLife client + /// Total number of layers this bake set is + /// composed of + /// Appearance parameters the drive the + /// baking process + public BakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) + { + Client = client; + TotalLayers = totalLayers; + ParamValues = paramValues; + } + + /// + /// Default constructor + /// + /// Reference to the SecondLife client + /// Total number of layers this bake set is + /// composed of + /// Appearance parameters the drive the + /// baking process + /// Width of the final baked image + /// Height of the final baked image + public BakeLayer(SecondLife client, int totalLayers, Dictionary paramValues, int width, int height) + { + Client = client; + TotalLayers = totalLayers; + ParamValues = paramValues; + BakeWidth = width; + BakeHeight = height; + } + + /// + /// Adds an image to this baking layer and potentially processes it, or + /// stores it for processing later + /// + /// The baking layer index of the image to be added + /// JPEG2000 compressed image to be added to the + /// baking layer + /// True if this layer is completely baked and JPEG2000 data + /// is available, otherwise false + public bool AddImage(BakeOrder index, byte[] jp2data) + { + lock (Textures) + { + Textures.Add(index, jp2data); + + if (Textures.Count == TotalLayers) + { + // All of the layers are in place, we can bake + Bake(); + Finished = true; + return true; + } + } + + return false; + } + + /// + /// Create the various dynamic alpha masks, apply them to the affected + /// textures, and composite all of the textures in to the final scratch + /// pad + /// + protected void Bake() + { + if (TotalLayers == 1) + { + // FIXME: Create a properly formatted JP2 bake (5 comps) + } + else + { + Client.Log("Too many layers for the null baking code!", Helpers.LogLevel.Error); + } + } + + private StreamReader GetResource(string resourceName) + { + if (assembly == null) assembly = Assembly.GetExecutingAssembly(); + + return new StreamReader(assembly.GetManifestResourceStream(String.Format("libsecondlife.Resources.{0}", + resourceName))); + } + } + + /// + /// + /// + public class UpperBakeLayer : BakeLayer + { + /// + /// Default constructor + /// + /// Reference to the SecondLife client + /// Total number of layers this bake set is + /// composed of + /// Appearance parameters the drive the + /// baking process + public UpperBakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) + : base(client, totalLayers, paramValues) + { + } + + /// + /// + /// + protected new void Bake() + { + // FIXME: Iterate through each texture, generate the alpha masks and apply them, + // and combine the masked texture in to the final scratch pad + } + } + + /// + /// + /// + public class LowerBakeLayer : BakeLayer + { + /// + /// Default constructor + /// + /// Reference to the SecondLife client + /// Total number of layers this bake set is + /// composed of + /// Appearance parameters the drive the + /// baking process + public LowerBakeLayer(SecondLife client, int totalLayers, Dictionary paramValues) + : base(client, totalLayers, paramValues) + { + } + + /// + /// + /// + protected new void Bake() + { + // FIXME: Iterate through each texture, generate the alpha masks and apply them, + // and combine the masked texture in to the final scratch pad + } + } +} diff --git a/libsecondlife-cs/BitPack.cs b/libsecondlife-cs/BitPack.cs index a11d6e69..404a37e1 100644 --- a/libsecondlife-cs/BitPack.cs +++ b/libsecondlife-cs/BitPack.cs @@ -1,275 +1,275 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; - -namespace libsecondlife -{ - /// - /// Wrapper around a byte array that allows bit to be packed and unpacked - /// one at a time or by a variable amount. Useful for very tightly packed - /// data like LayerData packets - /// - public class BitPack - { - /// - public byte[] Data; - - /// - public int BytePos - { - get - { - if (bytePos != 0 && bitPos == 0) - return bytePos - 1; - else - return bytePos; - } - } - - /// - public int BitPos { get { return bitPos; } } - - - private const int MAX_BITS = 8; - - private int bytePos; - private int bitPos; - - - /// - /// Default constructor, initialize the bit packer / bit unpacker - /// with a byte array and starting position - /// - /// Byte array to pack bits in to or unpack from - /// Starting position in the byte array - public BitPack(byte[] data, int pos) - { - Data = data; - bytePos = pos; - } - - /// - /// Pack a floating point value in to the data - /// - /// Floating point value to pack - public void PackFloat(float data) - { - byte[] input = BitConverter.GetBytes(data); - PackBitArray(input, 32); - } - - /// - /// Pack part or all of an integer in to the data - /// - /// Integer containing the data to pack - /// Number of bits of the integer to pack - public void PackBits(int data, int totalCount) - { - byte[] input = BitConverter.GetBytes(data); - PackBitArray(input, totalCount); - } - - /// - /// Unpacking a floating point value from the data - /// - /// Unpacked floating point value - public float UnpackFloat() - { - byte[] output = UnpackBitsArray(32); - - if (!BitConverter.IsLittleEndian) Array.Reverse(output); - return BitConverter.ToSingle(output, 0); - } - - /// - /// Unpack a variable number of bits from the data in to integer format - /// - /// Number of bits to unpack - /// An integer containing the unpacked bits - /// This function is only useful up to 32 bits - public int UnpackBits(int totalCount) - { - byte[] output = UnpackBitsArray(totalCount); - - if (!BitConverter.IsLittleEndian) Array.Reverse(output); - return BitConverter.ToInt32(output, 0); - } - - /// - /// Unpack a variable number of bits from the data in to unsigned - /// integer format - /// - /// Number of bits to unpack - /// An unsigned integer containing the unpacked bits - /// This function is only useful up to 32 bits - public uint UnpackUBits(int totalCount) - { - byte[] output = UnpackBitsArray(totalCount); - - if (!BitConverter.IsLittleEndian) Array.Reverse(output); - return BitConverter.ToUInt32(output, 0); - } - - public byte UnpackByte() - { - byte[] output = UnpackBitsArray(8); - return output[0]; - } - - public float UnpackFixed(bool signed, int intBits, int fracBits) - { - int minVal; - int maxVal; - int unsignedBits = intBits + fracBits; - int totalBits = unsignedBits; - float fixedVal; - - if (signed) - { - totalBits++; - - minVal = 1 << intBits; - minVal *= -1; - } - maxVal = 1 << intBits; - - if (totalBits <= 8) - fixedVal = (float)UnpackByte(); - else if (totalBits <= 16) - fixedVal = (float)UnpackUBits(16); - else if (totalBits <= 31) - fixedVal = (float)UnpackUBits(32); - else - return 0.0f; - - fixedVal /= (float)(1 << fracBits); - - if (signed) fixedVal -= (float)maxVal; - - return fixedVal; - } - - public LLUUID UnpackUUID() - { - if (bitPos != 0) return LLUUID.Zero; - - LLUUID val = new LLUUID(Data, bytePos); - bytePos += 16; - return val; - } - - private void PackBitArray(byte[] data, int totalCount) - { - int count = 0; - int curBytePos = 0; - int curBitPos = 0; - - while (totalCount > 0) - { - if (totalCount > MAX_BITS) - { - count = MAX_BITS; - totalCount -= MAX_BITS; - } - else - { - count = totalCount; - totalCount = 0; - } - - while (count > 0) - { - if ((data[curBytePos] & (0x01 << (count - 1))) != 0) - Data[bytePos] |= (byte)(0x80 >> bitPos); - - --count; - ++bitPos; - ++curBitPos; - - if (bitPos >= MAX_BITS) - { - bitPos = 0; - ++bytePos; - } - if (curBitPos >= MAX_BITS) - { - curBitPos = 0; - ++curBytePos; - } - } - } - } - - private byte[] UnpackBitsArray(int totalCount) - { - int count = 0; - byte[] output = new byte[4]; - int curBytePos = 0; - int curBitPos = 0; - - while (totalCount > 0) - { - if (totalCount > MAX_BITS) - { - count = MAX_BITS; - totalCount -= MAX_BITS; - } - else - { - count = totalCount; - totalCount = 0; - } - - while (count > 0) - { - // Shift the previous bits - output[curBytePos] <<= 1; - - // Grab one bit - if ((Data[bytePos] & (0x80 >> bitPos++)) != 0) - ++output[curBytePos]; - - --count; - ++curBitPos; - - if (bitPos >= MAX_BITS) - { - bitPos = 0; - ++bytePos; - } - if (curBitPos >= MAX_BITS) - { - curBitPos = 0; - ++curBytePos; - } - } - } - - return output; - } - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace libsecondlife +{ + /// + /// Wrapper around a byte array that allows bit to be packed and unpacked + /// one at a time or by a variable amount. Useful for very tightly packed + /// data like LayerData packets + /// + public class BitPack + { + /// + public byte[] Data; + + /// + public int BytePos + { + get + { + if (bytePos != 0 && bitPos == 0) + return bytePos - 1; + else + return bytePos; + } + } + + /// + public int BitPos { get { return bitPos; } } + + + private const int MAX_BITS = 8; + + private int bytePos; + private int bitPos; + + + /// + /// Default constructor, initialize the bit packer / bit unpacker + /// with a byte array and starting position + /// + /// Byte array to pack bits in to or unpack from + /// Starting position in the byte array + public BitPack(byte[] data, int pos) + { + Data = data; + bytePos = pos; + } + + /// + /// Pack a floating point value in to the data + /// + /// Floating point value to pack + public void PackFloat(float data) + { + byte[] input = BitConverter.GetBytes(data); + PackBitArray(input, 32); + } + + /// + /// Pack part or all of an integer in to the data + /// + /// Integer containing the data to pack + /// Number of bits of the integer to pack + public void PackBits(int data, int totalCount) + { + byte[] input = BitConverter.GetBytes(data); + PackBitArray(input, totalCount); + } + + /// + /// Unpacking a floating point value from the data + /// + /// Unpacked floating point value + public float UnpackFloat() + { + byte[] output = UnpackBitsArray(32); + + if (!BitConverter.IsLittleEndian) Array.Reverse(output); + return BitConverter.ToSingle(output, 0); + } + + /// + /// Unpack a variable number of bits from the data in to integer format + /// + /// Number of bits to unpack + /// An integer containing the unpacked bits + /// This function is only useful up to 32 bits + public int UnpackBits(int totalCount) + { + byte[] output = UnpackBitsArray(totalCount); + + if (!BitConverter.IsLittleEndian) Array.Reverse(output); + return BitConverter.ToInt32(output, 0); + } + + /// + /// Unpack a variable number of bits from the data in to unsigned + /// integer format + /// + /// Number of bits to unpack + /// An unsigned integer containing the unpacked bits + /// This function is only useful up to 32 bits + public uint UnpackUBits(int totalCount) + { + byte[] output = UnpackBitsArray(totalCount); + + if (!BitConverter.IsLittleEndian) Array.Reverse(output); + return BitConverter.ToUInt32(output, 0); + } + + public byte UnpackByte() + { + byte[] output = UnpackBitsArray(8); + return output[0]; + } + + public float UnpackFixed(bool signed, int intBits, int fracBits) + { + int minVal; + int maxVal; + int unsignedBits = intBits + fracBits; + int totalBits = unsignedBits; + float fixedVal; + + if (signed) + { + totalBits++; + + minVal = 1 << intBits; + minVal *= -1; + } + maxVal = 1 << intBits; + + if (totalBits <= 8) + fixedVal = (float)UnpackByte(); + else if (totalBits <= 16) + fixedVal = (float)UnpackUBits(16); + else if (totalBits <= 31) + fixedVal = (float)UnpackUBits(32); + else + return 0.0f; + + fixedVal /= (float)(1 << fracBits); + + if (signed) fixedVal -= (float)maxVal; + + return fixedVal; + } + + public LLUUID UnpackUUID() + { + if (bitPos != 0) return LLUUID.Zero; + + LLUUID val = new LLUUID(Data, bytePos); + bytePos += 16; + return val; + } + + private void PackBitArray(byte[] data, int totalCount) + { + int count = 0; + int curBytePos = 0; + int curBitPos = 0; + + while (totalCount > 0) + { + if (totalCount > MAX_BITS) + { + count = MAX_BITS; + totalCount -= MAX_BITS; + } + else + { + count = totalCount; + totalCount = 0; + } + + while (count > 0) + { + if ((data[curBytePos] & (0x01 << (count - 1))) != 0) + Data[bytePos] |= (byte)(0x80 >> bitPos); + + --count; + ++bitPos; + ++curBitPos; + + if (bitPos >= MAX_BITS) + { + bitPos = 0; + ++bytePos; + } + if (curBitPos >= MAX_BITS) + { + curBitPos = 0; + ++curBytePos; + } + } + } + } + + private byte[] UnpackBitsArray(int totalCount) + { + int count = 0; + byte[] output = new byte[4]; + int curBytePos = 0; + int curBitPos = 0; + + while (totalCount > 0) + { + if (totalCount > MAX_BITS) + { + count = MAX_BITS; + totalCount -= MAX_BITS; + } + else + { + count = totalCount; + totalCount = 0; + } + + while (count > 0) + { + // Shift the previous bits + output[curBytePos] <<= 1; + + // Grab one bit + if ((Data[bytePos] & (0x80 >> bitPos++)) != 0) + ++output[curBytePos]; + + --count; + ++curBitPos; + + if (bitPos >= MAX_BITS) + { + bitPos = 0; + ++bytePos; + } + if (curBitPos >= MAX_BITS) + { + curBitPos = 0; + ++curBytePos; + } + } + } + + return output; + } + } +} diff --git a/libsecondlife-cs/Caps.cs b/libsecondlife-cs/Caps.cs index 45bbe75c..4a76bf3c 100644 --- a/libsecondlife-cs/Caps.cs +++ b/libsecondlife-cs/Caps.cs @@ -1,232 +1,232 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Threading; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Net; -using System.IO; - -namespace libsecondlife -{ - /// - /// Capabilities is the name of the bi-directional HTTP REST protocol that - /// Second Life uses to communicate transactions such as teleporting or - /// asset transfers - /// - public class Caps - { - /// - /// Triggered when an event is received via the EventQueueGet capability; - /// - /// - /// - public delegate void EventQueueCallback(string message, object body); - - /// Reference to the SecondLife client this system is connected to - public SecondLife Client; - /// Reference to the simulator this system is connected to - public Simulator Simulator; - - /// - public string SeedCapsURI { get { return Seedcaps; } } - - internal bool Dead = false; - internal string Seedcaps; - - private StringDictionary Capabilities = new StringDictionary(); - private Thread CapsThread; - private string EventQueueCap = String.Empty; - private WebRequest EventQueueRequest = null; - - /// - /// Default constructor - /// - /// - /// - /// - /// - internal Caps(SecondLife client, Simulator simulator, string seedcaps) - { - Client = client; - Simulator = simulator; - Seedcaps = seedcaps; - - CapsThread = new Thread(new ThreadStart(Run)); - CapsThread.Start(); - } - - /// - /// - /// - /// - internal void Disconnect(bool immediate) - { - Dead = true; - - if (immediate && EventQueueRequest != null) - EventQueueRequest.Abort(); - } - - private void Run() - { - byte[] buffer = null; - ArrayList req = new ArrayList(); - req.Add("MapLayer"); - req.Add("MapLayerGod"); - req.Add("NewAgentInventory"); - req.Add("EventQueueGet"); - byte[] data = LLSD.LLSDSerialize(req); - - MakeRequest: - - try - { - WebRequest request = WebRequest.Create(Seedcaps); - request.Method = "POST"; - request.ContentLength = data.Length; - - Stream reqStream = request.GetRequestStream(); - reqStream.Write(data, 0, data.Length); - reqStream.Close(); - - WebResponse response = request.GetResponse(); - BinaryReader reader = new BinaryReader(response.GetResponseStream()); - buffer = reader.ReadBytes((int)response.ContentLength); - response.Close(); - } - catch (WebException e) - { - // Thank you .NET creators for giving us such a piss-poor way of checking for HTTP errors - if (e.Message.Contains("404")) - { - // This capability no longer exists, disable it - Disconnect(true); - return; - } - - Client.Log("CAPS initialization error: " + e.Message + ", retrying", Helpers.LogLevel.Warning); - goto MakeRequest; - } - - Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); - - foreach (string cap in resp.Keys) - { - Client.DebugLog(String.Format("Got cap {0}: {1}", cap, (string)resp[cap])); - Capabilities[cap] = (string)resp[cap]; - } - - if (Capabilities.ContainsKey("EventQueueGet")) - { - EventQueueCap = Capabilities["EventQueueGet"]; - Client.Log("Running event queue", Helpers.LogLevel.Info); - EventQueueHandler(null); - } - } - - private void EventQueueHandler(IAsyncResult result) - { - byte[] buffer = null; - long ack = 0; - - // Special handler for the first request - if (result == null) goto MakeRequest; - - try - { - HttpWebResponse response = (HttpWebResponse)EventQueueRequest.EndGetResponse(result); - BinaryReader reader = new BinaryReader(response.GetResponseStream()); - buffer = reader.ReadBytes((int)response.ContentLength); - response.Close(); - } - catch (WebException e) - { - string extstring=e.Message; - if (e.Message.IndexOf("502") < 0) - Client.DebugLog("EventQueue response: " + e.Message); - } - - if (buffer != null) - { - lock (Client.Network.EventQueueCallbacks) - { - Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); - ArrayList events = (ArrayList)resp["events"]; - ack = (long)resp["id"]; - - foreach (Hashtable evt in events) - { - string msg = (string)evt["message"]; - object body = (object)evt["body"]; - - Client.DebugLog("Event " + msg + ":" + Environment.NewLine + LLSD.LLSDDump(body, 0)); - - for (int i = 0; i < Client.Network.EventQueueCallbacks.Count; i++) - { - try { Client.Network.EventQueueCallbacks[i](msg, body); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - } - } - - MakeRequest: - - // Make a new request - Hashtable req = new Hashtable(); - if (ack != 0) req["ack"] = ack; - else req["ack"] = null; - req["done"] = Dead; - - byte[] data = LLSD.LLSDSerialize(req); - - try - { - EventQueueRequest = WebRequest.Create(EventQueueCap); - EventQueueRequest.Method = "POST"; - EventQueueRequest.ContentLength = data.Length; - - Stream reqStream = EventQueueRequest.GetRequestStream(); - reqStream.Write(data, 0, data.Length); - reqStream.Close(); - - if (!Dead) - EventQueueRequest.BeginGetResponse(new AsyncCallback(EventQueueHandler), EventQueueRequest); - else - EventQueueRequest.BeginGetResponse(null, EventQueueRequest); - } - catch (WebException e) - { - Client.DebugLog("EventQueue request: " + e.Message); - // If the CAPS system is shutting down don't bother trying too hard - if (!Dead) goto MakeRequest; - } - } - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Threading; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net; +using System.IO; + +namespace libsecondlife +{ + /// + /// Capabilities is the name of the bi-directional HTTP REST protocol that + /// Second Life uses to communicate transactions such as teleporting or + /// asset transfers + /// + public class Caps + { + /// + /// Triggered when an event is received via the EventQueueGet capability; + /// + /// + /// + public delegate void EventQueueCallback(string message, object body); + + /// Reference to the SecondLife client this system is connected to + public SecondLife Client; + /// Reference to the simulator this system is connected to + public Simulator Simulator; + + /// + public string SeedCapsURI { get { return Seedcaps; } } + + internal bool Dead = false; + internal string Seedcaps; + + private StringDictionary Capabilities = new StringDictionary(); + private Thread CapsThread; + private string EventQueueCap = String.Empty; + private WebRequest EventQueueRequest = null; + + /// + /// Default constructor + /// + /// + /// + /// + /// + internal Caps(SecondLife client, Simulator simulator, string seedcaps) + { + Client = client; + Simulator = simulator; + Seedcaps = seedcaps; + + CapsThread = new Thread(new ThreadStart(Run)); + CapsThread.Start(); + } + + /// + /// + /// + /// + internal void Disconnect(bool immediate) + { + Dead = true; + + if (immediate && EventQueueRequest != null) + EventQueueRequest.Abort(); + } + + private void Run() + { + byte[] buffer = null; + ArrayList req = new ArrayList(); + req.Add("MapLayer"); + req.Add("MapLayerGod"); + req.Add("NewAgentInventory"); + req.Add("EventQueueGet"); + byte[] data = LLSD.LLSDSerialize(req); + + MakeRequest: + + try + { + WebRequest request = WebRequest.Create(Seedcaps); + request.Method = "POST"; + request.ContentLength = data.Length; + + Stream reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + + WebResponse response = request.GetResponse(); + BinaryReader reader = new BinaryReader(response.GetResponseStream()); + buffer = reader.ReadBytes((int)response.ContentLength); + response.Close(); + } + catch (WebException e) + { + // Thank you .NET creators for giving us such a piss-poor way of checking for HTTP errors + if (e.Message.Contains("404")) + { + // This capability no longer exists, disable it + Disconnect(true); + return; + } + + Client.Log("CAPS initialization error: " + e.Message + ", retrying", Helpers.LogLevel.Warning); + goto MakeRequest; + } + + Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); + + foreach (string cap in resp.Keys) + { + Client.DebugLog(String.Format("Got cap {0}: {1}", cap, (string)resp[cap])); + Capabilities[cap] = (string)resp[cap]; + } + + if (Capabilities.ContainsKey("EventQueueGet")) + { + EventQueueCap = Capabilities["EventQueueGet"]; + Client.Log("Running event queue", Helpers.LogLevel.Info); + EventQueueHandler(null); + } + } + + private void EventQueueHandler(IAsyncResult result) + { + byte[] buffer = null; + long ack = 0; + + // Special handler for the first request + if (result == null) goto MakeRequest; + + try + { + HttpWebResponse response = (HttpWebResponse)EventQueueRequest.EndGetResponse(result); + BinaryReader reader = new BinaryReader(response.GetResponseStream()); + buffer = reader.ReadBytes((int)response.ContentLength); + response.Close(); + } + catch (WebException e) + { + string extstring=e.Message; + if (e.Message.IndexOf("502") < 0) + Client.DebugLog("EventQueue response: " + e.Message); + } + + if (buffer != null) + { + lock (Client.Network.EventQueueCallbacks) + { + Hashtable resp = (Hashtable)LLSD.LLSDDeserialize(buffer); + ArrayList events = (ArrayList)resp["events"]; + ack = (long)resp["id"]; + + foreach (Hashtable evt in events) + { + string msg = (string)evt["message"]; + object body = (object)evt["body"]; + + Client.DebugLog("Event " + msg + ":" + Environment.NewLine + LLSD.LLSDDump(body, 0)); + + for (int i = 0; i < Client.Network.EventQueueCallbacks.Count; i++) + { + try { Client.Network.EventQueueCallbacks[i](msg, body); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + } + } + + MakeRequest: + + // Make a new request + Hashtable req = new Hashtable(); + if (ack != 0) req["ack"] = ack; + else req["ack"] = null; + req["done"] = Dead; + + byte[] data = LLSD.LLSDSerialize(req); + + try + { + EventQueueRequest = WebRequest.Create(EventQueueCap); + EventQueueRequest.Method = "POST"; + EventQueueRequest.ContentLength = data.Length; + + Stream reqStream = EventQueueRequest.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + + if (!Dead) + EventQueueRequest.BeginGetResponse(new AsyncCallback(EventQueueHandler), EventQueueRequest); + else + EventQueueRequest.BeginGetResponse(null, EventQueueRequest); + } + catch (WebException e) + { + Client.DebugLog("EventQueue request: " + e.Message); + // If the CAPS system is shutting down don't bother trying too hard + if (!Dead) goto MakeRequest; + } + } + } +} diff --git a/libsecondlife-cs/DirectoryManager.cs b/libsecondlife-cs/DirectoryManager.cs index 5a03eb4c..d05c4973 100644 --- a/libsecondlife-cs/DirectoryManager.cs +++ b/libsecondlife-cs/DirectoryManager.cs @@ -1,384 +1,384 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Threading; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// - /// - public class DirectoryManager - { - /// - /// The different categories a classified ad can be placed in - /// - public enum ClassifiedCategories - { - /// - Any = 0, - /// - Shopping, - /// - LandRental, - /// - PropertyRental, - /// - SpecialAttraction, - /// - NewProducts, - /// - Employment, - /// - Wanted, - /// - Service, - /// - Personal - } - - /// - /// - /// - [Flags] - public enum DirFindFlags - { - /// - People = 1 << 0, - /// - Online = 1 << 1, - /// - [Obsolete] - Places = 1 << 2, - /// - Events = 1 << 3, - /// - Groups = 1 << 4, - /// - DateEvents = 1 << 5, - /// - AgentOwned = 1 << 6, - /// - ForSale = 1 << 7, - /// - GroupOwned = 1 << 8, - /// - [Obsolete] - Auction = 1 << 9, - /// - DwellSort = 1 << 10, - /// - PgSimsOnly = 1 << 11, - /// - PicturesOnly = 1 << 12, - /// - PgEventsOnly = 1 << 13, - /// - MatureSimsOnly = 1 << 14, - /// - SortAsc = 1 << 15, - /// - PricesSort = 1 << 16, - /// - PerMeterSort = 1 << 17, - /// - AreaSort = 1 << 18, - /// - NameSort = 1 << 19, - /// - LimitByPrice = 1 << 20, - /// - LimitByArea = 1 << 21 - } - - /// - /// - /// - [Flags] - public enum SearchTypeFlags - { - /// - None = 0, - /// - Auction = 1 << 1, - /// - Newbie = 1 << 2, - /// - Mainland = 1 << 3, - /// - Estate = 1 << 4 - } - - - /// - /// A classified ad in Second Life - /// - public struct Classified - { - /// UUID for this ad, useful for looking up detailed - /// information about it - public LLUUID ID; - /// The title of this classified ad - public string Name; - /// Unknown - public byte Flags; - /// Creation date of the ad - public DateTime CreationDate; - /// Expiration date of the ad - public DateTime ExpirationDate; - /// Price that was paid for this ad - public int Price; - } - - /// - /// A parcel retrieved from the dataserver such as results from the - /// "For-Sale" listings - /// - public struct DirectoryParcel - { - /// - public LLUUID ID; - /// - public string Name; - /// - public int ActualArea; - /// - public int SalePrice; - /// - public bool Auction; - /// - public bool ForSale; - /// - public bool ReservedNewbie; - } - - /*/// - public LLUUID OwnerID; - /// - public LLUUID SnapshotID; - /// - public ulong RegionHandle; - /// - public string SimName; - /// - public string Desc; - /// - public LLVector3 GlobalPosition; - /// - public LLVector3 SimPosition; - /// - public float Dwell;*/ - - - /// - /// - /// - /// - public delegate void ClassifiedReplyCallback(List classifieds); - /// - /// - /// - /// - public delegate void DirLandReplyCallback(List dirParcels); - - - /// - /// - /// - public event ClassifiedReplyCallback OnClassifiedReply; - /// - /// - /// - public event DirLandReplyCallback OnDirLandReply; - - - private SecondLife Client; - - - public DirectoryManager(SecondLife client) - { - Client = client; - - Client.Network.RegisterCallback(PacketType.DirClassifiedReply, new NetworkManager.PacketCallback(DirClassifiedReplyHandler)); - Client.Network.RegisterCallback(PacketType.DirLandReply, new NetworkManager.PacketCallback(DirLandReplyHandler)); - } - - public LLUUID StartClassifiedSearch(string searchText, ClassifiedCategories categories, bool mature) - { - DirClassifiedQueryPacket query = new DirClassifiedQueryPacket(); - LLUUID queryID = LLUUID.Random(); - - query.AgentData.AgentID = Client.Network.AgentID; - query.AgentData.SessionID = Client.Network.SessionID; - query.QueryData.Category = (uint)categories; - query.QueryData.QueryFlags = (uint)(mature ? 0 : 2); - query.QueryData.QueryID = queryID; - query.QueryData.QueryText = Helpers.StringToField(searchText); - - Client.Network.SendPacket(query); - - return queryID; - } - - /// - /// Starts a search for land sales using the directory - /// - /// What type of land to search for. Auction, - /// estate, mainland, "first land", etc - /// A unique identifier that can identify packets associated - /// with this query from other queries - /// The OnDirLandReply event handler must be registered before - /// calling this function. There is no way to determine how many - /// results will be returned, or how many times the callback will be - /// fired other than you won't get more than 100 total parcels from - /// each query. - public LLUUID StartLandSearch(SearchTypeFlags typeFlags) - { - return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0); - } - - /// - /// Starts a search for land sales using the directory - /// - /// What type of land to search for. Auction, - /// estate, mainland, "first land", etc - /// Maximum price to search for - /// Maximum area to search for - /// Each request is limited to 100 parcels - /// being returned. To get the first 100 parcels of a request use 0, - /// from 100-199 use 1, 200-299 use 2, etc. - /// A unique identifier that can identify packets associated - /// with this query from other queries - /// The OnDirLandReply event handler must be registered before - /// calling this function. There is no way to determine how many - /// results will be returned, or how many times the callback will be - /// fired other than you won't get more than 100 total parcels from - /// each query. - public LLUUID StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) - { - return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice | - DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart); - } - - /// - /// Starts a search for land sales using the directory - /// - /// A flags parameter that can modify the way - /// search results are returned, for example changing the ordering of - /// results or limiting based on price or area - /// What type of land to search for. Auction, - /// estate, mainland, "first land", etc - /// Maximum price to search for, the - /// DirFindFlags.LimitByPrice flag must be set - /// Maximum area to search for, the - /// DirFindFlags.LimitByArea flag must be set - /// Each request is limited to 100 parcels - /// being returned. To get the first 100 parcels of a request use 0, - /// from 100-199 use 1, 200-299 use 2, etc. - /// A unique identifier that can identify packets associated - /// with this query from other queries - /// The OnDirLandReply event handler must be registered before - /// calling this function. There is no way to determine how many - /// results will be returned, or how many times the callback will be - /// fired other than you won't get more than 100 total parcels from - /// each query. - public LLUUID StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit, - int areaLimit, int queryStart) - { - LLUUID queryID = LLUUID.Random(); - - DirLandQueryPacket query = new DirLandQueryPacket(); - query.AgentData.AgentID = Client.Network.AgentID; - query.AgentData.SessionID = Client.Network.SessionID; - query.QueryData.Area = areaLimit; - query.QueryData.Price = priceLimit; - query.QueryData.QueryStart = queryStart; - query.QueryData.SearchType = (uint)typeFlags; - query.QueryData.QueryFlags = (uint)findFlags; - query.QueryData.QueryID = queryID; - - Client.Network.SendPacket(query); - - return queryID; - } - - private void DirClassifiedReplyHandler(Packet packet, Simulator simulator) - { - if (OnClassifiedReply != null) - { - DirClassifiedReplyPacket reply = (DirClassifiedReplyPacket)packet; - List classifieds = new List(); - - foreach (DirClassifiedReplyPacket.QueryRepliesBlock block in reply.QueryReplies) - { - Classified classified = new Classified(); - - classified.CreationDate = Helpers.UnixTimeToDateTime(block.CreationDate); - classified.ExpirationDate = Helpers.UnixTimeToDateTime(block.ExpirationDate); - classified.Flags = block.ClassifiedFlags; - classified.ID = block.ClassifiedID; - classified.Name = Helpers.FieldToString(block.Name); - classified.Price = block.PriceForListing; - - classifieds.Add(classified); - } - - try { OnClassifiedReply(classifieds); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - - private void DirLandReplyHandler(Packet packet, Simulator simulator) - { - if (OnDirLandReply != null) - { - List parcelsForSale = new List(); - DirLandReplyPacket reply = (DirLandReplyPacket)packet; - - foreach (DirLandReplyPacket.QueryRepliesBlock block in reply.QueryReplies) - { - DirectoryParcel dirParcel = new DirectoryParcel(); - - dirParcel.ActualArea = block.ActualArea; - dirParcel.ID = block.ParcelID; - dirParcel.Name = Helpers.FieldToString(block.Name); - dirParcel.SalePrice = block.SalePrice; - dirParcel.Auction = block.Auction; - dirParcel.ForSale = block.ForSale; - dirParcel.ReservedNewbie = block.ReservedNewbie; - - parcelsForSale.Add(dirParcel); - } - - try { OnDirLandReply(parcelsForSale); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Threading; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// + /// + public class DirectoryManager + { + /// + /// The different categories a classified ad can be placed in + /// + public enum ClassifiedCategories + { + /// + Any = 0, + /// + Shopping, + /// + LandRental, + /// + PropertyRental, + /// + SpecialAttraction, + /// + NewProducts, + /// + Employment, + /// + Wanted, + /// + Service, + /// + Personal + } + + /// + /// + /// + [Flags] + public enum DirFindFlags + { + /// + People = 1 << 0, + /// + Online = 1 << 1, + /// + [Obsolete] + Places = 1 << 2, + /// + Events = 1 << 3, + /// + Groups = 1 << 4, + /// + DateEvents = 1 << 5, + /// + AgentOwned = 1 << 6, + /// + ForSale = 1 << 7, + /// + GroupOwned = 1 << 8, + /// + [Obsolete] + Auction = 1 << 9, + /// + DwellSort = 1 << 10, + /// + PgSimsOnly = 1 << 11, + /// + PicturesOnly = 1 << 12, + /// + PgEventsOnly = 1 << 13, + /// + MatureSimsOnly = 1 << 14, + /// + SortAsc = 1 << 15, + /// + PricesSort = 1 << 16, + /// + PerMeterSort = 1 << 17, + /// + AreaSort = 1 << 18, + /// + NameSort = 1 << 19, + /// + LimitByPrice = 1 << 20, + /// + LimitByArea = 1 << 21 + } + + /// + /// + /// + [Flags] + public enum SearchTypeFlags + { + /// + None = 0, + /// + Auction = 1 << 1, + /// + Newbie = 1 << 2, + /// + Mainland = 1 << 3, + /// + Estate = 1 << 4 + } + + + /// + /// A classified ad in Second Life + /// + public struct Classified + { + /// UUID for this ad, useful for looking up detailed + /// information about it + public LLUUID ID; + /// The title of this classified ad + public string Name; + /// Unknown + public byte Flags; + /// Creation date of the ad + public DateTime CreationDate; + /// Expiration date of the ad + public DateTime ExpirationDate; + /// Price that was paid for this ad + public int Price; + } + + /// + /// A parcel retrieved from the dataserver such as results from the + /// "For-Sale" listings + /// + public struct DirectoryParcel + { + /// + public LLUUID ID; + /// + public string Name; + /// + public int ActualArea; + /// + public int SalePrice; + /// + public bool Auction; + /// + public bool ForSale; + /// + public bool ReservedNewbie; + } + + /*/// + public LLUUID OwnerID; + /// + public LLUUID SnapshotID; + /// + public ulong RegionHandle; + /// + public string SimName; + /// + public string Desc; + /// + public LLVector3 GlobalPosition; + /// + public LLVector3 SimPosition; + /// + public float Dwell;*/ + + + /// + /// + /// + /// + public delegate void ClassifiedReplyCallback(List classifieds); + /// + /// + /// + /// + public delegate void DirLandReplyCallback(List dirParcels); + + + /// + /// + /// + public event ClassifiedReplyCallback OnClassifiedReply; + /// + /// + /// + public event DirLandReplyCallback OnDirLandReply; + + + private SecondLife Client; + + + public DirectoryManager(SecondLife client) + { + Client = client; + + Client.Network.RegisterCallback(PacketType.DirClassifiedReply, new NetworkManager.PacketCallback(DirClassifiedReplyHandler)); + Client.Network.RegisterCallback(PacketType.DirLandReply, new NetworkManager.PacketCallback(DirLandReplyHandler)); + } + + public LLUUID StartClassifiedSearch(string searchText, ClassifiedCategories categories, bool mature) + { + DirClassifiedQueryPacket query = new DirClassifiedQueryPacket(); + LLUUID queryID = LLUUID.Random(); + + query.AgentData.AgentID = Client.Network.AgentID; + query.AgentData.SessionID = Client.Network.SessionID; + query.QueryData.Category = (uint)categories; + query.QueryData.QueryFlags = (uint)(mature ? 0 : 2); + query.QueryData.QueryID = queryID; + query.QueryData.QueryText = Helpers.StringToField(searchText); + + Client.Network.SendPacket(query); + + return queryID; + } + + /// + /// Starts a search for land sales using the directory + /// + /// What type of land to search for. Auction, + /// estate, mainland, "first land", etc + /// A unique identifier that can identify packets associated + /// with this query from other queries + /// The OnDirLandReply event handler must be registered before + /// calling this function. There is no way to determine how many + /// results will be returned, or how many times the callback will be + /// fired other than you won't get more than 100 total parcels from + /// each query. + public LLUUID StartLandSearch(SearchTypeFlags typeFlags) + { + return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort, typeFlags, 0, 0, 0); + } + + /// + /// Starts a search for land sales using the directory + /// + /// What type of land to search for. Auction, + /// estate, mainland, "first land", etc + /// Maximum price to search for + /// Maximum area to search for + /// Each request is limited to 100 parcels + /// being returned. To get the first 100 parcels of a request use 0, + /// from 100-199 use 1, 200-299 use 2, etc. + /// A unique identifier that can identify packets associated + /// with this query from other queries + /// The OnDirLandReply event handler must be registered before + /// calling this function. There is no way to determine how many + /// results will be returned, or how many times the callback will be + /// fired other than you won't get more than 100 total parcels from + /// each query. + public LLUUID StartLandSearch(SearchTypeFlags typeFlags, int priceLimit, int areaLimit, int queryStart) + { + return StartLandSearch(DirFindFlags.SortAsc | DirFindFlags.PerMeterSort | DirFindFlags.LimitByPrice | + DirFindFlags.LimitByArea, typeFlags, priceLimit, areaLimit, queryStart); + } + + /// + /// Starts a search for land sales using the directory + /// + /// A flags parameter that can modify the way + /// search results are returned, for example changing the ordering of + /// results or limiting based on price or area + /// What type of land to search for. Auction, + /// estate, mainland, "first land", etc + /// Maximum price to search for, the + /// DirFindFlags.LimitByPrice flag must be set + /// Maximum area to search for, the + /// DirFindFlags.LimitByArea flag must be set + /// Each request is limited to 100 parcels + /// being returned. To get the first 100 parcels of a request use 0, + /// from 100-199 use 1, 200-299 use 2, etc. + /// A unique identifier that can identify packets associated + /// with this query from other queries + /// The OnDirLandReply event handler must be registered before + /// calling this function. There is no way to determine how many + /// results will be returned, or how many times the callback will be + /// fired other than you won't get more than 100 total parcels from + /// each query. + public LLUUID StartLandSearch(DirFindFlags findFlags, SearchTypeFlags typeFlags, int priceLimit, + int areaLimit, int queryStart) + { + LLUUID queryID = LLUUID.Random(); + + DirLandQueryPacket query = new DirLandQueryPacket(); + query.AgentData.AgentID = Client.Network.AgentID; + query.AgentData.SessionID = Client.Network.SessionID; + query.QueryData.Area = areaLimit; + query.QueryData.Price = priceLimit; + query.QueryData.QueryStart = queryStart; + query.QueryData.SearchType = (uint)typeFlags; + query.QueryData.QueryFlags = (uint)findFlags; + query.QueryData.QueryID = queryID; + + Client.Network.SendPacket(query); + + return queryID; + } + + private void DirClassifiedReplyHandler(Packet packet, Simulator simulator) + { + if (OnClassifiedReply != null) + { + DirClassifiedReplyPacket reply = (DirClassifiedReplyPacket)packet; + List classifieds = new List(); + + foreach (DirClassifiedReplyPacket.QueryRepliesBlock block in reply.QueryReplies) + { + Classified classified = new Classified(); + + classified.CreationDate = Helpers.UnixTimeToDateTime(block.CreationDate); + classified.ExpirationDate = Helpers.UnixTimeToDateTime(block.ExpirationDate); + classified.Flags = block.ClassifiedFlags; + classified.ID = block.ClassifiedID; + classified.Name = Helpers.FieldToString(block.Name); + classified.Price = block.PriceForListing; + + classifieds.Add(classified); + } + + try { OnClassifiedReply(classifieds); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + + private void DirLandReplyHandler(Packet packet, Simulator simulator) + { + if (OnDirLandReply != null) + { + List parcelsForSale = new List(); + DirLandReplyPacket reply = (DirLandReplyPacket)packet; + + foreach (DirLandReplyPacket.QueryRepliesBlock block in reply.QueryReplies) + { + DirectoryParcel dirParcel = new DirectoryParcel(); + + dirParcel.ActualArea = block.ActualArea; + dirParcel.ID = block.ParcelID; + dirParcel.Name = Helpers.FieldToString(block.Name); + dirParcel.SalePrice = block.SalePrice; + dirParcel.Auction = block.Auction; + dirParcel.ForSale = block.ForSale; + dirParcel.ReservedNewbie = block.ReservedNewbie; + + parcelsForSale.Add(dirParcel); + } + + try { OnDirLandReply(parcelsForSale); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + } +} diff --git a/libsecondlife-cs/Helpers.cs b/libsecondlife-cs/Helpers.cs index dd970be6..ecfc6e2f 100644 --- a/libsecondlife-cs/Helpers.cs +++ b/libsecondlife-cs/Helpers.cs @@ -1,945 +1,945 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Xml; -using System.Xml.Serialization; -using System.Text; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Static helper functions and global variables - /// - public class Helpers - { - /// This header flag signals that ACKs are appended to the packet - public const byte MSG_APPENDED_ACKS = 0x10; - /// This header flag signals that this packet has been sent before - public const byte MSG_RESENT = 0x20; - /// This header flags signals that an ACK is expected for this packet - public const byte MSG_RELIABLE = 0x40; - /// This header flag signals that the message is compressed using zerocoding - public const byte MSG_ZEROCODED = 0x80; - /// Used for converting degrees to radians - public const double DEG_TO_RAD = Math.PI / 180.0d; - /// Used for converting radians to degrees - public const double RAD_TO_DEG = 180.0d / Math.PI; - - /// - /// Passed to SecondLife.Log() to identify the severity of a log entry - /// - public enum LogLevel - { - /// Non-noisy useful information, may be helpful in - /// debugging a problem - Info, - /// A non-critical error occurred. A warning will not - /// prevent the rest of libsecondlife from operating as usual, - /// although it may be indicative of an underlying issue - Warning, - /// A critical error has occurred. Generally this will - /// be followed by the network layer shutting down, although the - /// stability of libsecondlife after an error is uncertain - Error, - /// Used for internal testing, this logging level can - /// generate very noisy (long and/or repetitive) messages. Don't - /// pass this to the Log() function, use DebugLog() instead. - /// - Debug - }; - - /// - /// - /// - [Flags] - public enum PermissionWho - { - /// - Group = 4, - /// - Everyone = 8, - /// - NextOwner = 16 - } - - /// - /// - /// - [Flags] - public enum PermissionType - { - /// - Copy = 0x00008000, - /// - Modify = 0x00004000, - /// - Move = 0x00080000, - /// - Transfer = 0x00002000 - } - - /// Provide a single instance of the MD5 class to avoid making - /// duplicate copies - public static System.Security.Cryptography.MD5 MD5Builder = - new System.Security.Cryptography.MD5CryptoServiceProvider(); - - /// - /// Converts an unsigned integer to a hexadecimal string - /// - /// An unsigned integer to convert to a string - /// A hexadecimal string 10 characters long - /// 0x7fffffff - public static string UIntToHexString(uint i) - { - return string.Format("{0:x8}", i); - } - - /// - /// Packs to 32-bit unsigned integers in to a 64-bit unsigned integer - /// - /// The left-hand (or X) value - /// The right-hand (or Y) value - /// A 64-bit integer containing the two 32-bit input values - public static ulong UIntsToLong(uint a, uint b) - { - return (ulong)(((ulong)a << 32) + (ulong)b); - } - - /// - /// Unpacks two 32-bit unsigned integers from a 64-bit unsigned integer - /// - /// The 64-bit input integer - /// The left-hand (or X) output value - /// The right-hand (or Y) output value - public static void LongToUInts(ulong a, out uint b, out uint c) - { - b = (uint)(a >> 32); - c = (uint)(a & 0x00000000FFFFFFFF); - } - - /// - /// Convert an integer to a byte array in little endian format - /// - /// The integer to convert - /// A four byte little endian array - public static byte[] IntToBytes(int x) - { - byte[] bytes = new byte[4]; - - bytes[0]= (byte)(x % 256); - bytes[1] = (byte)((x >> 8) % 256); - bytes[2] = (byte)((x >> 16) % 256); - bytes[3] = (byte)((x >> 24) % 256); - - return bytes; - } - - /// - /// Convert the first two bytes starting at the given position in - /// little endian ordering to an unsigned short - /// - /// Byte array containing the ushort - /// Position to start reading the ushort from - /// An unsigned short, will be zero if a ushort can't be read - /// at the given position - public static ushort BytesToUInt16(byte[] bytes, int pos) - { - if (bytes.Length <= pos + 1) return 0; - return (ushort)(bytes[pos] + (bytes[pos + 1] << 8)); - } - - /// - /// Convert the first four bytes starting at the given position in - /// little endian ordering to an unsigned integer - /// - /// Byte array containing the uint - /// Position to start reading the uint from - /// An unsigned integer, will be zero if a uint can't be read - /// at the given position - public static uint BytesToUInt(byte[] bytes, int pos) - { - if (bytes.Length <= pos + 4) return 0; - return (uint)(bytes[pos + 3] + (bytes[pos + 2] << 8) + (bytes[pos + 1] << 16) + (bytes[pos] << 24)); - } - - /// - /// Convert the first four bytes of the given array in little endian - /// ordering to an unsigned integer - /// - /// An array four bytes or longer - /// An unsigned integer, will be zero if the array contains - /// less than four bytes - public static uint BytesToUInt(byte[] bytes) - { - if (bytes.Length < 4) return 0; - return (uint)(bytes[3] + (bytes[2] << 8) + (bytes[1] << 16) + (bytes[0] << 24)); - } - - /// - /// Convert the first four bytes starting at the given position in - /// big endian ordering to an unsigned integer - /// - /// Byte array containing the uint - /// Position to start reading the uint from - /// An unsigned integer, will be zero if a uint can't be read - /// at the given position - public static uint BytesToUIntBig(byte[] bytes, int pos) - { - if (bytes.Length <= pos + 4) return 0; - return (uint)(bytes[pos] + (bytes[pos + 1] << 8) + (bytes[pos + 2] << 16) + (bytes[pos + 3] << 24)); - } - - /// - /// Convert the first four bytes of the given array in big endian - /// ordering to an unsigned integer - /// - /// An array four bytes or longer - /// An unsigned integer, will be zero if the array contains - /// less than four bytes - public static uint BytesToUIntBig(byte[] bytes) - { - if (bytes.Length < 4) return 0; - return (uint)(bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24)); - } - - /// - /// Convert the first eight bytes of the given array in little endian - /// ordering to an unsigned 64-bit integer - /// - /// An array eight bytes or longer - /// An unsigned 64-bit integer, will be zero if the array - /// contains less than eight bytes - public static ulong BytesToUInt64(byte[] bytes) - { - if (bytes.Length < 8) return 0; - return (ulong) - ((ulong)bytes[7] + - ((ulong)bytes[6] << 8) + - ((ulong)bytes[5] << 16) + - ((ulong)bytes[4] << 24) + - ((ulong)bytes[3] << 32) + - ((ulong)bytes[2] << 40) + - ((ulong)bytes[1] << 48) + - ((ulong)bytes[0] << 56)); - } - - /// - /// Converts a floating point number to a terse string format used for - /// transmitting numbers in wearable asset files - /// - /// Floating point number to convert to a string - /// A terse string representation of the input number - public static string FloatToTerseString(float val) - { - string s = string.Format("{0:.00}", val); - - // Trim trailing zeroes - while (s[s.Length - 1] == '0') - s = s.Remove(s.Length - 1); - - // Remove superfluous decimal places after the trim - if (s[s.Length - 1] == '.') - s = s.Remove(s.Length - 1); - // Remove leading zeroes after a negative sign - else if (s[0] == '-' && s[1] == '0') - s = s.Remove(1, 1); - // Remove leading zeroes in positive numbers - else if (s[0] == '0') - s = s.Remove(0, 1); - - return s; - } - - /// - /// Convert a float value to a byte given a minimum and maximum range - /// - /// Value to convert to a byte - /// Minimum value range - /// Maximum value range - /// A single byte representing the original float value - public static byte FloatToByte(float val, float lower, float upper) - { - val = Clamp(val, lower, upper); - // Normalize the value - val -= lower; - val /= (upper - lower); - - return (byte)Math.Floor(val * (float)byte.MaxValue); - } - - /// - /// Convert a byte to a float value given a minimum and maximum range - /// - /// Byte array to get the byte from - /// Position in the byte array the desired byte is at - /// Minimum value range - /// Maximum value range - /// A float value inclusively between lower and upper - public static float ByteToFloat(byte[] bytes, int pos, float lower, float upper) - { - if (bytes.Length <= pos) return 0; - return ByteToFloat(bytes[pos], lower, upper); - } - - /// - /// Convert a byte to a float value given a minimum and maximum range - /// - /// Byte to convert to a float value - /// Minimum value range - /// Maximum value range - /// A float value inclusively between lower and upper - public static float ByteToFloat(byte val, float lower, float upper) - { - const float ONE_OVER_BYTEMAX = 1.0f / (float)byte.MaxValue; - - float fval = (float)val * ONE_OVER_BYTEMAX; - float delta = (upper - lower); - fval *= delta; - fval += lower; - - // Test for values very close to zero - float error = delta * ONE_OVER_BYTEMAX; - if (Math.Abs(fval) < error) - fval = 0.0f; - - return fval; - } - - /// - /// - /// - /// - /// - /// - /// - /// - public static float UInt16ToFloat(byte[] bytes, int pos, float lower, float upper) - { - ushort val = BytesToUInt16(bytes, pos); - return UInt16ToFloat(val, lower, upper); - } - - /// - /// - /// - /// - /// - /// - /// - public static float UInt16ToFloat(ushort val, float lower, float upper) - { - const float ONE_OVER_U16_MAX = 1.0f / 65535.0f; - - float fval = (float)val * ONE_OVER_U16_MAX; - float delta = upper - lower; - fval *= delta; - fval += lower; - - // Make sure zeroes come through as zero - float maxError = delta * ONE_OVER_U16_MAX; - if (Math.Abs(fval) < maxError) - fval = 0.0f; - - return fval; - } - - /// - /// Clamp a given value between a range - /// - /// Value to clamp - /// Minimum allowable value - /// Maximum allowable value - /// A value inclusively between lower and upper - public static float Clamp(float val, float lower, float upper) - { - return Math.Min(Math.Max(val, lower), upper); - } - - /// - /// Convert a variable length field (byte array) to a UTF8 string - /// - /// The byte array to convert to a string - /// A UTF8 string - public static string FieldToUTF8String(byte[] bytes) - { - if (bytes.Length > 0 && bytes[bytes.Length - 1] == 0x00) - return UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1); - else - return UTF8Encoding.UTF8.GetString(bytes); - } - - /// - /// Convert a variable length field (byte array) to a string - /// - /// If the byte array has unprintable characters in it, a - /// hex dump will be put in the string instead - /// The byte array to convert to a string - /// An ASCII string or a string containing a hex dump, minus - /// the null terminator - public static string FieldToString(byte[] bytes) - { - return FieldToString(bytes, String.Empty); - } - - /// - /// Convert a variable length field (byte array) to a string, with a - /// field name prepended to each line of the output - /// - /// If the byte array has unprintable characters in it, a - /// hex dump will be put in the string instead - /// The byte array to convert to a string - /// A field name to prepend to each line of output - /// An ASCII string or a string containing a hex dump, minus - /// the null terminator - public static string FieldToString(byte[] bytes, string fieldName) - { - // Check for a common case - if (bytes.Length == 0) return String.Empty; - - StringBuilder output = new StringBuilder(); - bool printable = true; - - for (int i = 0; i < bytes.Length; ++i) - { - // Check if there are any unprintable characters in the array - if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09 - && bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00) - { - printable = false; - break; - } - } - - if (printable) - { - if (fieldName.Length > 0) - { - output.Append(fieldName); - output.Append(": "); - } - - if (bytes[bytes.Length - 1] == 0x00) - output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1)); - else - output.Append(UTF8Encoding.UTF8.GetString(bytes)); - } - else - { - for (int i = 0; i < bytes.Length; i += 16) - { - if (i != 0) - output.Append(Environment.NewLine); - if (fieldName.Length > 0) - { - output.Append(fieldName); - output.Append(": "); - } - - for (int j = 0; j < 16; j++) - { - if ((i + j) < bytes.Length) - output.Append(String.Format("{0:X2} ", bytes[i + j])); - else - output.Append(" "); - } - - for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) - { - if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) - output.Append((char)bytes[i + j]); - else - output.Append("."); - } - } - } - - return output.ToString(); - } - - /// - /// Converts a byte array to a string containing hexadecimal characters - /// - /// The byte array to convert to a string - /// The name of the field to prepend to each - /// line of the string - /// A string containing hexadecimal characters on multiple - /// lines. Each line is prepended with the field name - public static string FieldToHexString(byte[] bytes, string fieldName) - { - StringBuilder output = new StringBuilder(); - - for (int i = 0; i < bytes.Length; i += 16) - { - if (i != 0) - output.Append(Environment.NewLine); - if (fieldName.Length > 0) - output.Append(": "); - - for (int j = 0; j < 16; j++) - { - if ((i + j) < bytes.Length) - output.Append(String.Format("{0:X2} ", bytes[i + j])); - else - output.Append(" "); - } - - for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) - { - if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) - output.Append(bytes[i + j]); - else - output.Append("."); - } - } - - return output.ToString(); - } - - ///// - ///// Converts a string containing hexadecimal characters to a byte array - ///// - ///// String containing hexadecimal characters - ///// The converted byte array - //public static byte[] HexStringToField(string hexString) - //{ - // string newString = ""; - // char c; - - // // FIXME: For each line of the string, if a colon is found - // // remove everything before it - - // // remove all non A-F, 0-9, characters - // for (int i = 0; i < hexString.Length; i++) - // { - // c = hexString[i]; - // if (IsHexDigit(c)) - // newString += c; - // } - - // // if odd number of characters, discard last character - // if (newString.Length % 2 != 0) - // { - // newString = newString.Substring(0, newString.Length - 1); - // } - - // int byteLength = newString.Length / 2; - // byte[] bytes = new byte[byteLength]; - // string hex; - // int j = 0; - // for (int i = 0; i < bytes.Length; i++) - // { - // hex = new String(new Char[] { newString[j], newString[j + 1] }); - // bytes[i] = HexToByte(hex); - // j = j + 2; - // } - // return bytes; - //} - - /// - /// Convert a UTF8 string to a byte array - /// - /// The string to convert to a byte array - /// A null-terminated byte array - public static byte[] StringToField(string str) - { - if (str.Length == 0) { return new byte[0]; } - if (!str.EndsWith("\0")) { str += "\0"; } - return System.Text.UTF8Encoding.UTF8.GetBytes(str); - } - - /// - /// Gets a unix timestamp for the current time - /// - /// An unsigned integer representing a unix timestamp for now - public static uint GetUnixTime() - { - return (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds; - } - - /// - /// Convert a unix timestamp to a native DateTime format - /// - /// An unsigned integer representing a unix - /// timestamp - /// A DateTime object containing the same time specified in - /// the given timestamp - public static DateTime UnixTimeToDateTime(uint timestamp) - { - // Make a DateTime equivalent to the UNIX Epoch - System.DateTime dateTime = new System.DateTime(1970, 1, 1, 0, 0, 0, 0); - - // Add the number of seconds in our UNIX timestamp - dateTime = dateTime.AddSeconds(timestamp); - - return dateTime; - } - - /// - /// Converts a vector style rotation to a quaternion - /// - /// Axis rotation, such as 0,0,90 for 90 degrees to the right - /// A quaternion representing the axes of the supplied vector - public static LLQuaternion Axis2Rot(LLVector3 a) - { - if (a.X > 180) a.X -= 360; if (a.Y > 180) a.Y -= 360; if (a.Z > 180) a.Z -= 360; - if (a.X < -180) a.X += 360; if (a.Y < -180) a.Y += 360; if (a.Z < -180) a.Z += 360; - - LLQuaternion rot = LLQuaternion.Identity; - rot.X = (float)(a.X * DEG_TO_RAD); - rot.Y = (float)(a.Y * DEG_TO_RAD); - rot.Z = (float)(a.Z * DEG_TO_RAD); - if (a.Z > 180) rot.W = 0; - - return rot; - } - - /// - /// Calculates the distance between two vectors - /// - public static float VecDist(LLVector3 pointA, LLVector3 pointB) - { - float xd = pointB.X - pointA.X; - float yd = pointB.Y - pointA.Y; - float zd = pointB.Z - pointA.Z; - return (float)Math.Sqrt(xd * xd + yd * yd + zd * zd); - } - - /// - /// Calculate the magnitude of the supplied vector - /// - public static float VecMag(LLVector3 v) - { - return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z); - } - - /// - /// Calculate the magnitude of the supplied quaternion - /// - public static float RotMag(LLQuaternion q) - { - return (float)Math.Sqrt(q.W * q.W + q.X * q.X + q.Y * q.Y + q.Z * q.Z); - } - - /// - /// Return the supplied vector in normalized form - /// - public static LLVector3 VecNorm(LLVector3 vector) - { - float mag = VecMag(vector); - return new LLVector3(vector.X / mag, vector.Y / mag, vector.Z / mag); - } - - /// - /// Calculate the rotation between two vectors - /// - /// Directional vector, such as 1,0,0 for the forward face - /// Target vector - normalize first with VecNorm - public static LLQuaternion RotBetween(LLVector3 a, LLVector3 b) - { - //A and B should both be normalized - //dotProduct is 0 if a and b are perpendicular. I think that's normal? - float dotProduct = (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); - - LLVector3 crossProduct = new LLVector3(); - crossProduct.X = a.Y * b.Z - a.Z * b.Y; - crossProduct.Y = a.Z * b.X - a.X * b.Z; - crossProduct.Z = a.X * b.Y - a.Y * b.X; - - //float scalarProduct = (a.X * b.Y) + (a.Y * b.Z) + (a.Z * b.X); //not used? - float magProduct = VecMag(a) * VecMag(b); - double angle = Math.Acos(dotProduct / magProduct); - - LLVector3 axis = VecNorm(crossProduct); - float s = (float)Math.Sin(angle / 2); - return new LLQuaternion(axis.X * s, axis.Y * s, axis.Z * s, (float)Math.Cos(angle / 2)); - } - - /// - /// Decode a zerocoded byte array, used to decompress packets marked - /// with the zerocoded flag - /// - /// Any time a zero is encountered, the next byte is a count - /// of how many zeroes to expand. One zero is encoded with 0x00 0x01, - /// two zeroes is 0x00 0x02, three zeroes is 0x00 0x03, etc. The - /// first four bytes are copied directly to the output buffer. - /// - /// The byte array to decode - /// The length of the byte array to decode. This - /// would be the length of the packet up to (but not including) any - /// appended ACKs - /// The output byte array to decode to - /// The length of the output buffer - public static int ZeroDecode(byte[] src, int srclen, byte[] dest) - { - uint zerolen = 0; - int bodylen = 0; - uint i = 0; - - try - { - Array.Copy(src, 0, dest, 0, 4); - zerolen = 4; - bodylen = srclen; - - for (i = zerolen; i < bodylen; i++) - { - if (src[i] == 0x00) - { - for (byte j = 0; j < src[i + 1]; j++) - { - dest[zerolen++] = 0x00; - } - - i++; - } - else - { - dest[zerolen++] = src[i]; - } - } - - // Copy appended ACKs - for (; i < srclen; i++) - { - dest[zerolen++] = src[i]; - } - - return (int)zerolen; - } - catch (Exception e) - { - Console.WriteLine("Zerodecoding error: " + Environment.NewLine + - "i=" + i + "srclen=" + srclen + ", bodylen=" + bodylen + ", zerolen=" + zerolen + Environment.NewLine + - FieldToString(src, "src") + Environment.NewLine + - e.ToString()); - } - - return 0; - } - - /// - /// Decode enough of a byte array to get the packet ID. Data before and - /// after the packet ID is undefined. - /// - /// The byte array to decode - /// The output byte array to encode to - public static void ZeroDecodeCommand(byte[] src, byte[] dest) - { - for (int srcPos = 4, destPos = 4; destPos < 8; ++srcPos) - { - if (src[srcPos] == 0x00) - { - for (byte j = 0; j < src[srcPos + 1]; ++j) - { - dest[destPos++] = 0x00; - } - - ++srcPos; - } - else - { - dest[destPos++] = src[srcPos]; - } - } - } - - /// - /// Encode a byte array with zerocoding. Used to compress packets marked - /// with the zerocoded flag. Any zeroes in the array are compressed down - /// to a single zero byte followed by a count of how many zeroes to expand - /// out. A single zero becomes 0x00 0x01, two zeroes becomes 0x00 0x02, - /// three zeroes becomes 0x00 0x03, etc. The first four bytes are copied - /// directly to the output buffer. - /// - /// The byte array to encode - /// The length of the byte array to encode - /// The output byte array to encode to - /// The length of the output buffer - public static int ZeroEncode(byte[] src, int srclen, byte[] dest) - { - uint zerolen = 0; - byte zerocount = 0; - - Array.Copy(src, 0, dest, 0, 4); - zerolen += 4; - - int bodylen; - if ((src[0] & MSG_APPENDED_ACKS) == 0) - { - bodylen = srclen; - } - else - { - bodylen = srclen - src[srclen - 1] * 4 - 1; - } - - uint i; - for (i = zerolen; i < bodylen; i++) - { - if (src[i] == 0x00) - { - zerocount++; - - if (zerocount == 0) - { - dest[zerolen++] = 0x00; - dest[zerolen++] = 0xff; - zerocount++; - } - } - else - { - if (zerocount != 0) - { - dest[zerolen++] = 0x00; - dest[zerolen++] = (byte)zerocount; - zerocount = 0; - } - - dest[zerolen++] = src[i]; - } - } - - if (zerocount != 0) - { - dest[zerolen++] = 0x00; - dest[zerolen++] = (byte)zerocount; - } - - // copy appended ACKs - for (; i < srclen; i++) - { - dest[zerolen++] = src[i]; - } - - return (int)zerolen; - } - - /// - /// Calculates the CRC (cyclic redundancy check) needed to upload inventory. - /// - /// Creation date - /// Sale type - /// Inventory type - /// Type - /// Asset ID - /// Group ID - /// Sale price - /// Owner ID - /// Creator ID - /// Item ID - /// Folder ID - /// Everyone mask (permissions) - /// Flags - /// Next owner mask (permissions) - /// Group mask (permissions) - /// Owner mask (permisions) - /// The calculated CRC - public static uint InventoryCRC(int creationDate, byte saleType, sbyte invType, sbyte type, - LLUUID assetID, LLUUID groupID, int salePrice, LLUUID ownerID, LLUUID creatorID, - LLUUID itemID, LLUUID folderID, uint everyoneMask, uint flags, uint nextOwnerMask, - uint groupMask, uint ownerMask) - { - uint CRC = 0; - - // IDs - CRC += assetID.CRC(); // AssetID - CRC += folderID.CRC(); // FolderID - CRC += itemID.CRC(); // ItemID - - // Permission stuff - CRC += creatorID.CRC(); // CreatorID - CRC += ownerID.CRC(); // OwnerID - CRC += groupID.CRC(); // GroupID - - // CRC += another 4 words which always seem to be zero -- unclear if this is a LLUUID or what - CRC += ownerMask; - CRC += nextOwnerMask; - CRC += everyoneMask; - CRC += groupMask; - - // The rest of the CRC fields - CRC += flags; // Flags - CRC += (uint)invType; // InvType - CRC += (uint)type; // Type - CRC += (uint)creationDate; // CreationDate - CRC += (uint)salePrice; // SalePrice - CRC += (uint)((uint)saleType * 0x07073096); // SaleType - - return CRC; - } - - /// - /// Calculate the MD5 hash of a given string - /// - /// The password to hash - /// An MD5 hash in string format, with $1$ prepended - public static string MD5(string password) - { - StringBuilder digest = new StringBuilder(); - byte[] hash = MD5Builder.ComputeHash(ASCIIEncoding.Default.GetBytes(password)); - - // Convert the hash to a hex string - foreach (byte b in hash) - { - digest.AppendFormat("{0:x2}", b); - } - - return "$1$" + digest.ToString(); - } - - public static void PacketListToXml(List packets, XmlWriter xmlWriter) - { - //XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); - //ns.Add("", ""); - XmlSerializer serializer = new XmlSerializer(typeof(List)); - serializer.Serialize(xmlWriter, packets); - } - - public static void PrimListToXml(List list, XmlWriter xmlWriter) - { - XmlSerializer serializer = new XmlSerializer(typeof(List)); - serializer.Serialize(xmlWriter, list); - } - - public static List PrimListFromXml(XmlReader reader) - { - XmlSerializer serializer = new XmlSerializer(typeof(List)); - object list = serializer.Deserialize(reader); - return (List)list; - } - - public static List PacketListFromXml(XmlReader reader) - { - XmlSerializer serializer = new XmlSerializer(typeof(List)); - object list = serializer.Deserialize(reader); - return (List)list; - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; +using System.Text; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Static helper functions and global variables + /// + public class Helpers + { + /// This header flag signals that ACKs are appended to the packet + public const byte MSG_APPENDED_ACKS = 0x10; + /// This header flag signals that this packet has been sent before + public const byte MSG_RESENT = 0x20; + /// This header flags signals that an ACK is expected for this packet + public const byte MSG_RELIABLE = 0x40; + /// This header flag signals that the message is compressed using zerocoding + public const byte MSG_ZEROCODED = 0x80; + /// Used for converting degrees to radians + public const double DEG_TO_RAD = Math.PI / 180.0d; + /// Used for converting radians to degrees + public const double RAD_TO_DEG = 180.0d / Math.PI; + + /// + /// Passed to SecondLife.Log() to identify the severity of a log entry + /// + public enum LogLevel + { + /// Non-noisy useful information, may be helpful in + /// debugging a problem + Info, + /// A non-critical error occurred. A warning will not + /// prevent the rest of libsecondlife from operating as usual, + /// although it may be indicative of an underlying issue + Warning, + /// A critical error has occurred. Generally this will + /// be followed by the network layer shutting down, although the + /// stability of libsecondlife after an error is uncertain + Error, + /// Used for internal testing, this logging level can + /// generate very noisy (long and/or repetitive) messages. Don't + /// pass this to the Log() function, use DebugLog() instead. + /// + Debug + }; + + /// + /// + /// + [Flags] + public enum PermissionWho + { + /// + Group = 4, + /// + Everyone = 8, + /// + NextOwner = 16 + } + + /// + /// + /// + [Flags] + public enum PermissionType + { + /// + Copy = 0x00008000, + /// + Modify = 0x00004000, + /// + Move = 0x00080000, + /// + Transfer = 0x00002000 + } + + /// Provide a single instance of the MD5 class to avoid making + /// duplicate copies + public static System.Security.Cryptography.MD5 MD5Builder = + new System.Security.Cryptography.MD5CryptoServiceProvider(); + + /// + /// Converts an unsigned integer to a hexadecimal string + /// + /// An unsigned integer to convert to a string + /// A hexadecimal string 10 characters long + /// 0x7fffffff + public static string UIntToHexString(uint i) + { + return string.Format("{0:x8}", i); + } + + /// + /// Packs to 32-bit unsigned integers in to a 64-bit unsigned integer + /// + /// The left-hand (or X) value + /// The right-hand (or Y) value + /// A 64-bit integer containing the two 32-bit input values + public static ulong UIntsToLong(uint a, uint b) + { + return (ulong)(((ulong)a << 32) + (ulong)b); + } + + /// + /// Unpacks two 32-bit unsigned integers from a 64-bit unsigned integer + /// + /// The 64-bit input integer + /// The left-hand (or X) output value + /// The right-hand (or Y) output value + public static void LongToUInts(ulong a, out uint b, out uint c) + { + b = (uint)(a >> 32); + c = (uint)(a & 0x00000000FFFFFFFF); + } + + /// + /// Convert an integer to a byte array in little endian format + /// + /// The integer to convert + /// A four byte little endian array + public static byte[] IntToBytes(int x) + { + byte[] bytes = new byte[4]; + + bytes[0]= (byte)(x % 256); + bytes[1] = (byte)((x >> 8) % 256); + bytes[2] = (byte)((x >> 16) % 256); + bytes[3] = (byte)((x >> 24) % 256); + + return bytes; + } + + /// + /// Convert the first two bytes starting at the given position in + /// little endian ordering to an unsigned short + /// + /// Byte array containing the ushort + /// Position to start reading the ushort from + /// An unsigned short, will be zero if a ushort can't be read + /// at the given position + public static ushort BytesToUInt16(byte[] bytes, int pos) + { + if (bytes.Length <= pos + 1) return 0; + return (ushort)(bytes[pos] + (bytes[pos + 1] << 8)); + } + + /// + /// Convert the first four bytes starting at the given position in + /// little endian ordering to an unsigned integer + /// + /// Byte array containing the uint + /// Position to start reading the uint from + /// An unsigned integer, will be zero if a uint can't be read + /// at the given position + public static uint BytesToUInt(byte[] bytes, int pos) + { + if (bytes.Length <= pos + 4) return 0; + return (uint)(bytes[pos + 3] + (bytes[pos + 2] << 8) + (bytes[pos + 1] << 16) + (bytes[pos] << 24)); + } + + /// + /// Convert the first four bytes of the given array in little endian + /// ordering to an unsigned integer + /// + /// An array four bytes or longer + /// An unsigned integer, will be zero if the array contains + /// less than four bytes + public static uint BytesToUInt(byte[] bytes) + { + if (bytes.Length < 4) return 0; + return (uint)(bytes[3] + (bytes[2] << 8) + (bytes[1] << 16) + (bytes[0] << 24)); + } + + /// + /// Convert the first four bytes starting at the given position in + /// big endian ordering to an unsigned integer + /// + /// Byte array containing the uint + /// Position to start reading the uint from + /// An unsigned integer, will be zero if a uint can't be read + /// at the given position + public static uint BytesToUIntBig(byte[] bytes, int pos) + { + if (bytes.Length <= pos + 4) return 0; + return (uint)(bytes[pos] + (bytes[pos + 1] << 8) + (bytes[pos + 2] << 16) + (bytes[pos + 3] << 24)); + } + + /// + /// Convert the first four bytes of the given array in big endian + /// ordering to an unsigned integer + /// + /// An array four bytes or longer + /// An unsigned integer, will be zero if the array contains + /// less than four bytes + public static uint BytesToUIntBig(byte[] bytes) + { + if (bytes.Length < 4) return 0; + return (uint)(bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24)); + } + + /// + /// Convert the first eight bytes of the given array in little endian + /// ordering to an unsigned 64-bit integer + /// + /// An array eight bytes or longer + /// An unsigned 64-bit integer, will be zero if the array + /// contains less than eight bytes + public static ulong BytesToUInt64(byte[] bytes) + { + if (bytes.Length < 8) return 0; + return (ulong) + ((ulong)bytes[7] + + ((ulong)bytes[6] << 8) + + ((ulong)bytes[5] << 16) + + ((ulong)bytes[4] << 24) + + ((ulong)bytes[3] << 32) + + ((ulong)bytes[2] << 40) + + ((ulong)bytes[1] << 48) + + ((ulong)bytes[0] << 56)); + } + + /// + /// Converts a floating point number to a terse string format used for + /// transmitting numbers in wearable asset files + /// + /// Floating point number to convert to a string + /// A terse string representation of the input number + public static string FloatToTerseString(float val) + { + string s = string.Format("{0:.00}", val); + + // Trim trailing zeroes + while (s[s.Length - 1] == '0') + s = s.Remove(s.Length - 1); + + // Remove superfluous decimal places after the trim + if (s[s.Length - 1] == '.') + s = s.Remove(s.Length - 1); + // Remove leading zeroes after a negative sign + else if (s[0] == '-' && s[1] == '0') + s = s.Remove(1, 1); + // Remove leading zeroes in positive numbers + else if (s[0] == '0') + s = s.Remove(0, 1); + + return s; + } + + /// + /// Convert a float value to a byte given a minimum and maximum range + /// + /// Value to convert to a byte + /// Minimum value range + /// Maximum value range + /// A single byte representing the original float value + public static byte FloatToByte(float val, float lower, float upper) + { + val = Clamp(val, lower, upper); + // Normalize the value + val -= lower; + val /= (upper - lower); + + return (byte)Math.Floor(val * (float)byte.MaxValue); + } + + /// + /// Convert a byte to a float value given a minimum and maximum range + /// + /// Byte array to get the byte from + /// Position in the byte array the desired byte is at + /// Minimum value range + /// Maximum value range + /// A float value inclusively between lower and upper + public static float ByteToFloat(byte[] bytes, int pos, float lower, float upper) + { + if (bytes.Length <= pos) return 0; + return ByteToFloat(bytes[pos], lower, upper); + } + + /// + /// Convert a byte to a float value given a minimum and maximum range + /// + /// Byte to convert to a float value + /// Minimum value range + /// Maximum value range + /// A float value inclusively between lower and upper + public static float ByteToFloat(byte val, float lower, float upper) + { + const float ONE_OVER_BYTEMAX = 1.0f / (float)byte.MaxValue; + + float fval = (float)val * ONE_OVER_BYTEMAX; + float delta = (upper - lower); + fval *= delta; + fval += lower; + + // Test for values very close to zero + float error = delta * ONE_OVER_BYTEMAX; + if (Math.Abs(fval) < error) + fval = 0.0f; + + return fval; + } + + /// + /// + /// + /// + /// + /// + /// + /// + public static float UInt16ToFloat(byte[] bytes, int pos, float lower, float upper) + { + ushort val = BytesToUInt16(bytes, pos); + return UInt16ToFloat(val, lower, upper); + } + + /// + /// + /// + /// + /// + /// + /// + public static float UInt16ToFloat(ushort val, float lower, float upper) + { + const float ONE_OVER_U16_MAX = 1.0f / 65535.0f; + + float fval = (float)val * ONE_OVER_U16_MAX; + float delta = upper - lower; + fval *= delta; + fval += lower; + + // Make sure zeroes come through as zero + float maxError = delta * ONE_OVER_U16_MAX; + if (Math.Abs(fval) < maxError) + fval = 0.0f; + + return fval; + } + + /// + /// Clamp a given value between a range + /// + /// Value to clamp + /// Minimum allowable value + /// Maximum allowable value + /// A value inclusively between lower and upper + public static float Clamp(float val, float lower, float upper) + { + return Math.Min(Math.Max(val, lower), upper); + } + + /// + /// Convert a variable length field (byte array) to a UTF8 string + /// + /// The byte array to convert to a string + /// A UTF8 string + public static string FieldToUTF8String(byte[] bytes) + { + if (bytes.Length > 0 && bytes[bytes.Length - 1] == 0x00) + return UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1); + else + return UTF8Encoding.UTF8.GetString(bytes); + } + + /// + /// Convert a variable length field (byte array) to a string + /// + /// If the byte array has unprintable characters in it, a + /// hex dump will be put in the string instead + /// The byte array to convert to a string + /// An ASCII string or a string containing a hex dump, minus + /// the null terminator + public static string FieldToString(byte[] bytes) + { + return FieldToString(bytes, String.Empty); + } + + /// + /// Convert a variable length field (byte array) to a string, with a + /// field name prepended to each line of the output + /// + /// If the byte array has unprintable characters in it, a + /// hex dump will be put in the string instead + /// The byte array to convert to a string + /// A field name to prepend to each line of output + /// An ASCII string or a string containing a hex dump, minus + /// the null terminator + public static string FieldToString(byte[] bytes, string fieldName) + { + // Check for a common case + if (bytes.Length == 0) return String.Empty; + + StringBuilder output = new StringBuilder(); + bool printable = true; + + for (int i = 0; i < bytes.Length; ++i) + { + // Check if there are any unprintable characters in the array + if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09 + && bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00) + { + printable = false; + break; + } + } + + if (printable) + { + if (fieldName.Length > 0) + { + output.Append(fieldName); + output.Append(": "); + } + + if (bytes[bytes.Length - 1] == 0x00) + output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1)); + else + output.Append(UTF8Encoding.UTF8.GetString(bytes)); + } + else + { + for (int i = 0; i < bytes.Length; i += 16) + { + if (i != 0) + output.Append(Environment.NewLine); + if (fieldName.Length > 0) + { + output.Append(fieldName); + output.Append(": "); + } + + for (int j = 0; j < 16; j++) + { + if ((i + j) < bytes.Length) + output.Append(String.Format("{0:X2} ", bytes[i + j])); + else + output.Append(" "); + } + + for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) + { + if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) + output.Append((char)bytes[i + j]); + else + output.Append("."); + } + } + } + + return output.ToString(); + } + + /// + /// Converts a byte array to a string containing hexadecimal characters + /// + /// The byte array to convert to a string + /// The name of the field to prepend to each + /// line of the string + /// A string containing hexadecimal characters on multiple + /// lines. Each line is prepended with the field name + public static string FieldToHexString(byte[] bytes, string fieldName) + { + StringBuilder output = new StringBuilder(); + + for (int i = 0; i < bytes.Length; i += 16) + { + if (i != 0) + output.Append(Environment.NewLine); + if (fieldName.Length > 0) + output.Append(": "); + + for (int j = 0; j < 16; j++) + { + if ((i + j) < bytes.Length) + output.Append(String.Format("{0:X2} ", bytes[i + j])); + else + output.Append(" "); + } + + for (int j = 0; j < 16 && (i + j) < bytes.Length; j++) + { + if (bytes[i + j] >= 0x20 && bytes[i + j] < 0x7E) + output.Append(bytes[i + j]); + else + output.Append("."); + } + } + + return output.ToString(); + } + + ///// + ///// Converts a string containing hexadecimal characters to a byte array + ///// + ///// String containing hexadecimal characters + ///// The converted byte array + //public static byte[] HexStringToField(string hexString) + //{ + // string newString = ""; + // char c; + + // // FIXME: For each line of the string, if a colon is found + // // remove everything before it + + // // remove all non A-F, 0-9, characters + // for (int i = 0; i < hexString.Length; i++) + // { + // c = hexString[i]; + // if (IsHexDigit(c)) + // newString += c; + // } + + // // if odd number of characters, discard last character + // if (newString.Length % 2 != 0) + // { + // newString = newString.Substring(0, newString.Length - 1); + // } + + // int byteLength = newString.Length / 2; + // byte[] bytes = new byte[byteLength]; + // string hex; + // int j = 0; + // for (int i = 0; i < bytes.Length; i++) + // { + // hex = new String(new Char[] { newString[j], newString[j + 1] }); + // bytes[i] = HexToByte(hex); + // j = j + 2; + // } + // return bytes; + //} + + /// + /// Convert a UTF8 string to a byte array + /// + /// The string to convert to a byte array + /// A null-terminated byte array + public static byte[] StringToField(string str) + { + if (str.Length == 0) { return new byte[0]; } + if (!str.EndsWith("\0")) { str += "\0"; } + return System.Text.UTF8Encoding.UTF8.GetBytes(str); + } + + /// + /// Gets a unix timestamp for the current time + /// + /// An unsigned integer representing a unix timestamp for now + public static uint GetUnixTime() + { + return (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds; + } + + /// + /// Convert a unix timestamp to a native DateTime format + /// + /// An unsigned integer representing a unix + /// timestamp + /// A DateTime object containing the same time specified in + /// the given timestamp + public static DateTime UnixTimeToDateTime(uint timestamp) + { + // Make a DateTime equivalent to the UNIX Epoch + System.DateTime dateTime = new System.DateTime(1970, 1, 1, 0, 0, 0, 0); + + // Add the number of seconds in our UNIX timestamp + dateTime = dateTime.AddSeconds(timestamp); + + return dateTime; + } + + /// + /// Converts a vector style rotation to a quaternion + /// + /// Axis rotation, such as 0,0,90 for 90 degrees to the right + /// A quaternion representing the axes of the supplied vector + public static LLQuaternion Axis2Rot(LLVector3 a) + { + if (a.X > 180) a.X -= 360; if (a.Y > 180) a.Y -= 360; if (a.Z > 180) a.Z -= 360; + if (a.X < -180) a.X += 360; if (a.Y < -180) a.Y += 360; if (a.Z < -180) a.Z += 360; + + LLQuaternion rot = LLQuaternion.Identity; + rot.X = (float)(a.X * DEG_TO_RAD); + rot.Y = (float)(a.Y * DEG_TO_RAD); + rot.Z = (float)(a.Z * DEG_TO_RAD); + if (a.Z > 180) rot.W = 0; + + return rot; + } + + /// + /// Calculates the distance between two vectors + /// + public static float VecDist(LLVector3 pointA, LLVector3 pointB) + { + float xd = pointB.X - pointA.X; + float yd = pointB.Y - pointA.Y; + float zd = pointB.Z - pointA.Z; + return (float)Math.Sqrt(xd * xd + yd * yd + zd * zd); + } + + /// + /// Calculate the magnitude of the supplied vector + /// + public static float VecMag(LLVector3 v) + { + return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z); + } + + /// + /// Calculate the magnitude of the supplied quaternion + /// + public static float RotMag(LLQuaternion q) + { + return (float)Math.Sqrt(q.W * q.W + q.X * q.X + q.Y * q.Y + q.Z * q.Z); + } + + /// + /// Return the supplied vector in normalized form + /// + public static LLVector3 VecNorm(LLVector3 vector) + { + float mag = VecMag(vector); + return new LLVector3(vector.X / mag, vector.Y / mag, vector.Z / mag); + } + + /// + /// Calculate the rotation between two vectors + /// + /// Directional vector, such as 1,0,0 for the forward face + /// Target vector - normalize first with VecNorm + public static LLQuaternion RotBetween(LLVector3 a, LLVector3 b) + { + //A and B should both be normalized + //dotProduct is 0 if a and b are perpendicular. I think that's normal? + float dotProduct = (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); + + LLVector3 crossProduct = new LLVector3(); + crossProduct.X = a.Y * b.Z - a.Z * b.Y; + crossProduct.Y = a.Z * b.X - a.X * b.Z; + crossProduct.Z = a.X * b.Y - a.Y * b.X; + + //float scalarProduct = (a.X * b.Y) + (a.Y * b.Z) + (a.Z * b.X); //not used? + float magProduct = VecMag(a) * VecMag(b); + double angle = Math.Acos(dotProduct / magProduct); + + LLVector3 axis = VecNorm(crossProduct); + float s = (float)Math.Sin(angle / 2); + return new LLQuaternion(axis.X * s, axis.Y * s, axis.Z * s, (float)Math.Cos(angle / 2)); + } + + /// + /// Decode a zerocoded byte array, used to decompress packets marked + /// with the zerocoded flag + /// + /// Any time a zero is encountered, the next byte is a count + /// of how many zeroes to expand. One zero is encoded with 0x00 0x01, + /// two zeroes is 0x00 0x02, three zeroes is 0x00 0x03, etc. The + /// first four bytes are copied directly to the output buffer. + /// + /// The byte array to decode + /// The length of the byte array to decode. This + /// would be the length of the packet up to (but not including) any + /// appended ACKs + /// The output byte array to decode to + /// The length of the output buffer + public static int ZeroDecode(byte[] src, int srclen, byte[] dest) + { + uint zerolen = 0; + int bodylen = 0; + uint i = 0; + + try + { + Array.Copy(src, 0, dest, 0, 4); + zerolen = 4; + bodylen = srclen; + + for (i = zerolen; i < bodylen; i++) + { + if (src[i] == 0x00) + { + for (byte j = 0; j < src[i + 1]; j++) + { + dest[zerolen++] = 0x00; + } + + i++; + } + else + { + dest[zerolen++] = src[i]; + } + } + + // Copy appended ACKs + for (; i < srclen; i++) + { + dest[zerolen++] = src[i]; + } + + return (int)zerolen; + } + catch (Exception e) + { + Console.WriteLine("Zerodecoding error: " + Environment.NewLine + + "i=" + i + "srclen=" + srclen + ", bodylen=" + bodylen + ", zerolen=" + zerolen + Environment.NewLine + + FieldToString(src, "src") + Environment.NewLine + + e.ToString()); + } + + return 0; + } + + /// + /// Decode enough of a byte array to get the packet ID. Data before and + /// after the packet ID is undefined. + /// + /// The byte array to decode + /// The output byte array to encode to + public static void ZeroDecodeCommand(byte[] src, byte[] dest) + { + for (int srcPos = 4, destPos = 4; destPos < 8; ++srcPos) + { + if (src[srcPos] == 0x00) + { + for (byte j = 0; j < src[srcPos + 1]; ++j) + { + dest[destPos++] = 0x00; + } + + ++srcPos; + } + else + { + dest[destPos++] = src[srcPos]; + } + } + } + + /// + /// Encode a byte array with zerocoding. Used to compress packets marked + /// with the zerocoded flag. Any zeroes in the array are compressed down + /// to a single zero byte followed by a count of how many zeroes to expand + /// out. A single zero becomes 0x00 0x01, two zeroes becomes 0x00 0x02, + /// three zeroes becomes 0x00 0x03, etc. The first four bytes are copied + /// directly to the output buffer. + /// + /// The byte array to encode + /// The length of the byte array to encode + /// The output byte array to encode to + /// The length of the output buffer + public static int ZeroEncode(byte[] src, int srclen, byte[] dest) + { + uint zerolen = 0; + byte zerocount = 0; + + Array.Copy(src, 0, dest, 0, 4); + zerolen += 4; + + int bodylen; + if ((src[0] & MSG_APPENDED_ACKS) == 0) + { + bodylen = srclen; + } + else + { + bodylen = srclen - src[srclen - 1] * 4 - 1; + } + + uint i; + for (i = zerolen; i < bodylen; i++) + { + if (src[i] == 0x00) + { + zerocount++; + + if (zerocount == 0) + { + dest[zerolen++] = 0x00; + dest[zerolen++] = 0xff; + zerocount++; + } + } + else + { + if (zerocount != 0) + { + dest[zerolen++] = 0x00; + dest[zerolen++] = (byte)zerocount; + zerocount = 0; + } + + dest[zerolen++] = src[i]; + } + } + + if (zerocount != 0) + { + dest[zerolen++] = 0x00; + dest[zerolen++] = (byte)zerocount; + } + + // copy appended ACKs + for (; i < srclen; i++) + { + dest[zerolen++] = src[i]; + } + + return (int)zerolen; + } + + /// + /// Calculates the CRC (cyclic redundancy check) needed to upload inventory. + /// + /// Creation date + /// Sale type + /// Inventory type + /// Type + /// Asset ID + /// Group ID + /// Sale price + /// Owner ID + /// Creator ID + /// Item ID + /// Folder ID + /// Everyone mask (permissions) + /// Flags + /// Next owner mask (permissions) + /// Group mask (permissions) + /// Owner mask (permisions) + /// The calculated CRC + public static uint InventoryCRC(int creationDate, byte saleType, sbyte invType, sbyte type, + LLUUID assetID, LLUUID groupID, int salePrice, LLUUID ownerID, LLUUID creatorID, + LLUUID itemID, LLUUID folderID, uint everyoneMask, uint flags, uint nextOwnerMask, + uint groupMask, uint ownerMask) + { + uint CRC = 0; + + // IDs + CRC += assetID.CRC(); // AssetID + CRC += folderID.CRC(); // FolderID + CRC += itemID.CRC(); // ItemID + + // Permission stuff + CRC += creatorID.CRC(); // CreatorID + CRC += ownerID.CRC(); // OwnerID + CRC += groupID.CRC(); // GroupID + + // CRC += another 4 words which always seem to be zero -- unclear if this is a LLUUID or what + CRC += ownerMask; + CRC += nextOwnerMask; + CRC += everyoneMask; + CRC += groupMask; + + // The rest of the CRC fields + CRC += flags; // Flags + CRC += (uint)invType; // InvType + CRC += (uint)type; // Type + CRC += (uint)creationDate; // CreationDate + CRC += (uint)salePrice; // SalePrice + CRC += (uint)((uint)saleType * 0x07073096); // SaleType + + return CRC; + } + + /// + /// Calculate the MD5 hash of a given string + /// + /// The password to hash + /// An MD5 hash in string format, with $1$ prepended + public static string MD5(string password) + { + StringBuilder digest = new StringBuilder(); + byte[] hash = MD5Builder.ComputeHash(ASCIIEncoding.Default.GetBytes(password)); + + // Convert the hash to a hex string + foreach (byte b in hash) + { + digest.AppendFormat("{0:x2}", b); + } + + return "$1$" + digest.ToString(); + } + + public static void PacketListToXml(List packets, XmlWriter xmlWriter) + { + //XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + //ns.Add("", ""); + XmlSerializer serializer = new XmlSerializer(typeof(List)); + serializer.Serialize(xmlWriter, packets); + } + + public static void PrimListToXml(List list, XmlWriter xmlWriter) + { + XmlSerializer serializer = new XmlSerializer(typeof(List)); + serializer.Serialize(xmlWriter, list); + } + + public static List PrimListFromXml(XmlReader reader) + { + XmlSerializer serializer = new XmlSerializer(typeof(List)); + object list = serializer.Deserialize(reader); + return (List)list; + } + + public static List PacketListFromXml(XmlReader reader) + { + XmlSerializer serializer = new XmlSerializer(typeof(List)); + object list = serializer.Deserialize(reader); + return (List)list; + } + } +} diff --git a/libsecondlife-cs/LLObject.cs b/libsecondlife-cs/LLObject.cs index 6da3aea6..fdc02a6e 100644 --- a/libsecondlife-cs/LLObject.cs +++ b/libsecondlife-cs/LLObject.cs @@ -1,602 +1,602 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Xml; -using System.Xml.Serialization; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Base class for primitives and avatars - /// - [Serializable] - public abstract partial class LLObject - { - #region Enumerations - - /// - /// Primary parameters for primitives such as Physics Enabled or Phantom - /// - [Flags] - public enum ObjectFlags : uint - { - /// None of the primary flags are enabled - None = 0, - /// Whether physics are enabled for this object - Physics = 0x00000001, - /// - CreateSelected = 0x00000002, - /// - ObjectModify = 0x00000004, - /// - ObjectCopy = 0x00000008, - /// - ObjectAnyOwner = 0x00000010, - /// - ObjectYouOwner = 0x00000020, - /// - Scripted = 0x00000040, - /// Whether this object contains an active touch script - Touch = 0x00000080, - /// - ObjectMove = 0x00000100, - /// Whether this object can receive payments - Money = 0x00000200, - /// Whether this object is phantom (no collisions) - Phantom = 0x00000400, - /// - InventoryEmpty = 0x00000800, - /// - JointHinge = 0x00001000, - /// - JointP2P = 0x00002000, - /// - JointLP2P = 0x00004000, - /// Deprecated - JointWheel = 0x00008000, - /// - AllowInventoryDrop = 0x00010000, - /// - ObjectTransfer = 0x00020000, - /// - ObjectGroupOwned = 0x00040000, - /// Deprecated - ObjectYouOfficer = 0x00080000, - /// - CameraDecoupled = 0x00100000, - /// - AnimSource = 0x00200000, - /// - CameraSource = 0x00400000, - /// - CastShadows = 0x00800000, - /// - ObjectOwnerModify = 0x10000000, - /// - TemporaryOnRez = 0x20000000, - /// - Temporary = 0x40000000, - /// - ZlibCompressed = 0x80000000 - } - - #endregion Enumerations - - - #region Structs - - /// - /// - /// - [Serializable] - public struct ObjectData - { - /// - [XmlAttribute("pathtwistbegin")] - public int PathTwistBegin; - /// - [XmlAttribute("pathend")] - public float PathEnd; - /// - [XmlAttribute("profilebegin")] - public float ProfileBegin; - /// - [XmlAttribute("pathradiusoffset")] - public float PathRadiusOffset; - /// - [XmlAttribute("pathskew")] - public float PathSkew; - /// - [XmlAttribute("profilecurve")] - public uint ProfileCurve; - /// - [XmlAttribute("pathscalex")] - public float PathScaleX; - /// - [XmlAttribute("pathscaley")] - public float PathScaleY; - /// - [XmlAttribute("material")] - public uint Material; - /// - [XmlAttribute("pathshearx")] - public float PathShearX; - /// - [XmlAttribute("pathsheary")] - public float PathShearY; - /// - [XmlAttribute("pathtaperx")] - public float PathTaperX; - /// - [XmlAttribute("pathtapery")] - public float PathTaperY; - /// - [XmlAttribute("profileend")] - public float ProfileEnd; - /// - [XmlAttribute("pathbegin")] - public float PathBegin; - /// - [XmlAttribute("pathcurve")] - public uint PathCurve; - /// - [XmlAttribute("pathtwist")] - public int PathTwist; - /// - [XmlAttribute("profilehollow")] - public uint ProfileHollow; - /// - [XmlAttribute("pathrevolutions")] - public float PathRevolutions; - /// - [XmlAttribute("state")] - public uint State; - /// - [XmlIgnore] - public ObjectManager.PCode PCode; - } - - /// - /// - /// - public struct ObjectProperties - { - /// - public LLUUID ObjectID; - /// - public LLUUID CreatorID; - /// - public LLUUID OwnerID; - /// - public LLUUID GroupID; - /// - public ulong CreationDate; - /// - public uint BaseMask; - /// - public uint OwnerMask; - /// - public uint GroupMask; - /// - public uint EveryoneMask; - /// - public uint NextOwnerMask; - /// - public int OwnershipCost; - /// - public byte SaleType; - /// - public int SalePrice; - /// - public byte AggregatePerms; - /// - public byte AggregatePermTextures; - /// - public byte AggregatePermTexturesOwner; - /// - public uint Category; - /// - public short InventorySerial; - /// - public LLUUID ItemID; - /// - public LLUUID FolderID; - /// - public LLUUID FromTaskID; - /// - public LLUUID LastOwnerID; - /// - public string Name; - /// - public string Description; - /// - public string TouchName; - /// - public string SitName; - /// - public LLUUID[] TextureIDs; - } - - /// - /// - /// - public struct ObjectPropertiesFamily - { - /// - /// - /// - public enum RequestFlagsType - { - /// - BugReportRequest = 1, - /// - ComplaintReportRequest = 2 - } - - /// - public RequestFlagsType RequestFlags; - /// - public LLUUID ObjectID; - /// - public LLUUID OwnerID; - /// - public LLUUID GroupID; - /// - public uint BaseMask; - /// - public uint OwnerMask; - /// - public uint GroupMask; - /// - public uint EveryoneMask; - /// - public uint NextOwnerMask; - /// - public int OwnershipCost; - /// - public byte SaleType; - /// - public int SalePrice; - /// - public uint Category; - /// - public LLUUID LastOwnerID; - /// - public string Name; - /// - public string Description; - } - - #endregion Structs - - - #region Public Members - - /// - public LLUUID ID = LLUUID.Zero; - /// - public LLUUID GroupID = LLUUID.Zero; - /// - public uint LocalID; - /// - public uint ParentID; - /// - public ulong RegionHandle; - /// - public ObjectFlags Flags; - /// Unknown - public byte[] GenericData; - /// - public LLVector3 Position; - /// - public LLVector3 Scale; - /// - public LLQuaternion Rotation = LLQuaternion.Identity; - /// - public LLVector3 Velocity; - /// - public LLVector3 AngularVelocity; - /// - public LLVector3 Acceleration; - /// - public LLVector4 CollisionPlane; - /// - public TextureEntry Textures; - /// - public ObjectProperties Properties; - /// - public ObjectPropertiesFamily PropertiesFamily; - /// - public SerializableDictionary NameValues = new SerializableDictionary(); - - #endregion Public Members - - - #region Public Properties - - /// - public ObjectData Data { get { return data; } } - - #endregion Public Properties - - - internal ObjectData data = new ObjectData(); - - - public override bool Equals(object obj) - { - LLObject llobj = obj as LLObject; - if (llobj == null) - return false; - return ID.Equals(llobj.ID); - } - - public override int GetHashCode() - { - return ID.GetHashCode(); - } - - #region Static Methods - - /// - /// - /// - /// - /// - public static byte PathScaleByte(float pathScale) - { - // Y = 100 + 100X - int scale = (int)Math.Round(100.0f * pathScale); - return (byte)(100 + scale); - } - - /// - /// - /// - /// - /// - public static float PathScaleFloat(byte pathScale) - { - // Y = -1 + 0.01X - return (float)Math.Round((double)pathScale * 0.01d - 1.0d, 6); - } - - /// - /// - /// - /// - /// - public static byte PathShearByte(float pathShear) - { - // Y = 256 + 100X - int shear = (int)Math.Round(100.0f * pathShear); - shear += 256; - return (byte)(shear % 256); - } - - /// - /// - /// - /// - /// - public static float PathShearFloat(byte pathShear) - { - if (pathShear == 0) return 0.0f; - - if (pathShear > 150) - { - // Negative value - return ((float)pathShear - 256.0f) / 100.0f; - } - else - { - // Positive value - return (float)pathShear / 100.0f; - } - } - - /// - /// - /// - /// - /// - public static byte ProfileBeginByte(float profileBegin) - { - // Y = ceil (200X) - return (byte)Math.Round(200.0f * profileBegin); - } - - /// - /// - /// - /// - /// - public static float ProfileBeginFloat(byte profileBegin) - { - // Y = 0.005X - return (float)Math.Round((double)profileBegin * 0.005d, 6); - } - - /// - /// - /// - /// - /// - public static byte ProfileEndByte(float profileEnd) - { - // Y = 200 - 200X - int end = (int)Math.Round(200.0d * (double)profileEnd); - return (byte)(200 - end); - } - - /// - /// - /// - /// - /// - public static float ProfileEndFloat(byte profileEnd) - { - // Y = 1 - 0.005X - return (float)Math.Round(1.0d - ((double)profileEnd * 0.005d), 6); - } - - /// - /// - /// - /// - /// - public static byte PathBeginByte(float pathBegin) - { - // Y = 100X - return (byte)Convert.ToInt16(100.0f * pathBegin); - } - - /// - /// - /// - /// - /// - public static float PathBeginFloat(byte pathBegin) - { - // Y = X / 100 - return (float)pathBegin / 100.0f; - } - - /// - /// - /// - /// - /// - public static byte PathEndByte(float pathEnd) - { - // Y = 100 - 100X - int end = (int)Math.Round(100.0f * pathEnd); - return (byte)(100 - end); - } - - /// - /// - /// - /// - /// - public static float PathEndFloat(byte pathEnd) - { - // Y = 1 - X / 100 - return 1.0f - (float)pathEnd / 100.0f; - } - - /// - /// - /// - /// - /// - public static sbyte PathRadiusOffsetByte(float pathRadiusOffset) - { - // Y = 256 + 100X - return (sbyte)PathShearByte(pathRadiusOffset); - } - - /// - /// - /// - /// - /// - public static float PathRadiusOffsetFloat(sbyte pathRadiusOffset) - { - // Y = X / 100 - return (float)pathRadiusOffset / 100.0f; - } - - /// - /// - /// - /// - /// - public static byte PathRevolutionsByte(float pathRevolutions) - { - // Y = 66.5X - 66 - int revolutions = (int)Math.Round(66.5d * (double)pathRevolutions); - return (byte)(revolutions - 66); - } - - /// - /// - /// - /// - /// - public static float PathRevolutionsFloat(byte pathRevolutions) - { - // Y = 1 + 0.015X - return (float)Math.Round(1.0d + (double)pathRevolutions * 0.015d, 6); - } - - /// - /// - /// - /// - /// - public static sbyte PathSkewByte(float pathSkew) - { - return PathTaperByte(pathSkew); - } - - /// - /// - /// - /// - /// - public static float PathSkewFloat(sbyte pathSkew) - { - return PathTaperFloat(pathSkew); - } - - /// - /// - /// - /// - /// - public static sbyte PathTaperByte(float pathTaper) - { - // Y = 256 + 100X - return (sbyte)PathShearByte(pathTaper); - } - - /// - /// - /// - /// - /// - public static float PathTaperFloat(sbyte pathTaper) - { - return (float)pathTaper / 100.0f; - } - - #endregion Static Methods - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Base class for primitives and avatars + /// + [Serializable] + public abstract partial class LLObject + { + #region Enumerations + + /// + /// Primary parameters for primitives such as Physics Enabled or Phantom + /// + [Flags] + public enum ObjectFlags : uint + { + /// None of the primary flags are enabled + None = 0, + /// Whether physics are enabled for this object + Physics = 0x00000001, + /// + CreateSelected = 0x00000002, + /// + ObjectModify = 0x00000004, + /// + ObjectCopy = 0x00000008, + /// + ObjectAnyOwner = 0x00000010, + /// + ObjectYouOwner = 0x00000020, + /// + Scripted = 0x00000040, + /// Whether this object contains an active touch script + Touch = 0x00000080, + /// + ObjectMove = 0x00000100, + /// Whether this object can receive payments + Money = 0x00000200, + /// Whether this object is phantom (no collisions) + Phantom = 0x00000400, + /// + InventoryEmpty = 0x00000800, + /// + JointHinge = 0x00001000, + /// + JointP2P = 0x00002000, + /// + JointLP2P = 0x00004000, + /// Deprecated + JointWheel = 0x00008000, + /// + AllowInventoryDrop = 0x00010000, + /// + ObjectTransfer = 0x00020000, + /// + ObjectGroupOwned = 0x00040000, + /// Deprecated + ObjectYouOfficer = 0x00080000, + /// + CameraDecoupled = 0x00100000, + /// + AnimSource = 0x00200000, + /// + CameraSource = 0x00400000, + /// + CastShadows = 0x00800000, + /// + ObjectOwnerModify = 0x10000000, + /// + TemporaryOnRez = 0x20000000, + /// + Temporary = 0x40000000, + /// + ZlibCompressed = 0x80000000 + } + + #endregion Enumerations + + + #region Structs + + /// + /// + /// + [Serializable] + public struct ObjectData + { + /// + [XmlAttribute("pathtwistbegin")] + public int PathTwistBegin; + /// + [XmlAttribute("pathend")] + public float PathEnd; + /// + [XmlAttribute("profilebegin")] + public float ProfileBegin; + /// + [XmlAttribute("pathradiusoffset")] + public float PathRadiusOffset; + /// + [XmlAttribute("pathskew")] + public float PathSkew; + /// + [XmlAttribute("profilecurve")] + public uint ProfileCurve; + /// + [XmlAttribute("pathscalex")] + public float PathScaleX; + /// + [XmlAttribute("pathscaley")] + public float PathScaleY; + /// + [XmlAttribute("material")] + public uint Material; + /// + [XmlAttribute("pathshearx")] + public float PathShearX; + /// + [XmlAttribute("pathsheary")] + public float PathShearY; + /// + [XmlAttribute("pathtaperx")] + public float PathTaperX; + /// + [XmlAttribute("pathtapery")] + public float PathTaperY; + /// + [XmlAttribute("profileend")] + public float ProfileEnd; + /// + [XmlAttribute("pathbegin")] + public float PathBegin; + /// + [XmlAttribute("pathcurve")] + public uint PathCurve; + /// + [XmlAttribute("pathtwist")] + public int PathTwist; + /// + [XmlAttribute("profilehollow")] + public uint ProfileHollow; + /// + [XmlAttribute("pathrevolutions")] + public float PathRevolutions; + /// + [XmlAttribute("state")] + public uint State; + /// + [XmlIgnore] + public ObjectManager.PCode PCode; + } + + /// + /// + /// + public struct ObjectProperties + { + /// + public LLUUID ObjectID; + /// + public LLUUID CreatorID; + /// + public LLUUID OwnerID; + /// + public LLUUID GroupID; + /// + public ulong CreationDate; + /// + public uint BaseMask; + /// + public uint OwnerMask; + /// + public uint GroupMask; + /// + public uint EveryoneMask; + /// + public uint NextOwnerMask; + /// + public int OwnershipCost; + /// + public byte SaleType; + /// + public int SalePrice; + /// + public byte AggregatePerms; + /// + public byte AggregatePermTextures; + /// + public byte AggregatePermTexturesOwner; + /// + public uint Category; + /// + public short InventorySerial; + /// + public LLUUID ItemID; + /// + public LLUUID FolderID; + /// + public LLUUID FromTaskID; + /// + public LLUUID LastOwnerID; + /// + public string Name; + /// + public string Description; + /// + public string TouchName; + /// + public string SitName; + /// + public LLUUID[] TextureIDs; + } + + /// + /// + /// + public struct ObjectPropertiesFamily + { + /// + /// + /// + public enum RequestFlagsType + { + /// + BugReportRequest = 1, + /// + ComplaintReportRequest = 2 + } + + /// + public RequestFlagsType RequestFlags; + /// + public LLUUID ObjectID; + /// + public LLUUID OwnerID; + /// + public LLUUID GroupID; + /// + public uint BaseMask; + /// + public uint OwnerMask; + /// + public uint GroupMask; + /// + public uint EveryoneMask; + /// + public uint NextOwnerMask; + /// + public int OwnershipCost; + /// + public byte SaleType; + /// + public int SalePrice; + /// + public uint Category; + /// + public LLUUID LastOwnerID; + /// + public string Name; + /// + public string Description; + } + + #endregion Structs + + + #region Public Members + + /// + public LLUUID ID = LLUUID.Zero; + /// + public LLUUID GroupID = LLUUID.Zero; + /// + public uint LocalID; + /// + public uint ParentID; + /// + public ulong RegionHandle; + /// + public ObjectFlags Flags; + /// Unknown + public byte[] GenericData; + /// + public LLVector3 Position; + /// + public LLVector3 Scale; + /// + public LLQuaternion Rotation = LLQuaternion.Identity; + /// + public LLVector3 Velocity; + /// + public LLVector3 AngularVelocity; + /// + public LLVector3 Acceleration; + /// + public LLVector4 CollisionPlane; + /// + public TextureEntry Textures; + /// + public ObjectProperties Properties; + /// + public ObjectPropertiesFamily PropertiesFamily; + /// + public SerializableDictionary NameValues = new SerializableDictionary(); + + #endregion Public Members + + + #region Public Properties + + /// + public ObjectData Data { get { return data; } } + + #endregion Public Properties + + + internal ObjectData data = new ObjectData(); + + + public override bool Equals(object obj) + { + LLObject llobj = obj as LLObject; + if (llobj == null) + return false; + return ID.Equals(llobj.ID); + } + + public override int GetHashCode() + { + return ID.GetHashCode(); + } + + #region Static Methods + + /// + /// + /// + /// + /// + public static byte PathScaleByte(float pathScale) + { + // Y = 100 + 100X + int scale = (int)Math.Round(100.0f * pathScale); + return (byte)(100 + scale); + } + + /// + /// + /// + /// + /// + public static float PathScaleFloat(byte pathScale) + { + // Y = -1 + 0.01X + return (float)Math.Round((double)pathScale * 0.01d - 1.0d, 6); + } + + /// + /// + /// + /// + /// + public static byte PathShearByte(float pathShear) + { + // Y = 256 + 100X + int shear = (int)Math.Round(100.0f * pathShear); + shear += 256; + return (byte)(shear % 256); + } + + /// + /// + /// + /// + /// + public static float PathShearFloat(byte pathShear) + { + if (pathShear == 0) return 0.0f; + + if (pathShear > 150) + { + // Negative value + return ((float)pathShear - 256.0f) / 100.0f; + } + else + { + // Positive value + return (float)pathShear / 100.0f; + } + } + + /// + /// + /// + /// + /// + public static byte ProfileBeginByte(float profileBegin) + { + // Y = ceil (200X) + return (byte)Math.Round(200.0f * profileBegin); + } + + /// + /// + /// + /// + /// + public static float ProfileBeginFloat(byte profileBegin) + { + // Y = 0.005X + return (float)Math.Round((double)profileBegin * 0.005d, 6); + } + + /// + /// + /// + /// + /// + public static byte ProfileEndByte(float profileEnd) + { + // Y = 200 - 200X + int end = (int)Math.Round(200.0d * (double)profileEnd); + return (byte)(200 - end); + } + + /// + /// + /// + /// + /// + public static float ProfileEndFloat(byte profileEnd) + { + // Y = 1 - 0.005X + return (float)Math.Round(1.0d - ((double)profileEnd * 0.005d), 6); + } + + /// + /// + /// + /// + /// + public static byte PathBeginByte(float pathBegin) + { + // Y = 100X + return (byte)Convert.ToInt16(100.0f * pathBegin); + } + + /// + /// + /// + /// + /// + public static float PathBeginFloat(byte pathBegin) + { + // Y = X / 100 + return (float)pathBegin / 100.0f; + } + + /// + /// + /// + /// + /// + public static byte PathEndByte(float pathEnd) + { + // Y = 100 - 100X + int end = (int)Math.Round(100.0f * pathEnd); + return (byte)(100 - end); + } + + /// + /// + /// + /// + /// + public static float PathEndFloat(byte pathEnd) + { + // Y = 1 - X / 100 + return 1.0f - (float)pathEnd / 100.0f; + } + + /// + /// + /// + /// + /// + public static sbyte PathRadiusOffsetByte(float pathRadiusOffset) + { + // Y = 256 + 100X + return (sbyte)PathShearByte(pathRadiusOffset); + } + + /// + /// + /// + /// + /// + public static float PathRadiusOffsetFloat(sbyte pathRadiusOffset) + { + // Y = X / 100 + return (float)pathRadiusOffset / 100.0f; + } + + /// + /// + /// + /// + /// + public static byte PathRevolutionsByte(float pathRevolutions) + { + // Y = 66.5X - 66 + int revolutions = (int)Math.Round(66.5d * (double)pathRevolutions); + return (byte)(revolutions - 66); + } + + /// + /// + /// + /// + /// + public static float PathRevolutionsFloat(byte pathRevolutions) + { + // Y = 1 + 0.015X + return (float)Math.Round(1.0d + (double)pathRevolutions * 0.015d, 6); + } + + /// + /// + /// + /// + /// + public static sbyte PathSkewByte(float pathSkew) + { + return PathTaperByte(pathSkew); + } + + /// + /// + /// + /// + /// + public static float PathSkewFloat(sbyte pathSkew) + { + return PathTaperFloat(pathSkew); + } + + /// + /// + /// + /// + /// + public static sbyte PathTaperByte(float pathTaper) + { + // Y = 256 + 100X + return (sbyte)PathShearByte(pathTaper); + } + + /// + /// + /// + /// + /// + public static float PathTaperFloat(sbyte pathTaper) + { + return (float)pathTaper / 100.0f; + } + + #endregion Static Methods + } +} diff --git a/libsecondlife-cs/LLSD.cs b/libsecondlife-cs/LLSD.cs index f7a7d1a7..7fb5c040 100644 --- a/libsecondlife-cs/LLSD.cs +++ b/libsecondlife-cs/LLSD.cs @@ -1,449 +1,449 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Xml; -using System.IO; -using libsecondlife; -using System.Security.Cryptography; -using System.Text; - -namespace libsecondlife -{ - /// - /// - /// - public class LLSD - { - /// - /// - /// - public class LLSDParseException : Exception - { - public LLSDParseException(string message) : base(message) { } - } - - /// - /// - /// - public class LLSDSerializeException : Exception - { - public LLSDSerializeException(string message) : base(message) { } - } - - /// - /// - /// - /// - /// - public static object LLSDDeserialize(byte[] b) - { - return LLSDDeserialize(new MemoryStream(b, false)); - } - - /// - /// - /// - /// - /// - public static object LLSDDeserialize(Stream st) - { - XmlTextReader reader = new XmlTextReader(st); - reader.Read(); SkipWS(reader); - if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd") - { - throw new LLSDParseException("Expected "); - } - reader.Read(); - object ret = LLSDParseOne(reader); - SkipWS(reader); - if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd") - throw new LLSDParseException("Expected "); - return ret; - } - - /// - /// - /// - /// - /// - public static byte[] LLSDSerialize(object obj) - { - StringWriter sw = new StringWriter(); - XmlTextWriter writer = new XmlTextWriter(sw); - writer.Formatting = Formatting.None; - writer.WriteStartElement("", "llsd", ""); - LLSDWriteOne(writer, obj); - writer.WriteEndElement(); - writer.Close(); - return Encoding.UTF8.GetBytes(sw.ToString()); - } - - /// - /// - /// - /// - /// - public static void LLSDWriteOne(XmlTextWriter writer, object obj) - { - if (obj == null) - { - writer.WriteStartElement("", "undef", ""); - writer.WriteEndElement(); - return; - } - - Type t = obj.GetType(); - if (t == typeof(string)) - { - writer.WriteStartElement("", "string", ""); - writer.WriteString((string)obj); - writer.WriteEndElement(); - } - else if (t == typeof(long)) - { - writer.WriteStartElement("", "integer", ""); - writer.WriteString(obj.ToString()); - writer.WriteEndElement(); - } - else if (t == typeof(double)) - { - writer.WriteStartElement("", "real", ""); - writer.WriteString(obj.ToString()); - writer.WriteEndElement(); - } - else if (t == typeof(bool)) - { - bool b = (bool)obj; - writer.WriteStartElement("", "boolean", ""); - if (b) - writer.WriteString("1"); - else writer.WriteString("0"); - writer.WriteEndElement(); - } - else if (t == typeof(LLUUID)) - { - LLUUID u = (LLUUID)obj; - writer.WriteStartElement("", "uuid", ""); - writer.WriteString(u.ToStringHyphenated()); - writer.WriteEndElement(); - } - else if (t == typeof(Hashtable)) - { - Hashtable h = (Hashtable)obj; - writer.WriteStartElement("", "map", ""); - foreach (string key in h.Keys) - { - writer.WriteStartElement("", "key", ""); - writer.WriteString(key); - writer.WriteEndElement(); - LLSDWriteOne(writer, h[key]); - } - writer.WriteEndElement(); - } - else if (t == typeof(ArrayList)) - { - ArrayList a = (ArrayList)obj; - writer.WriteStartElement("", "array", ""); - foreach (object item in a) - { - LLSDWriteOne(writer, item); - } - writer.WriteEndElement(); - } - else if (t == typeof(byte[])) - { - byte[] b = (byte[])obj; - writer.WriteStartElement("", "binary", ""); - writer.WriteStartAttribute("", "encoding", ""); - writer.WriteString("base64"); - writer.WriteEndAttribute(); - char[] tmp = new char[b.Length * 2]; // too much - int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0); - Array.Resize(ref tmp, i); - writer.WriteString(new String(tmp)); - writer.WriteEndElement(); - - } - else - { - throw new LLSDSerializeException("Unknown type " + t.Name); - } - } - - /// - /// - /// - /// - /// - public static object LLSDParseOne(XmlTextReader reader) - { - SkipWS(reader); - if (reader.NodeType != XmlNodeType.Element) - throw new LLSDParseException("Expected an element"); - string dtype = reader.LocalName; object ret = null; - //bool st = false; - - switch (dtype) - { - case "undef": - { - if (reader.IsEmptyElement) - { - reader.Read(); return null; - } - reader.Read(); SkipWS(reader); ret = null; break; - } - case "boolean": - { - if (reader.IsEmptyElement) - { - reader.Read(); return false; - } - reader.Read(); - string s = reader.ReadString().Trim(); - if (s == "" || s == "false" || s == "0") - { - ret = false; - } - else if (s == "true" || s == "1") - { - ret = true; - } - else - { - throw new LLSDParseException("Bad boolean value " + s); - } - break; - } - case "integer": - { - if (reader.IsEmptyElement) - { - reader.Read(); return 0L; - } - reader.Read(); - ret = Convert.ToInt64(reader.ReadString().Trim()); - break; - } - case "real": - { - if (reader.IsEmptyElement) - { - reader.Read(); return 0.0f; - } - reader.Read(); - ret = Convert.ToDouble(reader.ReadString().Trim()); - break; - } - case "uuid": - { - if (reader.IsEmptyElement) - { - reader.Read(); return new LLUUID(); - } - reader.Read(); - ret = new LLUUID(reader.ReadString().Trim()); - break; - } - case "string": - { - if (reader.IsEmptyElement) - { - reader.Read(); return String.Empty; - } - reader.Read(); - ret = reader.ReadString(); - break; - } - case "binary": - { - if (reader.IsEmptyElement) - { - reader.Read(); return new byte[0]; - } - if (reader.GetAttribute("encoding") != null && - reader.GetAttribute("encoding") != "base64") - throw new LLSDParseException("Unknown encoding: " + - reader.GetAttribute("encoding")); - reader.Read(); - FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces); - byte[] inp = Encoding.ASCII.GetBytes(reader.ReadString()); - ret = b64.TransformFinalBlock(inp, 0, inp.Length); - break; - } - case "date": - { - reader.Read(); - throw new Exception("LLSD TODO: date"); - } - case "map": - { - return LLSDParseMap(reader); - } - case "array": - { - return LLSDParseArray(reader); - } - default: - throw new LLSDParseException("Unknown element <" + dtype + ">"); - } - if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype) - { - throw new LLSDParseException("Expected "); - } - reader.Read(); - return ret; - } - - /// - /// - /// - /// - /// - public static Hashtable LLSDParseMap(XmlTextReader reader) - { - if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") - throw new LLSDParseException("Expected "); - if (reader.IsEmptyElement) - { - reader.Read(); return new Hashtable(); - } - reader.Read(); - - Hashtable ret = new Hashtable(); - - while (true) - { - SkipWS(reader); - if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") - { - reader.Read(); break; - } - if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") - throw new LLSDParseException("Expected "); - string key = reader.ReadString(); - if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") - throw new LLSDParseException("Expected "); - reader.Read(); - object val = LLSDParseOne(reader); - ret[key] = val; - } - return ret; // TODO - } - - /// - /// - /// - /// - /// - public static ArrayList LLSDParseArray(XmlTextReader reader) - { - if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") - throw new LLSDParseException("Expected "); - if (reader.IsEmptyElement) - { - reader.Read(); return new ArrayList(); - } - reader.Read(); - - ArrayList ret = new ArrayList(); - - while (true) - { - SkipWS(reader); - if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") - { - reader.Read(); break; - } - ret.Insert(ret.Count, LLSDParseOne(reader)); - } - return ret; // TODO - } - - /// - /// - /// - /// - /// - private static string GetSpaces(int count) - { - StringBuilder b = new StringBuilder(); - for (int i = 0; i < count; i++) b.Append(" "); - return b.ToString(); - } - - /// - /// - /// - /// - /// - /// - public static String LLSDDump(object obj, int indent) - { - if (obj == null) - { - return GetSpaces(indent) + "- undef\n"; - } - else if (obj.GetType() == typeof(string)) - { - return GetSpaces(indent) + "- string \"" + (string)obj + "\"\n"; - } - else if (obj.GetType() == typeof(long)) - { - return GetSpaces(indent) + "- integer " + obj.ToString() + "\n"; - } - else if (obj.GetType() == typeof(double)) - { - return GetSpaces(indent) + "- float " + obj.ToString() + "\n"; - } - else if (obj.GetType() == typeof(LLUUID)) - { - return GetSpaces(indent) + "- uuid " + ((LLUUID)obj).ToStringHyphenated() + Environment.NewLine; - } - else if (obj.GetType() == typeof(Hashtable)) - { - StringBuilder ret = new StringBuilder(); - ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine); - Hashtable map = (Hashtable)obj; - - foreach (string key in map.Keys) - { - ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine); - ret.Append(LLSDDump(map[key], indent + 3)); - } - - return ret.ToString(); - } - else if (obj.GetType() == typeof(ArrayList)) - { - StringBuilder ret = new StringBuilder(); - ret.Append(GetSpaces(indent) + "- array\n"); - ArrayList list = (ArrayList)obj; - - foreach (object item in list) - { - ret.Append(LLSDDump(item, indent + 2)); - } - - return ret.ToString(); - } - else if (obj.GetType() == typeof(byte[])) - { - return GetSpaces(indent) + "- binary\n" + Helpers.FieldToHexString((byte[])obj, "") + Environment.NewLine; - } - else - { - return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine; - } - } - - /// - /// - /// - /// - private static void SkipWS(XmlTextReader reader) - { - while (reader.NodeType == XmlNodeType.Comment || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace || reader.NodeType == XmlNodeType.XmlDeclaration) reader.Read(); - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Xml; +using System.IO; +using libsecondlife; +using System.Security.Cryptography; +using System.Text; + +namespace libsecondlife +{ + /// + /// + /// + public class LLSD + { + /// + /// + /// + public class LLSDParseException : Exception + { + public LLSDParseException(string message) : base(message) { } + } + + /// + /// + /// + public class LLSDSerializeException : Exception + { + public LLSDSerializeException(string message) : base(message) { } + } + + /// + /// + /// + /// + /// + public static object LLSDDeserialize(byte[] b) + { + return LLSDDeserialize(new MemoryStream(b, false)); + } + + /// + /// + /// + /// + /// + public static object LLSDDeserialize(Stream st) + { + XmlTextReader reader = new XmlTextReader(st); + reader.Read(); SkipWS(reader); + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "llsd") + { + throw new LLSDParseException("Expected "); + } + reader.Read(); + object ret = LLSDParseOne(reader); + SkipWS(reader); + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "llsd") + throw new LLSDParseException("Expected "); + return ret; + } + + /// + /// + /// + /// + /// + public static byte[] LLSDSerialize(object obj) + { + StringWriter sw = new StringWriter(); + XmlTextWriter writer = new XmlTextWriter(sw); + writer.Formatting = Formatting.None; + writer.WriteStartElement("", "llsd", ""); + LLSDWriteOne(writer, obj); + writer.WriteEndElement(); + writer.Close(); + return Encoding.UTF8.GetBytes(sw.ToString()); + } + + /// + /// + /// + /// + /// + public static void LLSDWriteOne(XmlTextWriter writer, object obj) + { + if (obj == null) + { + writer.WriteStartElement("", "undef", ""); + writer.WriteEndElement(); + return; + } + + Type t = obj.GetType(); + if (t == typeof(string)) + { + writer.WriteStartElement("", "string", ""); + writer.WriteString((string)obj); + writer.WriteEndElement(); + } + else if (t == typeof(long)) + { + writer.WriteStartElement("", "integer", ""); + writer.WriteString(obj.ToString()); + writer.WriteEndElement(); + } + else if (t == typeof(double)) + { + writer.WriteStartElement("", "real", ""); + writer.WriteString(obj.ToString()); + writer.WriteEndElement(); + } + else if (t == typeof(bool)) + { + bool b = (bool)obj; + writer.WriteStartElement("", "boolean", ""); + if (b) + writer.WriteString("1"); + else writer.WriteString("0"); + writer.WriteEndElement(); + } + else if (t == typeof(LLUUID)) + { + LLUUID u = (LLUUID)obj; + writer.WriteStartElement("", "uuid", ""); + writer.WriteString(u.ToStringHyphenated()); + writer.WriteEndElement(); + } + else if (t == typeof(Hashtable)) + { + Hashtable h = (Hashtable)obj; + writer.WriteStartElement("", "map", ""); + foreach (string key in h.Keys) + { + writer.WriteStartElement("", "key", ""); + writer.WriteString(key); + writer.WriteEndElement(); + LLSDWriteOne(writer, h[key]); + } + writer.WriteEndElement(); + } + else if (t == typeof(ArrayList)) + { + ArrayList a = (ArrayList)obj; + writer.WriteStartElement("", "array", ""); + foreach (object item in a) + { + LLSDWriteOne(writer, item); + } + writer.WriteEndElement(); + } + else if (t == typeof(byte[])) + { + byte[] b = (byte[])obj; + writer.WriteStartElement("", "binary", ""); + writer.WriteStartAttribute("", "encoding", ""); + writer.WriteString("base64"); + writer.WriteEndAttribute(); + char[] tmp = new char[b.Length * 2]; // too much + int i = Convert.ToBase64CharArray(b, 0, b.Length, tmp, 0); + Array.Resize(ref tmp, i); + writer.WriteString(new String(tmp)); + writer.WriteEndElement(); + + } + else + { + throw new LLSDSerializeException("Unknown type " + t.Name); + } + } + + /// + /// + /// + /// + /// + public static object LLSDParseOne(XmlTextReader reader) + { + SkipWS(reader); + if (reader.NodeType != XmlNodeType.Element) + throw new LLSDParseException("Expected an element"); + string dtype = reader.LocalName; object ret = null; + //bool st = false; + + switch (dtype) + { + case "undef": + { + if (reader.IsEmptyElement) + { + reader.Read(); return null; + } + reader.Read(); SkipWS(reader); ret = null; break; + } + case "boolean": + { + if (reader.IsEmptyElement) + { + reader.Read(); return false; + } + reader.Read(); + string s = reader.ReadString().Trim(); + if (s == "" || s == "false" || s == "0") + { + ret = false; + } + else if (s == "true" || s == "1") + { + ret = true; + } + else + { + throw new LLSDParseException("Bad boolean value " + s); + } + break; + } + case "integer": + { + if (reader.IsEmptyElement) + { + reader.Read(); return 0L; + } + reader.Read(); + ret = Convert.ToInt64(reader.ReadString().Trim()); + break; + } + case "real": + { + if (reader.IsEmptyElement) + { + reader.Read(); return 0.0f; + } + reader.Read(); + ret = Convert.ToDouble(reader.ReadString().Trim()); + break; + } + case "uuid": + { + if (reader.IsEmptyElement) + { + reader.Read(); return new LLUUID(); + } + reader.Read(); + ret = new LLUUID(reader.ReadString().Trim()); + break; + } + case "string": + { + if (reader.IsEmptyElement) + { + reader.Read(); return String.Empty; + } + reader.Read(); + ret = reader.ReadString(); + break; + } + case "binary": + { + if (reader.IsEmptyElement) + { + reader.Read(); return new byte[0]; + } + if (reader.GetAttribute("encoding") != null && + reader.GetAttribute("encoding") != "base64") + throw new LLSDParseException("Unknown encoding: " + + reader.GetAttribute("encoding")); + reader.Read(); + FromBase64Transform b64 = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces); + byte[] inp = Encoding.ASCII.GetBytes(reader.ReadString()); + ret = b64.TransformFinalBlock(inp, 0, inp.Length); + break; + } + case "date": + { + reader.Read(); + throw new Exception("LLSD TODO: date"); + } + case "map": + { + return LLSDParseMap(reader); + } + case "array": + { + return LLSDParseArray(reader); + } + default: + throw new LLSDParseException("Unknown element <" + dtype + ">"); + } + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != dtype) + { + throw new LLSDParseException("Expected "); + } + reader.Read(); + return ret; + } + + /// + /// + /// + /// + /// + public static Hashtable LLSDParseMap(XmlTextReader reader) + { + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map") + throw new LLSDParseException("Expected "); + if (reader.IsEmptyElement) + { + reader.Read(); return new Hashtable(); + } + reader.Read(); + + Hashtable ret = new Hashtable(); + + while (true) + { + SkipWS(reader); + if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map") + { + reader.Read(); break; + } + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key") + throw new LLSDParseException("Expected "); + string key = reader.ReadString(); + if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key") + throw new LLSDParseException("Expected "); + reader.Read(); + object val = LLSDParseOne(reader); + ret[key] = val; + } + return ret; // TODO + } + + /// + /// + /// + /// + /// + public static ArrayList LLSDParseArray(XmlTextReader reader) + { + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array") + throw new LLSDParseException("Expected "); + if (reader.IsEmptyElement) + { + reader.Read(); return new ArrayList(); + } + reader.Read(); + + ArrayList ret = new ArrayList(); + + while (true) + { + SkipWS(reader); + if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array") + { + reader.Read(); break; + } + ret.Insert(ret.Count, LLSDParseOne(reader)); + } + return ret; // TODO + } + + /// + /// + /// + /// + /// + private static string GetSpaces(int count) + { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < count; i++) b.Append(" "); + return b.ToString(); + } + + /// + /// + /// + /// + /// + /// + public static String LLSDDump(object obj, int indent) + { + if (obj == null) + { + return GetSpaces(indent) + "- undef\n"; + } + else if (obj.GetType() == typeof(string)) + { + return GetSpaces(indent) + "- string \"" + (string)obj + "\"\n"; + } + else if (obj.GetType() == typeof(long)) + { + return GetSpaces(indent) + "- integer " + obj.ToString() + "\n"; + } + else if (obj.GetType() == typeof(double)) + { + return GetSpaces(indent) + "- float " + obj.ToString() + "\n"; + } + else if (obj.GetType() == typeof(LLUUID)) + { + return GetSpaces(indent) + "- uuid " + ((LLUUID)obj).ToStringHyphenated() + Environment.NewLine; + } + else if (obj.GetType() == typeof(Hashtable)) + { + StringBuilder ret = new StringBuilder(); + ret.Append(GetSpaces(indent) + "- map" + Environment.NewLine); + Hashtable map = (Hashtable)obj; + + foreach (string key in map.Keys) + { + ret.Append(GetSpaces(indent + 2) + "- key \"" + key + "\"" + Environment.NewLine); + ret.Append(LLSDDump(map[key], indent + 3)); + } + + return ret.ToString(); + } + else if (obj.GetType() == typeof(ArrayList)) + { + StringBuilder ret = new StringBuilder(); + ret.Append(GetSpaces(indent) + "- array\n"); + ArrayList list = (ArrayList)obj; + + foreach (object item in list) + { + ret.Append(LLSDDump(item, indent + 2)); + } + + return ret.ToString(); + } + else if (obj.GetType() == typeof(byte[])) + { + return GetSpaces(indent) + "- binary\n" + Helpers.FieldToHexString((byte[])obj, "") + Environment.NewLine; + } + else + { + return GetSpaces(indent) + "- unknown type " + obj.GetType().Name + Environment.NewLine; + } + } + + /// + /// + /// + /// + private static void SkipWS(XmlTextReader reader) + { + while (reader.NodeType == XmlNodeType.Comment || reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.SignificantWhitespace || reader.NodeType == XmlNodeType.XmlDeclaration) reader.Read(); + } + } +} diff --git a/libsecondlife-cs/MainAvatar.cs b/libsecondlife-cs/MainAvatar.cs index 5aa33b3b..1501ab1c 100644 --- a/libsecondlife-cs/MainAvatar.cs +++ b/libsecondlife-cs/MainAvatar.cs @@ -1,1890 +1,1890 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Timers; -using System.Net; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Text; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Class to hold Client Avatar's data - /// - public partial class MainAvatar - { - #region Enums - - /// - /// Used to specify movement actions for your agent - /// - [Flags] - public enum AgentUpdateFlags - { - /// Empty flag - NONE = 0, - /// Move Forward (SL Keybinding: W/Up Arrow) - AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX, - /// Move Backward (SL Keybinding: S/Down Arrow) - AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX, - /// Move Left (SL Keybinding: Shift-(A/Left Arrow)) - AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX, - /// Move Right (SL Keybinding: Shift-(D/Right Arrow)) - AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX, - /// Not Flying: Jump/Flying: Move Up (SL Keybinding: E) - AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX, - /// Not Flying: Croutch/Flying: Move Down (SL Keybinding: C) - AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX, - /// Unused - AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX, - /// Unused - AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX, - /// Unused - AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX, - /// Unused - AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX, - /// ORed with AGENT_CONTROL_AT_* if the keyboard is being used - AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX, - /// ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used - AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX, - /// ORed with AGENT_CONTROL_UP_* if the keyboard is being used - AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX, - /// Fly - AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX, - /// - AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX, - /// Finish our current animation - AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX, - /// Stand up from the ground or a prim seat - AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX, - /// Sit on the ground at our current location - AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX, - /// Whether mouselook is currently enabled - AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX, - /// Legacy, used if a key was pressed for less than a certain amount of time - AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX, - /// - AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX, - /// - AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX, - /// Set when the avatar is idled or set to away. Note that the away animation is - /// activated separately from setting this flag - AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX, - /// - AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX, - /// - AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX, - /// - AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX, - /// - AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX - } - - /// - /// Current teleport status - /// - public enum TeleportStatus - { - /// Unknown status - None, - /// Teleport initialized - Start, - /// Teleport in progress - Progress, - /// Teleport failed - Failed, - /// Teleport completed - Finished, - /// Teleport cancelled - Cancelled - } - - /// - /// Special commands used in Instant Messages - /// - public enum InstantMessageDialog : byte - { - /// Indicates a regular IM from another agent - MessageFromAgent = 0, - /// Simple notification box with an OK button - MessageBox = 1, - /// Used to show a countdown notification with an OK - /// button, deprecated now - [Obsolete] - MessageBoxCountdown = 2, - /// You've been invited to join a group. - GroupInvitation = 3, - /// Inventory offer - InventoryOffered = 4, - /// Accepted inventory offer - InventoryAccepted = 5, - /// Declined inventory offer - InventoryDeclined = 6, - /// Group vote - GroupVote = 7, - /// A message to everyone in the agent's group, no longer - /// used - [Obsolete] - DeprecatedGroupMessage = 8, - /// An object is offering its inventory - TaskInventoryOffered = 9, - /// Accept an inventory offer from an object - TaskInventoryAccepted = 10, - /// Decline an inventory offer from an object - TaskInventoryDeclined = 11, - /// Unknown - NewUserDefault = 12, - /// Start a session, or add users to a session - SessionAdd = 13, - /// Start a session, but don't prune offline users - SessionOfflineAdd = 14, - /// Start a session with your group - SessionGroupStart = 15, - /// Start a session without a calling card (finder or objects) - SessionCardlessStart = 16, - /// Send a message to a session - SessionSend = 17, - /// Leave a session - SessionDrop = 18, - /// Indicates that the IM is from an object - MessageFromObject = 19, - /// sent an IM to a busy user, this is the auto response - BusyAutoResponse = 20, - /// Shows the message in the console and chat history - ConsoleAndChatHistory = 21, - /// IM Types used for luring your friends - RequestTeleport = 22, - /// Response sent to the agent which inititiated a teleport invitation - AcceptTeleport = 23, - /// Response sent to the agent which inititiated a teleport invitation - DenyTeleport = 24, - /// Only useful if you have Linden permissions - GodLikeRequestTeleport = 25, - /// A placeholder type for future expansion, currently not - /// used - CurrentlyUnused = 26, - /// Notification of a new group election, this is - /// deprecated - [Obsolete] - DeprecatedGroupElection = 27, - /// IM to tell the user to go to an URL - GotoUrl = 28, - /// IM for help - Session911Start = 29, - /// IM sent automatically on call for help, sends a lure - /// to each Helper reached - Lure911 = 30, - /// Like an IM but won't go to email - FromTaskAsAlert = 31, - /// IM from a group officer to all group members - GroupNotice = 32, - /// Unknown - GroupNoticeInventoryAccepted = 33, - /// Unknown - GroupNoticeInventoryDeclined = 34, - /// Accept a group invitation - GroupInvitationAccept = 35, - /// Decline a group invitation - GroupInvitationDecline = 36, - /// Unknown - GroupNoticeRequested = 37, - /// An avatar is offering you friendship - FriendshipOffered = 38, - /// An avatar has accepted your friendship offer - FriendshipAccepted = 39, - /// An avatar has declined your friendship offer - FriendshipDeclined = 40, - /// Indicates that a user has started typing - StartTyping = 41, - /// Indicates that a user has stopped typing - StopTyping = 42 - } - - /// - /// Flag in Instant Messages, whether the IM should be delivered to - /// offline avatars as well - /// - public enum InstantMessageOnline - { - /// Only deliver to online avatars - Online = 0, - /// If the avatar is offline the message will be held until - /// they login next, and possibly forwarded to their e-mail account - Offline = 1 - } - - /// - /// Conversion type to denote Chat Packet types in an easier-to-understand format - /// - public enum ChatType : byte - { - /// Whisper (5m radius) - Whisper = 0, - /// Normal chat (10/20m radius), what the official viewer typically sends - Normal = 1, - /// Shouting! (100m radius) - Shout = 2, - /// Say chat (10/20m radius) - The official viewer will - /// print "[4:15] You say, hey" instead of "[4:15] You: hey" - [Obsolete] - Say = 3, - /// Event message when an Avatar has begun to type - StartTyping = 4, - /// Event message when an Avatar has stopped typing - StopTyping = 5, - /// Unknown - Debug = 6 - } - - /// - /// Identifies the source of a chat message - /// - public enum ChatSourceType : byte - { - /// Chat from the grid or simulator - System = 0, - /// Chat from another avatar - Agent = 1, - /// Chat from an object - Object = 2 - } - - /// - /// - /// - public enum ChatAudibleLevel : sbyte - { - /// - Not = -1, - /// - Barely = 0, - /// - Fully = 1 - } - - /// - /// Effect type used in ViewerEffect packets - /// - public enum EffectType : byte - { - /// Place floating text above an object - Text = 0, - /// Unknown, probably places an icon above an object - Icon, - /// Unknown - Connector, - /// Unknown - FlexibleObject, - /// Unknown - AnimalControls, - /// Unknown - AnimationObject, - /// Unknown - Cloth, - /// Project a beam from a source to a destination, such as - /// the one used when editing an object - Beam, - /// Not implemented yet - Glow, - /// Unknown - Point, - /// Unknown - Trail, - /// Create a swirl of particles around an object - Sphere, - /// Unknown - Spiral, - /// Unknown - Edit, - /// Cause an avatar to look at an object - LookAt, - /// Cause an avatar to point at an object - PointAt - } - - /// - /// The action an avatar is doing when looking at something, used in - /// ViewerEffect packets for the LookAt effect - /// - public enum LookAtTarget : byte - { - /// - None, - /// - Idle, - /// - AutoListen, - /// - FreeLook, - /// - Respond, - /// - Hover, - /// Deprecated - Conversation, - /// - Select, - /// - Focus, - /// - Mouselook, - /// - Clear - } - - /// - /// The action an avatar is doing when pointing at something, used in - /// ViewerEffect packets for the PointAt effect - /// - public enum PointAtType : byte - { - /// - None, - /// - Select, - /// - Grab, - /// - Clear - } - - /// - /// - /// - [Flags] - public enum TeleportFlags : uint - { - /// - Default = 0, - /// - SetHomeToTarget = 1 << 0, - /// - SetLastToTarget = 1 << 1, - /// - ViaLure = 1 << 2, - /// - ViaLandmark = 1 << 3, - /// - ViaLocation = 1 << 4, - /// - ViaHome = 1 << 5, - /// - ViaTelehub = 1 << 6, - /// - ViaLogin = 1 << 7, - /// - ViaGodlikeLure = 1 << 8, - /// - Godlike = 1 << 9, - /// - NineOneOne = 1 << 10, - /// - DisableCancel = 1 << 11, - /// - ViaRegionID = 1 << 12, - /// - IsFlying = 1 << 13 - } - - /// - /// - /// - [Flags] - public enum TeleportLureFlags - { - /// - NormalLure = 0, - /// - GodlikeLure = 1, - /// - GodlikePursuit = 2 - } - - #endregion - - - #region Callbacks & Events - /// - /// Triggered on incoming chat messages - /// - /// Text of chat message - /// Audible level of this chat message - /// Type of chat (whisper, shout, status, etc.) - /// Source of the chat message - /// Name of the sending object - /// - /// - /// - public delegate void ChatCallback(string message, ChatAudibleLevel audible, ChatType type, - ChatSourceType sourceType, string fromName, LLUUID id, LLUUID ownerid, LLVector3 position); - - /// - /// Triggered when a script pops up a dialog box - /// - /// The dialog box message - /// Name of the object that sent the dialog - /// Image to be displayed in the dialog - /// ID of the object that sent the dialog - /// First name of the object owner - /// Last name of the object owner - /// Chat channel that the object is communicating on - /// List of button labels - public delegate void ScriptDialogCallback(string message, string objectName, LLUUID imageID, - LLUUID objectID, string firstName, string lastName, int chatChannel, List buttons); - - /// - /// Triggered when the L$ account balance for this avatar changes - /// - /// The new account balance - public delegate void BalanceCallback(int balance); - - /// - /// Triggered on Money Balance Reply - /// - /// ID provided in Request Money Balance, or auto-generated by system events - /// Was the transaction successful - /// Current balance - /// - /// - /// - public delegate void MoneyBalanceReplyCallback(LLUUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description); - - /// - /// Tiggered on incoming instant messages - /// - /// Key of sender - /// Name of sender - /// Key of destination Avatar - /// ID of originating Estate - /// Key of originating Region - /// Coordinates in originating Region - /// - /// Group IM session toggle - /// Key of IM Session - /// Timestamp of message - /// Text of message - /// Enum of whether this message is held for - /// offline avatars - /// - public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName, - LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, - InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, - InstantMessageOnline offline, byte[] binaryBucket); - - /// - /// Triggered for any status updates of a teleport (progress, failed, succeeded) - /// - /// A message about the current teleport status - /// The current status of the teleport - /// Various flags describing the teleport - public delegate void TeleportCallback(string message, TeleportStatus status, TeleportFlags flags); - - /// - /// Reply to a request to join a group, informs whether it was successful or not - /// - /// The group we attempted to join - /// Whether we joined the group or not - public delegate void JoinGroupCallback(LLUUID groupID, bool success); - - /// - /// Reply to a request to leave a group, informs whether it was successful or not - /// - /// The group we attempted to leave - /// Whether we left the group or not - public delegate void LeaveGroupCallback(LLUUID groupID, bool success); - - /// - /// Informs the avatar that it is no longer a member of a group - /// - /// The group we are no longer a member of - public delegate void GroupDroppedCallback(LLUUID groupID); - - - /// Callback for incoming chat packets - public event ChatCallback OnChat; - /// Callback for pop-up dialogs from scripts - public event ScriptDialogCallback OnScriptDialog; - /// Callback for incoming IMs - public event InstantMessageCallback OnInstantMessage; - /// Callback for Teleport request update - public event TeleportCallback OnTeleport; - /// Callback for incoming change in L$ balance - public event BalanceCallback OnBalanceUpdated; - /// Callback for incoming Money Balance Replies - public event MoneyBalanceReplyCallback OnMoneyBalanceReplyReceived; - /// Callback reply for an attempt to join a group - public event JoinGroupCallback OnJoinGroup; - /// Callback reply for an attempt to leave a group - public event LeaveGroupCallback OnLeaveGroup; - /// Callback for informing the avatar that it is no longer a member of a group - public event GroupDroppedCallback OnGroupDropped; - - #endregion - - - #region Public Members - - /// Your (client) avatar UUID - public LLUUID ID = LLUUID.Zero; - /// Your (client) avatar ID, local to the current region/sim - public uint LocalID = 0; - /// Avatar First Name (i.e. Philip) - public string FirstName = String.Empty; - /// Avatar Last Name (i.e. Linden) - public string LastName = String.Empty; - /// Positive and negative ratings - /// This information is read-only and any changes will not be - /// reflected on the server - public Avatar.Statistics ProfileStatistics = new Avatar.Statistics(); - /// Avatar properties including about text, profile URL, image IDs and - /// publishing settings - /// If you change fields in this struct, the changes will not - /// be reflected on the server until you call SetAvatarInformation - public Avatar.AvatarProperties ProfileProperties = new Avatar.AvatarProperties(); - /// Avatar interests including spoken languages, skills, and "want to" - /// choices - /// If you change fields in this struct, the changes will not - /// be reflected on the server until you call SetAvatarInformation - public Avatar.Interests ProfileInterests = new Avatar.Interests(); - /// Current position of avatar - public LLVector3 Position = LLVector3.Zero; - /// Current rotation of avatar - public LLQuaternion Rotation = LLQuaternion.Identity; - /// - public LLVector4 CollisionPlane = LLVector4.Zero; - /// - public LLVector3 Velocity = LLVector3.Zero; - /// - public LLVector3 Acceleration = LLVector3.Zero; - /// - public LLVector3 AngularVelocity = LLVector3.Zero; - /// The point the avatar is currently looking at - /// (may not stay updated) - public LLVector3 LookAt = LLVector3.Zero; - /// Position avatar client will goto when login to 'home' or during - /// teleport request to 'home' region. - public LLVector3 HomePosition = LLVector3.Zero; - /// LookAt point saved/restored with HomePosition - public LLVector3 HomeLookAt = LLVector3.Zero; - /// Used for camera and control key state tracking - public MainAvatarStatus Status; - /// The UUID of your root inventory folder - public LLUUID InventoryRootFolderUUID = LLUUID.Zero; - - /// Gets the health of the agent - public float Health { get { return health; } } - /// Gets the current balance of the agent - public int Balance { get { return balance; } } - /// Gets the local ID of the prim the avatar is sitting on, - /// zero if the avatar is not currently sitting - public uint SittingOn { get { return sittingOn; } } - /// Gets the UUID of the active group. - public LLUUID ActiveGroup { get { return activeGroup; } } - - #endregion Public Members - - - internal uint sittingOn = 0; - internal string teleportMessage = String.Empty; - - private SecondLife Client; - private TeleportStatus TeleportStat = TeleportStatus.None; - private ManualResetEvent TeleportEvent = new ManualResetEvent(false); - private uint HeightWidthGenCounter = 0; - private float health = 0.0f; - private int balance = 0; - private LLUUID activeGroup = LLUUID.Zero; - - - #region AgentUpdate Constants - - private const int CONTROL_AT_POS_INDEX = 0; - private const int CONTROL_AT_NEG_INDEX = 1; - private const int CONTROL_LEFT_POS_INDEX = 2; - private const int CONTROL_LEFT_NEG_INDEX = 3; - private const int CONTROL_UP_POS_INDEX = 4; - private const int CONTROL_UP_NEG_INDEX = 5; - private const int CONTROL_PITCH_POS_INDEX = 6; - private const int CONTROL_PITCH_NEG_INDEX = 7; - private const int CONTROL_YAW_POS_INDEX = 8; - private const int CONTROL_YAW_NEG_INDEX = 9; - private const int CONTROL_FAST_AT_INDEX = 10; - private const int CONTROL_FAST_LEFT_INDEX = 11; - private const int CONTROL_FAST_UP_INDEX = 12; - private const int CONTROL_FLY_INDEX = 13; - private const int CONTROL_STOP_INDEX = 14; - private const int CONTROL_FINISH_ANIM_INDEX = 15; - private const int CONTROL_STAND_UP_INDEX = 16; - private const int CONTROL_SIT_ON_GROUND_INDEX = 17; - private const int CONTROL_MOUSELOOK_INDEX = 18; - private const int CONTROL_NUDGE_AT_POS_INDEX = 19; - private const int CONTROL_NUDGE_AT_NEG_INDEX = 20; - private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21; - private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22; - private const int CONTROL_NUDGE_UP_POS_INDEX = 23; - private const int CONTROL_NUDGE_UP_NEG_INDEX = 24; - private const int CONTROL_TURN_LEFT_INDEX = 25; - private const int CONTROL_TURN_RIGHT_INDEX = 26; - private const int CONTROL_AWAY_INDEX = 27; - private const int CONTROL_LBUTTON_DOWN_INDEX = 28; - private const int CONTROL_LBUTTON_UP_INDEX = 29; - private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30; - private const int CONTROL_ML_LBUTTON_UP_INDEX = 31; - private const int TOTAL_CONTROLS = 32; - - #endregion AgentUpdate Constants - - - /// - /// Constructor, setup callbacks for packets related to our avatar - /// - /// - public MainAvatar(SecondLife client) - { - Client = client; - Status = new MainAvatarStatus(Client); - NetworkManager.PacketCallback callback; - - // Teleport callbacks - callback = new NetworkManager.PacketCallback(TeleportHandler); - Client.Network.RegisterCallback(PacketType.TeleportStart, callback); - Client.Network.RegisterCallback(PacketType.TeleportProgress, callback); - Client.Network.RegisterCallback(PacketType.TeleportFailed, callback); - Client.Network.RegisterCallback(PacketType.TeleportFinish, callback); - Client.Network.RegisterCallback(PacketType.TeleportCancel, callback); - Client.Network.RegisterCallback(PacketType.TeleportLocal, callback); - - // Instant Message callback - Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, new NetworkManager.PacketCallback(InstantMessageHandler)); - - // Chat callback - Client.Network.RegisterCallback(PacketType.ChatFromSimulator, new NetworkManager.PacketCallback(ChatHandler)); - - // Script dialog callback - Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler)); - - // Movement complete callback - Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler)); - - // Health callback - Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler)); - - // Money callbacks - callback = new NetworkManager.PacketCallback(BalanceHandler); - Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, callback); - Client.Network.RegisterCallback(PacketType.MoneySummaryReply, callback); - Client.Network.RegisterCallback(PacketType.AdjustBalance, callback); - - Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(MoneyBalanceReplyHandler)); - - // Group callbacks - Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler)); - Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupHandler)); - Client.Network.RegisterCallback(PacketType.AgentDropGroup, new NetworkManager.PacketCallback(DropGroupHandler)); - - //Agent Update Callback - Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler)); - - // Event queue callback (used for Caps teleports currently) - Client.Network.RegisterEventCallback(new Caps.EventQueueCallback(EventQueueHandler)); - } - - /// - /// Send an Instant Message - /// - /// Target of the Instant Message - /// Text message being sent - public void InstantMessage(LLUUID target, string message) - { - InstantMessage(FirstName + " " + LastName, target, message, LLUUID.Random(), - InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, - LLUUID.Zero, new byte[0]); - } - - /// - /// Send an Instant Message - /// - /// Target of the Instant Message - /// Text message being sent - /// IM session ID (to differentiate between IM windows) - public void InstantMessage(LLUUID target, string message, LLUUID imSessionID) - { - InstantMessage(FirstName + " " + LastName, target, message, imSessionID, - InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, - LLUUID.Zero, new byte[0]); - } - - /// - /// Send an Instant Message - /// - /// The name this IM will show up as being from - /// Key of Avatar - /// Text message being sent - /// IM session ID (to differentiate between IM windows) - /// - public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID, - LLUUID[] conferenceIDs) - { - byte[] binaryBucket; - - if (conferenceIDs != null && conferenceIDs.Length > 0) - { - binaryBucket = new byte[16 * conferenceIDs.Length]; - for (int i = 0; i < conferenceIDs.Length; ++i) - Array.Copy(conferenceIDs[i].Data, 0, binaryBucket, i * 16, 16); - } - else - { - binaryBucket = new byte[0]; - } - - InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent, - InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, binaryBucket); - } - - /// - /// Send an Instant Message - /// - /// The name this IM will show up as being from - /// Key of Avatar - /// Text message being sent - /// IM session ID (to differentiate between IM windows) - /// Type of instant message to send - /// Whether to IM offline avatars as well - /// - /// - /// Packed binary data that is specific to - /// the dialog type - public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID, - InstantMessageDialog dialog, InstantMessageOnline offline, LLVector3 position, LLUUID regionID, - byte[] binaryBucket) - { - ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); - - im.AgentData.AgentID = Client.Network.AgentID; - im.AgentData.SessionID = Client.Network.SessionID; - - im.MessageBlock.Dialog = (byte)dialog; - im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); - im.MessageBlock.FromGroup = false; - im.MessageBlock.ID = imSessionID; - im.MessageBlock.Message = Helpers.StringToField(message); - im.MessageBlock.Offline = (byte)offline; - im.MessageBlock.ToAgentID = target; - - if (binaryBucket != null) - im.MessageBlock.BinaryBucket = binaryBucket; - else - im.MessageBlock.BinaryBucket = new byte[0]; - - // These fields are mandatory, even if we don't have valid values for them - im.MessageBlock.Position = LLVector3.Zero; - //TODO: Allow region id to be correctly set by caller or fetched from Client.* - im.MessageBlock.RegionID = regionID; - - // Send the message - Client.Network.SendPacket(im); - } - - /// - /// Send an Instant Message to a group - /// - /// Key of Group - /// Text Message being sent. - public void InstantMessageGroup(LLUUID groupUUID, string message) - { - InstantMessageGroup(FirstName + " " + LastName, groupUUID, message); - } - - /// - /// Send an Instant Message to a group - /// - /// The name this IM will show up as being from - /// Key of the group - /// Text message being sent - /// This does not appear to function with groups the agent is not in - public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message) - { - ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); - - im.AgentData.AgentID = Client.Network.AgentID; - im.AgentData.SessionID = Client.Network.SessionID; - im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.SessionSend; - im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); - im.MessageBlock.FromGroup = false; - im.MessageBlock.Message = Helpers.StringToField(message); - im.MessageBlock.Offline = 0; - im.MessageBlock.ID = groupUUID; - im.MessageBlock.ToAgentID = groupUUID; - im.MessageBlock.BinaryBucket = new byte[0]; - im.MessageBlock.Position = LLVector3.Zero; - im.MessageBlock.RegionID = LLUUID.Zero; - - // Send the message - Client.Network.SendPacket(im); - } - - /// - /// - /// - /// - /// - /// - /// - public void PointAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, PointAtType type) - { - ViewerEffectPacket effect = new ViewerEffectPacket(); - - effect.AgentData.AgentID = Client.Network.AgentID; - effect.AgentData.SessionID = Client.Network.SessionID; - - effect.Effect = new ViewerEffectPacket.EffectBlock[1]; - effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); - effect.Effect[0].AgentID = Client.Network.AgentID; - effect.Effect[0].Color = LLColor.Black.GetBytes(); - effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f; - effect.Effect[0].ID = LLUUID.Random(); - effect.Effect[0].Type = (byte)EffectType.PointAt; - - byte[] typeData = new byte[57]; - if (sourceAvatar != null) - Array.Copy(sourceAvatar.GetBytes(), typeData, 16); - if (targetObject != null) - Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); - Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24); - typeData[56] = (byte)type; - - effect.Effect[0].TypeData = typeData; - - Client.Network.SendPacket(effect); - } - - /// - /// - /// - /// - /// - /// - /// - public void LookAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LookAtTarget type) - { - ViewerEffectPacket effect = new ViewerEffectPacket(); - - effect.AgentData.AgentID = Client.Network.AgentID; - effect.AgentData.SessionID = Client.Network.SessionID; - - float duration; - - switch (type) - { - case LookAtTarget.Clear: - duration = 0.0f; - break; - case LookAtTarget.Hover: - duration = 1.0f; - break; - case LookAtTarget.FreeLook: - duration = 2.0f; - break; - case LookAtTarget.Idle: - duration = 3.0f; - break; - case LookAtTarget.AutoListen: - case LookAtTarget.Respond: - duration = 4.0f; - break; - case LookAtTarget.None: - case LookAtTarget.Conversation: - case LookAtTarget.Select: - case LookAtTarget.Focus: - case LookAtTarget.Mouselook: - duration = Single.MaxValue / 2.0f; - break; - default: - duration = 0.0f; - break; - } - - effect.Effect = new ViewerEffectPacket.EffectBlock[1]; - effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); - effect.Effect[0].AgentID = Client.Network.AgentID; - effect.Effect[0].Color = LLColor.Black.GetBytes(); - effect.Effect[0].Duration = duration; - effect.Effect[0].ID = LLUUID.Random(); - effect.Effect[0].Type = (byte)EffectType.LookAt; - - byte[] typeData = new byte[57]; - if (sourceAvatar != null) - Array.Copy(sourceAvatar.GetBytes(), typeData, 16); - if (targetObject != null) - Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); - typeData[56] = (byte)type; - - effect.Effect[0].TypeData = typeData; - - Client.Network.SendPacket(effect); - } - - /// - /// - /// - /// - /// - /// - /// - /// - public void BeamEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LLColor color, - float duration) - { - ViewerEffectPacket effect = new ViewerEffectPacket(); - - effect.AgentData.AgentID = Client.Network.AgentID; - effect.AgentData.SessionID = Client.Network.SessionID; - - effect.Effect = new ViewerEffectPacket.EffectBlock[1]; - effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); - effect.Effect[0].AgentID = Client.Network.AgentID; - effect.Effect[0].Color = color.GetBytes(); - effect.Effect[0].Duration = duration; - effect.Effect[0].ID = LLUUID.Random(); - effect.Effect[0].Type = (byte)EffectType.Beam; - - byte[] typeData = new byte[56]; - if (sourceAvatar != null) - Array.Copy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); - if (targetObject != null) - Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); - Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24); - - effect.Effect[0].TypeData = typeData; - - Client.Network.SendPacket(effect); - } - - /// - /// Synchronize the local profile and interests information to the server - /// - public void SetAvatarInformation() - { - // Basic profile properties - AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); - - apup.AgentData.AgentID = this.ID; - apup.AgentData.SessionID = Client.Network.SessionID; - apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText); - apup.PropertiesData.AllowPublish = this.ProfileProperties.AllowPublish; - apup.PropertiesData.FLAboutText = Helpers.StringToField(this.ProfileProperties.FirstLifeText); - apup.PropertiesData.FLImageID = this.ProfileProperties.FirstLifeImage; - apup.PropertiesData.ImageID = this.ProfileProperties.ProfileImage; - apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish; - apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL); - - // Interests - AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket(); - - aiup.AgentData.AgentID = this.ID; - aiup.AgentData.SessionID = Client.Network.SessionID; - aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText); - aiup.PropertiesData.SkillsMask = this.ProfileInterests.SkillsMask; - aiup.PropertiesData.SkillsText = Helpers.StringToField(this.ProfileInterests.SkillsText); - aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask; - aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText); - - //Send packets - Client.Network.SendPacket(apup); - Client.Network.SendPacket(aiup); - } - - /// - /// Send a chat message - /// - /// The Message you're sending out. - /// Channel number (0 would be default 'Say' message, other numbers - /// denote the equivalent of /# in normal client). - /// Chat Type, see above. - public void Chat(string message, int channel, ChatType type) - { - ChatFromViewerPacket chat = new ChatFromViewerPacket(); - chat.AgentData.AgentID = this.ID; - chat.AgentData.SessionID = Client.Network.SessionID; - chat.ChatData.Channel = channel; - chat.ChatData.Message = Helpers.StringToField(message); - chat.ChatData.Type = (byte)type; - - Client.Network.SendPacket(chat); - } - - /// - /// Set the height and the width of the client window. This is used - /// by the server to build a virtual camera frustum for our avatar - /// - /// New height of the viewer window - /// New width of the viewer window - public void SetHeightWidth(ushort height, ushort width) - { - AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket(); - heightwidth.AgentData.AgentID = Client.Network.AgentID; - heightwidth.AgentData.SessionID = Client.Network.SessionID; - heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode; - heightwidth.HeightWidthBlock.Height = height; - heightwidth.HeightWidthBlock.Width = width; - heightwidth.HeightWidthBlock.GenCounter = HeightWidthGenCounter++; - - Client.Network.SendPacket(heightwidth); - } - - /// - /// Sends a request to sit on the specified object - /// - /// LLUUID of the object to sit on - /// Sit at offset - public void RequestSit(LLUUID targetID, LLVector3 offset) - { - AgentRequestSitPacket requestSit = new AgentRequestSitPacket(); - requestSit.AgentData.AgentID = Client.Network.AgentID; - requestSit.AgentData.SessionID = Client.Network.SessionID; - requestSit.TargetObject.TargetID = targetID; - requestSit.TargetObject.Offset = offset; - Client.Network.SendPacket(requestSit); - } - - /// - /// Request the list of muted things for this avatar - /// - public void RequestMuteList() - { - MuteListRequestPacket mute = new MuteListRequestPacket(); - mute.AgentData.AgentID = Client.Network.AgentID; - mute.AgentData.SessionID = Client.Network.SessionID; - mute.MuteData.MuteCRC = 0; - - Client.Network.SendPacket(mute); - } - - /// - /// Request the current L$ balance - /// - public void RequestBalance() - { - MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket(); - money.AgentData.AgentID = Client.Network.AgentID; - money.AgentData.SessionID = Client.Network.SessionID; - money.MoneyData.TransactionID = LLUUID.Zero; - - Client.Network.SendPacket(money); - } - - /// - /// Follows a call to RequestSit() to actually sit on the object - /// - public void Sit() - { - AgentSitPacket sit = new AgentSitPacket(); - sit.AgentData.AgentID = Client.Network.AgentID; - sit.AgentData.SessionID = Client.Network.SessionID; - Client.Network.SendPacket(sit); - } - - /// - /// Give Money to destination Avatar - /// - /// UUID of the Target Avatar - /// Amount in L$ - /// Reason (optional normally) - public void GiveMoney(LLUUID target, int amount, string description) - { - // 5001 - transaction type for av to av money transfers - if (amount > 0) - GiveMoney(target, amount, description, 5001); - else - Client.Log("Attempted to pay zero or negative value " + amount, Helpers.LogLevel.Warning); - } - - /// - /// Give Money to destionation Object or Avatar - /// - /// UUID of the Target Object/Avatar - /// Amount in L$ - /// Reason (Optional normally) - /// The type of transaction. Currently only 5001 is - /// documented for Av->Av money transfers. - public void GiveMoney(LLUUID target, int amount, string description, int transactiontype) - { - MoneyTransferRequestPacket money = new MoneyTransferRequestPacket(); - money.AgentData.AgentID = this.ID; - money.AgentData.SessionID = Client.Network.SessionID; - money.MoneyData.Description = Helpers.StringToField(description); - money.MoneyData.DestID = target; - money.MoneyData.SourceID = this.ID; - money.MoneyData.TransactionType = transactiontype; - money.MoneyData.AggregatePermInventory = 0; //TODO: whats this? - money.MoneyData.AggregatePermNextOwner = 0; //TODO: whats this? - money.MoneyData.Flags = 0; //TODO: whats this? - money.MoneyData.Amount = amount; - - Client.Network.SendPacket(money); - } - - /// - /// Send an AgentAnimation packet that toggles a single animation on - /// - /// The animation to start playing - public void AnimationStart(LLUUID animation) - { - Dictionary animations = new Dictionary(); - animations[animation] = true; - - Animate(animations); - } - - /// - /// Send an AgentAnimation packet that toggles a single animation off - /// - /// The animation to stop playing - public void AnimationStop(LLUUID animation) - { - Dictionary animations = new Dictionary(); - animations[animation] = false; - - Animate(animations); - } - - /// - /// Send an AgentAnimation packet that will toggle animations on or off - /// - /// A list of animation UUIDs, and whether to - /// turn that animation on or off - public void Animate(Dictionary animations) - { - AgentAnimationPacket animate = new AgentAnimationPacket(); - - animate.AgentData.AgentID = Client.Network.AgentID; - animate.AgentData.SessionID = Client.Network.SessionID; - animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count]; - int i = 0; - - foreach (KeyValuePair animation in animations) - { - animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock(); - animate.AnimationList[i].AnimID = animation.Key; - animate.AnimationList[i].StartAnim = animation.Value; - - i++; - } - - Client.Network.SendPacket(animate); - } - - /// - /// Use the autopilot sim function to move the avatar to a new position - /// - /// The z value is currently not handled properly by the simulator - /// Integer value for the global X coordinate to move to - /// Integer value for the global Y coordinate to move to - /// Floating-point value for the Z coordinate to move to - /// AutoPilot(252620, 247078, 20.2674); - public void AutoPilot(ulong globalX, ulong globalY, float z) - { - GenericMessagePacket autopilot = new GenericMessagePacket(); - - autopilot.AgentData.AgentID = Client.Network.AgentID; - autopilot.AgentData.SessionID = Client.Network.SessionID; - autopilot.AgentData.TransactionID = LLUUID.Zero; - autopilot.MethodData.Invoice = LLUUID.Zero; - autopilot.MethodData.Method = Helpers.StringToField("autopilot"); - autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3]; - autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock(); - autopilot.ParamList[0].Parameter = Helpers.StringToField(globalX.ToString()); - autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock(); - autopilot.ParamList[1].Parameter = Helpers.StringToField(globalY.ToString()); - autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock(); - // TODO: Do we need to prevent z coordinates from being sent in 1.4827e-18 notation? - autopilot.ParamList[2].Parameter = Helpers.StringToField(z.ToString()); - - Client.Network.SendPacket(autopilot); - } - - /// - /// Use the autopilot sim function to move the avatar to a new position - /// - /// The z value is currently not handled properly by the simulator - /// Integer value for the local X coordinate to move to - /// Integer value for the local Y coordinate to move to - /// Floating-point value for the Z coordinate to move to - public void AutoPilotLocal(int localX, int localY, float z) - { - uint x, y; - Helpers.LongToUInts(Client.Network.CurrentSim.Handle, out x, out y); - AutoPilot((ulong)(x + localX), (ulong)(y + localY), z); - } - - /// - /// Attempt to look up a simulator name and teleport to the discovered - /// destination - /// - /// Region name to look up - /// Position to teleport to - /// True if the lookup and teleport were successful, otherwise - /// false - public bool Teleport(string simName, LLVector3 position) - { - return Teleport(simName, position, new LLVector3(0, 1.0f, 0)); - } - - /// - /// Attempt to look up a simulator name and teleport to the discovered - /// destination - /// - /// Region name to look up - /// Position to teleport to - /// Target to look at - /// True if the lookup and teleport were successful, otherwise - /// false - public bool Teleport(string simName, LLVector3 position, LLVector3 lookAt) - { - TeleportStat = TeleportStatus.None; - simName = simName.ToLower(); - - if (simName != Client.Network.CurrentSim.Name.ToLower()) - { - // Teleporting to a foreign sim - GridRegion region = Client.Grid.GetGridRegion(simName); - - if (region != null) - { - return Teleport(region.RegionHandle, position, lookAt); - } - else - { - teleportMessage = "Unable to resolve name: " + simName; - TeleportStat = TeleportStatus.Failed; - return false; - } - } - else - { - // Teleporting to the sim we're already in - return Teleport(Client.Network.CurrentSim.Handle, position, lookAt); - } - } - - /// - /// Start a teleport process - /// - /// - /// Position for Teleport - /// - public bool Teleport(ulong regionHandle, LLVector3 position) - { - return Teleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); - } - - /// - /// Start a teleport process - /// - /// - /// Position for Teleport - /// Target to look at - /// - public bool Teleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) - { - TeleportStat = TeleportStatus.None; - TeleportEvent.Reset(); - - RequestTeleport(regionHandle, position, lookAt); - - TeleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false); - - if (TeleportStat == TeleportStatus.None || - TeleportStat == TeleportStatus.Start || - TeleportStat == TeleportStatus.Progress) - { - teleportMessage = "Teleport timed out."; - TeleportStat = TeleportStatus.Failed; - } - - return (TeleportStat == TeleportStatus.Finished); - } - - /// - /// Start a teleport process - /// - /// - /// Position for Teleport - public void RequestTeleport(ulong regionHandle, LLVector3 position) - { - RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); - } - - /// - /// Start a teleport process - /// - /// - /// Position for Teleport - /// Target to look at - public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) - { - TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket(); - teleport.AgentData.AgentID = Client.Network.AgentID; - teleport.AgentData.SessionID = Client.Network.SessionID; - teleport.Info.LookAt = lookAt; - teleport.Info.Position = position; - teleport.Info.RegionHandle = regionHandle; - - Client.Log("Teleporting to region " + regionHandle.ToString(), Helpers.LogLevel.Info); - - Client.Network.SendPacket(teleport); - } - - /// - /// Respond to a teleport lure by either accepting it and initiating - /// the teleport, or denying it - /// - /// UUID of the avatar requesting the teleport - /// Accept the teleport request or deny it - public void TeleportLureRespond(LLUUID requesterID, bool accept) - { - InstantMessage(FirstName + " " + LastName, requesterID, String.Empty, LLUUID.Random(), - accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport, - InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); - - if (accept) - { - TeleportLureRequestPacket lure = new TeleportLureRequestPacket(); - - lure.Info.AgentID = Client.Network.AgentID; - lure.Info.SessionID = Client.Network.SessionID; - lure.Info.LureID = Client.Network.AgentID; - lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure; - - Client.Network.SendPacket(lure); - } - } - - /// - /// Grabs an object - /// - /// Local ID of Object to grab - public void Grab(uint objectLocalID) - { - ObjectGrabPacket grab = new ObjectGrabPacket(); - grab.AgentData.AgentID = Client.Network.AgentID; - grab.AgentData.SessionID = Client.Network.SessionID; - grab.ObjectData.LocalID = objectLocalID; - grab.ObjectData.GrabOffset = new LLVector3(0, 0, 0); - Client.Network.SendPacket(grab); - } - - /// - /// Drags on an object - /// - /// Strangely, LLUID instead of local ID - /// Drag target in region coordinates - public void GrabUpdate(LLUUID objectID, LLVector3 grabPosition) - { - ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket(); - grab.AgentData.AgentID = Client.Network.AgentID; - grab.AgentData.SessionID = Client.Network.SessionID; - grab.ObjectData.ObjectID = objectID; - grab.ObjectData.GrabOffsetInitial = new LLVector3(0, 0, 0); - grab.ObjectData.GrabPosition = grabPosition; - grab.ObjectData.TimeSinceLast = 0; - Client.Network.SendPacket(grab); - } - - /// - /// Releases a grabbed object - /// - public void DeGrab(uint objectLocalID) - { - ObjectDeGrabPacket degrab = new ObjectDeGrabPacket(); - degrab.AgentData.AgentID = Client.Network.AgentID; - degrab.AgentData.SessionID = Client.Network.SessionID; - degrab.ObjectData.LocalID = objectLocalID; - Client.Network.SendPacket(degrab); - } - - /// - /// Touches an object - /// - public void Touch(uint objectLocalID) - { - Client.Self.Grab(objectLocalID); - Client.Self.DeGrab(objectLocalID); - } - - /// - /// Request to join a group. If there is an enrollment fee it will - /// automatically be deducted from your balance - /// - /// The group to attempt to join - public void RequestJoinGroup(LLUUID groupID) - { - JoinGroupRequestPacket join = new JoinGroupRequestPacket(); - - join.AgentData.AgentID = Client.Network.AgentID; - join.AgentData.SessionID = Client.Network.SessionID; - join.GroupData.GroupID = groupID; - - Client.Network.SendPacket(join); - } - - /// - /// Request to leave a group - /// - /// The group to attempt to leave - public void RequestLeaveGroup(LLUUID groupID) - { - LeaveGroupRequestPacket leave = new LeaveGroupRequestPacket(); - - leave.AgentData.AgentID = Client.Network.AgentID; - leave.AgentData.SessionID = Client.Network.SessionID; - leave.GroupData.GroupID = groupID; - - Client.Network.SendPacket(leave); - } - - /// - /// Set our current active group - /// - /// The group we are a member of that we want to - /// activate - public void ActivateGroup(LLUUID groupID) - { - ActivateGroupPacket activate = new ActivateGroupPacket(); - - activate.AgentData.AgentID = Client.Network.AgentID; - activate.AgentData.SessionID = Client.Network.SessionID; - activate.AgentData.GroupID = groupID; - - Client.Network.SendPacket(activate); - } - - /// - /// Move an agent in to a simulator. This packet is the last packet - /// needed to complete the transition in to a new simulator - /// - /// - public void CompleteAgentMovement(Simulator simulator) - { - CompleteAgentMovementPacket move = new CompleteAgentMovementPacket(); - - move.AgentData.AgentID = Client.Network.AgentID; - move.AgentData.SessionID = Client.Network.SessionID; - move.AgentData.CircuitCode = Client.Network.CircuitCode; - - Client.Network.SendPacket(move, simulator); - } - - /// - /// Sends camera and action updates to the server including the - /// position and orientation of our camera, and a ControlFlags field - /// specifying our current movement actions - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public void UpdateCamera(MainAvatar.AgentUpdateFlags controlFlags, LLVector3 position, LLVector3 forwardAxis, - LLVector3 leftAxis, LLVector3 upAxis, LLQuaternion bodyRotation, LLQuaternion headRotation, float farClip, - bool reliable) - { - AgentUpdatePacket update = new AgentUpdatePacket(); - - update.AgentData.AgentID = Client.Network.AgentID; - update.AgentData.SessionID = Client.Network.SessionID; - update.AgentData.State = 0; - update.AgentData.BodyRotation = bodyRotation; - update.AgentData.HeadRotation = headRotation; - update.AgentData.CameraCenter = position; - update.AgentData.CameraAtAxis = forwardAxis; - update.AgentData.CameraLeftAxis = leftAxis; - update.AgentData.CameraUpAxis = upAxis; - update.AgentData.Far = farClip; - update.AgentData.ControlFlags = (uint)controlFlags; - update.AgentData.Flags = 0; - update.Header.Reliable = reliable; - - Client.Network.SendPacket(update); - } - - /// - /// Take an incoming ImprovedInstantMessage packet, auto-parse, and if - /// OnInstantMessage is defined call that with the appropriate arguments - /// - /// Incoming ImprovedInstantMessagePacket - /// Unused - private void InstantMessageHandler(Packet packet, Simulator simulator) - { - if (packet.Type == PacketType.ImprovedInstantMessage) - { - ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet; - - if (OnInstantMessage != null) - { - OnInstantMessage( - im.AgentData.AgentID - , Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName), - im.MessageBlock.ToAgentID - , im.MessageBlock.ParentEstateID - , im.MessageBlock.RegionID - , im.MessageBlock.Position - , (InstantMessageDialog)im.MessageBlock.Dialog - , im.MessageBlock.FromGroup - , im.MessageBlock.ID - , new DateTime(im.MessageBlock.Timestamp) - , Helpers.FieldToUTF8String(im.MessageBlock.Message) - , (InstantMessageOnline)im.MessageBlock.Offline - , im.MessageBlock.BinaryBucket - ); - } - } - } - - /// - /// Take an incoming Chat packet, auto-parse, and if OnChat is defined call - /// that with the appropriate arguments. - /// - /// Incoming ChatFromSimulatorPacket - /// Unused - private void ChatHandler(Packet packet, Simulator simulator) - { - if (OnChat != null) - { - ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet; - - OnChat(Helpers.FieldToUTF8String(chat.ChatData.Message) - , (ChatAudibleLevel)chat.ChatData.Audible - , (ChatType)chat.ChatData.ChatType - , (ChatSourceType)chat.ChatData.SourceType - , Helpers.FieldToUTF8String(chat.ChatData.FromName) - , chat.ChatData.SourceID - , chat.ChatData.OwnerID - , chat.ChatData.Position - ); - } - } - - /// - /// Used for parsing llDialog's - /// - /// Incoming ScriptDialog packet - /// Unused - private void ScriptDialogHandler(Packet packet, Simulator simulator) - { - if (OnScriptDialog != null) - { - ScriptDialogPacket dialog = (ScriptDialogPacket)packet; - List buttons = new List(); - - foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons) - { - buttons.Add(Helpers.FieldToString(button.ButtonLabel)); - } - - OnScriptDialog(Helpers.FieldToUTF8String(dialog.Data.Message), - Helpers.FieldToString(dialog.Data.ObjectName), - dialog.Data.ImageID, - dialog.Data.ObjectID, - Helpers.FieldToUTF8String(dialog.Data.FirstName), - Helpers.FieldToUTF8String(dialog.Data.LastName), - dialog.Data.ChatChannel, - buttons); - } - } - - /// - /// Update client's Position, LookAt and region handle from incoming packet - /// - /// Incoming AgentMovementCompletePacket - /// Unused - private void MovementCompleteHandler(Packet packet, Simulator simulator) - { - AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet; - - this.Position = movement.Data.Position; - this.LookAt = movement.Data.LookAt; - simulator.Handle = movement.Data.RegionHandle; - } - - /// - /// Update Client Avatar's health via incoming packet - /// - /// Incoming HealthMessagePacket - /// Unused - private void HealthHandler(Packet packet, Simulator simulator) - { - health = ((HealthMessagePacket)packet).HealthData.Health; - } - - private void JoinGroupHandler(Packet packet, Simulator simulator) - { - if (OnJoinGroup != null) - { - JoinGroupReplyPacket reply = (JoinGroupReplyPacket)packet; - - OnJoinGroup(reply.GroupData.GroupID, reply.GroupData.Success); - } - } - - private void LeaveGroupHandler(Packet packet, Simulator simulator) - { - if (OnLeaveGroup != null) - { - LeaveGroupReplyPacket reply = (LeaveGroupReplyPacket)packet; - - OnLeaveGroup(reply.GroupData.GroupID, reply.GroupData.Success); - } - } - - public void AgentDataUpdateHandler(Packet packet, Simulator simulator) - { - AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet; - if (p.AgentData.AgentID == simulator.Client.Network.AgentID) { - activeGroup = p.AgentData.ActiveGroupID; - } - } - - private void DropGroupHandler(Packet packet, Simulator simulator) - { - if (OnGroupDropped != null) - { - OnGroupDropped(((AgentDropGroupPacket)packet).AgentData.GroupID); - } - } - - /// - /// Update Client Avatar's L$ balance from incoming packet - /// - /// Incoming MoneyBalanceReplyPacket - /// Unused - private void BalanceHandler(Packet packet, Simulator simulator) - { - if (packet.Type == PacketType.MoneySummaryReply) - { - balance = ((MoneySummaryReplyPacket)packet).MoneyData.Balance; - } - else if (packet.Type == PacketType.AdjustBalance) - { - balance += ((AdjustBalancePacket)packet).AgentData.Delta; - } - - if (OnBalanceUpdated != null) - { - OnBalanceUpdated(balance); - } - } - - /// - /// Update Client Avatar's L$ balance from incoming packet, and - /// trigger a complete callback with all available fields. - /// - /// - /// - private void MoneyBalanceReplyHandler(Packet packet, Simulator simulator) - { - MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet; - balance = mbrp.MoneyData.MoneyBalance; - - if (OnMoneyBalanceReplyReceived != null) - { - OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID, mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance, mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted, Helpers.FieldToString(mbrp.MoneyData.Description)); - } - - if (OnBalanceUpdated != null) - { - OnBalanceUpdated(balance); - } - - } - - private void EventQueueHandler(string message, object body) - { - if (message == "TeleportFinish") - { - Hashtable tpt = (Hashtable)body; - Hashtable info = (Hashtable)tpt["Info"]; - - // Backwards compatibility hack - TeleportFinishPacket packet = new TeleportFinishPacket(); - - packet.Info.SimIP = Helpers.BytesToUIntBig((byte[])info["SimIP"]); - packet.Info.LocationID = Helpers.BytesToUInt((byte[])info["LocationID"]); - packet.Info.TeleportFlags = Helpers.BytesToUInt((byte[])info["TeleportFlags"]); - packet.Info.AgentID = (LLUUID)info["AgentID"]; - packet.Info.RegionHandle = Helpers.BytesToUInt64((byte[])info["RegionHandle"]); - packet.Info.SeedCapability = Helpers.StringToField((string)info["SeedCapability"]); - packet.Info.SimPort = (ushort)(long)info["SimPort"]; - packet.Info.SimAccess = (byte)(long)info["SimAccess"]; - - Client.DebugLog("Received a TeleportFinish event, SimIP: " + new IPAddress(packet.Info.SimIP) + - ", LocationID: " + packet.Info.LocationID + ", RegionHandle: " + packet.Info.RegionHandle); - - TeleportHandler(packet, Client.Network.CurrentSim); - } - else - { - Client.Log("Received unhandled event " + message + " in the EventQueueHandler", - Helpers.LogLevel.Warning); - } - } - - /// - /// Handler for teleport Requests - /// - /// Incoming TeleportHandler packet - /// Simulator sending teleport information - private void TeleportHandler(Packet packet, Simulator simulator) - { - bool finished = false; - TeleportFlags flags = TeleportFlags.Default; - - if (packet.Type == PacketType.TeleportStart) - { - TeleportStartPacket start = (TeleportStartPacket)packet; - - teleportMessage = "Teleport started"; - flags = (TeleportFlags)start.Info.TeleportFlags; - TeleportStat = TeleportStatus.Start; - - Client.DebugLog("TeleportStart received from " + simulator.ToString() + ", Flags: " + flags.ToString()); - } - else if (packet.Type == PacketType.TeleportProgress) - { - TeleportProgressPacket progress = (TeleportProgressPacket)packet; - - teleportMessage = Helpers.FieldToUTF8String(progress.Info.Message); - flags = (TeleportFlags)progress.Info.TeleportFlags; - TeleportStat = TeleportStatus.Progress; - - Client.DebugLog("TeleportProgress received from " + simulator.ToString() + ", Flags: " + flags.ToString()); - } - else if (packet.Type == PacketType.TeleportFailed) - { - TeleportFailedPacket failed = (TeleportFailedPacket)packet; - - teleportMessage = Helpers.FieldToUTF8String(failed.Info.Reason); - TeleportStat = TeleportStatus.Failed; - finished = true; - - Client.DebugLog("TeleportFailed received from " + simulator.ToString() + ", Reason: " + teleportMessage); - } - else if (packet.Type == PacketType.TeleportFinish) - { - TeleportFinishPacket finish = (TeleportFinishPacket)packet; - - Simulator previousSim = Client.Network.CurrentSim; - flags = (TeleportFlags)finish.Info.TeleportFlags; - string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability); - IPAddress simIP = new IPAddress(finish.Info.SimIP); - finished = true; - - Client.DebugLog("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString()); - - // Disable CAPS on the current sim since we are moving - if (Client.Network.CurrentCaps != null) Client.Network.CurrentCaps.Dead = true; - - // Connect to the new sim - Simulator sim = Client.Network.Connect(simIP, finish.Info.SimPort, true, seedcaps); - - if (sim != null) - { - teleportMessage = "Teleport finished"; - TeleportStat = TeleportStatus.Finished; - - // Disconnect from the previous sim - Client.Network.DisconnectSim(previousSim); - - Client.Log("Moved to new sim " + sim.ToString(), Helpers.LogLevel.Info); - } - else - { - teleportMessage = "Failed to connect to the new sim after a teleport"; - TeleportStat = TeleportStatus.Failed; - - // Attempt to reconnect to the previous simulator - // TODO: This hasn't been tested at all - Client.Network.Connect(previousSim.IPEndPoint.Address, (ushort)previousSim.IPEndPoint.Port, - true, Client.Network.CurrentCaps.Seedcaps); - - Client.Log(teleportMessage, Helpers.LogLevel.Warning); - } - } - else if (packet.Type == PacketType.TeleportCancel) - { - //TeleportCancelPacket cancel = (TeleportCancelPacket)packet; - - teleportMessage = "Cancelled."; - TeleportStat = TeleportStatus.Cancelled; - finished = true; - - Client.DebugLog("TeleportCancel received from " + simulator.ToString()); - } - else if (packet.Type == PacketType.TeleportLocal) - { - TeleportLocalPacket local = (TeleportLocalPacket)packet; - - teleportMessage = "Teleport finished"; - flags = (TeleportFlags)local.Info.TeleportFlags; - TeleportStat = TeleportStatus.Finished; - LookAt = local.Info.LookAt; - Position = local.Info.Position; - // This field is apparently not used for anything - //local.Info.LocationID; - finished = true; - - Client.DebugLog("TeleportLocal received from " + simulator.ToString() + ", Flags: " + flags.ToString()); - } - - if (OnTeleport != null) - { - try { OnTeleport(teleportMessage, TeleportStat, flags); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - - if (finished) TeleportEvent.Set(); - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Timers; +using System.Net; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Text; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Class to hold Client Avatar's data + /// + public partial class MainAvatar + { + #region Enums + + /// + /// Used to specify movement actions for your agent + /// + [Flags] + public enum AgentUpdateFlags + { + /// Empty flag + NONE = 0, + /// Move Forward (SL Keybinding: W/Up Arrow) + AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX, + /// Move Backward (SL Keybinding: S/Down Arrow) + AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX, + /// Move Left (SL Keybinding: Shift-(A/Left Arrow)) + AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX, + /// Move Right (SL Keybinding: Shift-(D/Right Arrow)) + AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX, + /// Not Flying: Jump/Flying: Move Up (SL Keybinding: E) + AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX, + /// Not Flying: Croutch/Flying: Move Down (SL Keybinding: C) + AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX, + /// Unused + AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX, + /// Unused + AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX, + /// Unused + AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX, + /// Unused + AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX, + /// ORed with AGENT_CONTROL_AT_* if the keyboard is being used + AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX, + /// ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used + AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX, + /// ORed with AGENT_CONTROL_UP_* if the keyboard is being used + AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX, + /// Fly + AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX, + /// + AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX, + /// Finish our current animation + AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX, + /// Stand up from the ground or a prim seat + AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX, + /// Sit on the ground at our current location + AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX, + /// Whether mouselook is currently enabled + AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX, + /// Legacy, used if a key was pressed for less than a certain amount of time + AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX, + /// + AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX, + /// + AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX, + /// Set when the avatar is idled or set to away. Note that the away animation is + /// activated separately from setting this flag + AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX, + /// + AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX, + /// + AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX, + /// + AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX, + /// + AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX + } + + /// + /// Current teleport status + /// + public enum TeleportStatus + { + /// Unknown status + None, + /// Teleport initialized + Start, + /// Teleport in progress + Progress, + /// Teleport failed + Failed, + /// Teleport completed + Finished, + /// Teleport cancelled + Cancelled + } + + /// + /// Special commands used in Instant Messages + /// + public enum InstantMessageDialog : byte + { + /// Indicates a regular IM from another agent + MessageFromAgent = 0, + /// Simple notification box with an OK button + MessageBox = 1, + /// Used to show a countdown notification with an OK + /// button, deprecated now + [Obsolete] + MessageBoxCountdown = 2, + /// You've been invited to join a group. + GroupInvitation = 3, + /// Inventory offer + InventoryOffered = 4, + /// Accepted inventory offer + InventoryAccepted = 5, + /// Declined inventory offer + InventoryDeclined = 6, + /// Group vote + GroupVote = 7, + /// A message to everyone in the agent's group, no longer + /// used + [Obsolete] + DeprecatedGroupMessage = 8, + /// An object is offering its inventory + TaskInventoryOffered = 9, + /// Accept an inventory offer from an object + TaskInventoryAccepted = 10, + /// Decline an inventory offer from an object + TaskInventoryDeclined = 11, + /// Unknown + NewUserDefault = 12, + /// Start a session, or add users to a session + SessionAdd = 13, + /// Start a session, but don't prune offline users + SessionOfflineAdd = 14, + /// Start a session with your group + SessionGroupStart = 15, + /// Start a session without a calling card (finder or objects) + SessionCardlessStart = 16, + /// Send a message to a session + SessionSend = 17, + /// Leave a session + SessionDrop = 18, + /// Indicates that the IM is from an object + MessageFromObject = 19, + /// sent an IM to a busy user, this is the auto response + BusyAutoResponse = 20, + /// Shows the message in the console and chat history + ConsoleAndChatHistory = 21, + /// IM Types used for luring your friends + RequestTeleport = 22, + /// Response sent to the agent which inititiated a teleport invitation + AcceptTeleport = 23, + /// Response sent to the agent which inititiated a teleport invitation + DenyTeleport = 24, + /// Only useful if you have Linden permissions + GodLikeRequestTeleport = 25, + /// A placeholder type for future expansion, currently not + /// used + CurrentlyUnused = 26, + /// Notification of a new group election, this is + /// deprecated + [Obsolete] + DeprecatedGroupElection = 27, + /// IM to tell the user to go to an URL + GotoUrl = 28, + /// IM for help + Session911Start = 29, + /// IM sent automatically on call for help, sends a lure + /// to each Helper reached + Lure911 = 30, + /// Like an IM but won't go to email + FromTaskAsAlert = 31, + /// IM from a group officer to all group members + GroupNotice = 32, + /// Unknown + GroupNoticeInventoryAccepted = 33, + /// Unknown + GroupNoticeInventoryDeclined = 34, + /// Accept a group invitation + GroupInvitationAccept = 35, + /// Decline a group invitation + GroupInvitationDecline = 36, + /// Unknown + GroupNoticeRequested = 37, + /// An avatar is offering you friendship + FriendshipOffered = 38, + /// An avatar has accepted your friendship offer + FriendshipAccepted = 39, + /// An avatar has declined your friendship offer + FriendshipDeclined = 40, + /// Indicates that a user has started typing + StartTyping = 41, + /// Indicates that a user has stopped typing + StopTyping = 42 + } + + /// + /// Flag in Instant Messages, whether the IM should be delivered to + /// offline avatars as well + /// + public enum InstantMessageOnline + { + /// Only deliver to online avatars + Online = 0, + /// If the avatar is offline the message will be held until + /// they login next, and possibly forwarded to their e-mail account + Offline = 1 + } + + /// + /// Conversion type to denote Chat Packet types in an easier-to-understand format + /// + public enum ChatType : byte + { + /// Whisper (5m radius) + Whisper = 0, + /// Normal chat (10/20m radius), what the official viewer typically sends + Normal = 1, + /// Shouting! (100m radius) + Shout = 2, + /// Say chat (10/20m radius) - The official viewer will + /// print "[4:15] You say, hey" instead of "[4:15] You: hey" + [Obsolete] + Say = 3, + /// Event message when an Avatar has begun to type + StartTyping = 4, + /// Event message when an Avatar has stopped typing + StopTyping = 5, + /// Unknown + Debug = 6 + } + + /// + /// Identifies the source of a chat message + /// + public enum ChatSourceType : byte + { + /// Chat from the grid or simulator + System = 0, + /// Chat from another avatar + Agent = 1, + /// Chat from an object + Object = 2 + } + + /// + /// + /// + public enum ChatAudibleLevel : sbyte + { + /// + Not = -1, + /// + Barely = 0, + /// + Fully = 1 + } + + /// + /// Effect type used in ViewerEffect packets + /// + public enum EffectType : byte + { + /// Place floating text above an object + Text = 0, + /// Unknown, probably places an icon above an object + Icon, + /// Unknown + Connector, + /// Unknown + FlexibleObject, + /// Unknown + AnimalControls, + /// Unknown + AnimationObject, + /// Unknown + Cloth, + /// Project a beam from a source to a destination, such as + /// the one used when editing an object + Beam, + /// Not implemented yet + Glow, + /// Unknown + Point, + /// Unknown + Trail, + /// Create a swirl of particles around an object + Sphere, + /// Unknown + Spiral, + /// Unknown + Edit, + /// Cause an avatar to look at an object + LookAt, + /// Cause an avatar to point at an object + PointAt + } + + /// + /// The action an avatar is doing when looking at something, used in + /// ViewerEffect packets for the LookAt effect + /// + public enum LookAtTarget : byte + { + /// + None, + /// + Idle, + /// + AutoListen, + /// + FreeLook, + /// + Respond, + /// + Hover, + /// Deprecated + Conversation, + /// + Select, + /// + Focus, + /// + Mouselook, + /// + Clear + } + + /// + /// The action an avatar is doing when pointing at something, used in + /// ViewerEffect packets for the PointAt effect + /// + public enum PointAtType : byte + { + /// + None, + /// + Select, + /// + Grab, + /// + Clear + } + + /// + /// + /// + [Flags] + public enum TeleportFlags : uint + { + /// + Default = 0, + /// + SetHomeToTarget = 1 << 0, + /// + SetLastToTarget = 1 << 1, + /// + ViaLure = 1 << 2, + /// + ViaLandmark = 1 << 3, + /// + ViaLocation = 1 << 4, + /// + ViaHome = 1 << 5, + /// + ViaTelehub = 1 << 6, + /// + ViaLogin = 1 << 7, + /// + ViaGodlikeLure = 1 << 8, + /// + Godlike = 1 << 9, + /// + NineOneOne = 1 << 10, + /// + DisableCancel = 1 << 11, + /// + ViaRegionID = 1 << 12, + /// + IsFlying = 1 << 13 + } + + /// + /// + /// + [Flags] + public enum TeleportLureFlags + { + /// + NormalLure = 0, + /// + GodlikeLure = 1, + /// + GodlikePursuit = 2 + } + + #endregion + + + #region Callbacks & Events + /// + /// Triggered on incoming chat messages + /// + /// Text of chat message + /// Audible level of this chat message + /// Type of chat (whisper, shout, status, etc.) + /// Source of the chat message + /// Name of the sending object + /// + /// + /// + public delegate void ChatCallback(string message, ChatAudibleLevel audible, ChatType type, + ChatSourceType sourceType, string fromName, LLUUID id, LLUUID ownerid, LLVector3 position); + + /// + /// Triggered when a script pops up a dialog box + /// + /// The dialog box message + /// Name of the object that sent the dialog + /// Image to be displayed in the dialog + /// ID of the object that sent the dialog + /// First name of the object owner + /// Last name of the object owner + /// Chat channel that the object is communicating on + /// List of button labels + public delegate void ScriptDialogCallback(string message, string objectName, LLUUID imageID, + LLUUID objectID, string firstName, string lastName, int chatChannel, List buttons); + + /// + /// Triggered when the L$ account balance for this avatar changes + /// + /// The new account balance + public delegate void BalanceCallback(int balance); + + /// + /// Triggered on Money Balance Reply + /// + /// ID provided in Request Money Balance, or auto-generated by system events + /// Was the transaction successful + /// Current balance + /// + /// + /// + public delegate void MoneyBalanceReplyCallback(LLUUID transactionID, bool transactionSuccess, int balance, int metersCredit, int metersCommitted, string description); + + /// + /// Tiggered on incoming instant messages + /// + /// Key of sender + /// Name of sender + /// Key of destination Avatar + /// ID of originating Estate + /// Key of originating Region + /// Coordinates in originating Region + /// + /// Group IM session toggle + /// Key of IM Session + /// Timestamp of message + /// Text of message + /// Enum of whether this message is held for + /// offline avatars + /// + public delegate void InstantMessageCallback(LLUUID fromAgentID, string fromAgentName, + LLUUID toAgentID, uint parentEstateID, LLUUID regionID, LLVector3 position, + InstantMessageDialog dialog, bool groupIM, LLUUID imSessionID, DateTime timestamp, string message, + InstantMessageOnline offline, byte[] binaryBucket); + + /// + /// Triggered for any status updates of a teleport (progress, failed, succeeded) + /// + /// A message about the current teleport status + /// The current status of the teleport + /// Various flags describing the teleport + public delegate void TeleportCallback(string message, TeleportStatus status, TeleportFlags flags); + + /// + /// Reply to a request to join a group, informs whether it was successful or not + /// + /// The group we attempted to join + /// Whether we joined the group or not + public delegate void JoinGroupCallback(LLUUID groupID, bool success); + + /// + /// Reply to a request to leave a group, informs whether it was successful or not + /// + /// The group we attempted to leave + /// Whether we left the group or not + public delegate void LeaveGroupCallback(LLUUID groupID, bool success); + + /// + /// Informs the avatar that it is no longer a member of a group + /// + /// The group we are no longer a member of + public delegate void GroupDroppedCallback(LLUUID groupID); + + + /// Callback for incoming chat packets + public event ChatCallback OnChat; + /// Callback for pop-up dialogs from scripts + public event ScriptDialogCallback OnScriptDialog; + /// Callback for incoming IMs + public event InstantMessageCallback OnInstantMessage; + /// Callback for Teleport request update + public event TeleportCallback OnTeleport; + /// Callback for incoming change in L$ balance + public event BalanceCallback OnBalanceUpdated; + /// Callback for incoming Money Balance Replies + public event MoneyBalanceReplyCallback OnMoneyBalanceReplyReceived; + /// Callback reply for an attempt to join a group + public event JoinGroupCallback OnJoinGroup; + /// Callback reply for an attempt to leave a group + public event LeaveGroupCallback OnLeaveGroup; + /// Callback for informing the avatar that it is no longer a member of a group + public event GroupDroppedCallback OnGroupDropped; + + #endregion + + + #region Public Members + + /// Your (client) avatar UUID + public LLUUID ID = LLUUID.Zero; + /// Your (client) avatar ID, local to the current region/sim + public uint LocalID = 0; + /// Avatar First Name (i.e. Philip) + public string FirstName = String.Empty; + /// Avatar Last Name (i.e. Linden) + public string LastName = String.Empty; + /// Positive and negative ratings + /// This information is read-only and any changes will not be + /// reflected on the server + public Avatar.Statistics ProfileStatistics = new Avatar.Statistics(); + /// Avatar properties including about text, profile URL, image IDs and + /// publishing settings + /// If you change fields in this struct, the changes will not + /// be reflected on the server until you call SetAvatarInformation + public Avatar.AvatarProperties ProfileProperties = new Avatar.AvatarProperties(); + /// Avatar interests including spoken languages, skills, and "want to" + /// choices + /// If you change fields in this struct, the changes will not + /// be reflected on the server until you call SetAvatarInformation + public Avatar.Interests ProfileInterests = new Avatar.Interests(); + /// Current position of avatar + public LLVector3 Position = LLVector3.Zero; + /// Current rotation of avatar + public LLQuaternion Rotation = LLQuaternion.Identity; + /// + public LLVector4 CollisionPlane = LLVector4.Zero; + /// + public LLVector3 Velocity = LLVector3.Zero; + /// + public LLVector3 Acceleration = LLVector3.Zero; + /// + public LLVector3 AngularVelocity = LLVector3.Zero; + /// The point the avatar is currently looking at + /// (may not stay updated) + public LLVector3 LookAt = LLVector3.Zero; + /// Position avatar client will goto when login to 'home' or during + /// teleport request to 'home' region. + public LLVector3 HomePosition = LLVector3.Zero; + /// LookAt point saved/restored with HomePosition + public LLVector3 HomeLookAt = LLVector3.Zero; + /// Used for camera and control key state tracking + public MainAvatarStatus Status; + /// The UUID of your root inventory folder + public LLUUID InventoryRootFolderUUID = LLUUID.Zero; + + /// Gets the health of the agent + public float Health { get { return health; } } + /// Gets the current balance of the agent + public int Balance { get { return balance; } } + /// Gets the local ID of the prim the avatar is sitting on, + /// zero if the avatar is not currently sitting + public uint SittingOn { get { return sittingOn; } } + /// Gets the UUID of the active group. + public LLUUID ActiveGroup { get { return activeGroup; } } + + #endregion Public Members + + + internal uint sittingOn = 0; + internal string teleportMessage = String.Empty; + + private SecondLife Client; + private TeleportStatus TeleportStat = TeleportStatus.None; + private ManualResetEvent TeleportEvent = new ManualResetEvent(false); + private uint HeightWidthGenCounter = 0; + private float health = 0.0f; + private int balance = 0; + private LLUUID activeGroup = LLUUID.Zero; + + + #region AgentUpdate Constants + + private const int CONTROL_AT_POS_INDEX = 0; + private const int CONTROL_AT_NEG_INDEX = 1; + private const int CONTROL_LEFT_POS_INDEX = 2; + private const int CONTROL_LEFT_NEG_INDEX = 3; + private const int CONTROL_UP_POS_INDEX = 4; + private const int CONTROL_UP_NEG_INDEX = 5; + private const int CONTROL_PITCH_POS_INDEX = 6; + private const int CONTROL_PITCH_NEG_INDEX = 7; + private const int CONTROL_YAW_POS_INDEX = 8; + private const int CONTROL_YAW_NEG_INDEX = 9; + private const int CONTROL_FAST_AT_INDEX = 10; + private const int CONTROL_FAST_LEFT_INDEX = 11; + private const int CONTROL_FAST_UP_INDEX = 12; + private const int CONTROL_FLY_INDEX = 13; + private const int CONTROL_STOP_INDEX = 14; + private const int CONTROL_FINISH_ANIM_INDEX = 15; + private const int CONTROL_STAND_UP_INDEX = 16; + private const int CONTROL_SIT_ON_GROUND_INDEX = 17; + private const int CONTROL_MOUSELOOK_INDEX = 18; + private const int CONTROL_NUDGE_AT_POS_INDEX = 19; + private const int CONTROL_NUDGE_AT_NEG_INDEX = 20; + private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21; + private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22; + private const int CONTROL_NUDGE_UP_POS_INDEX = 23; + private const int CONTROL_NUDGE_UP_NEG_INDEX = 24; + private const int CONTROL_TURN_LEFT_INDEX = 25; + private const int CONTROL_TURN_RIGHT_INDEX = 26; + private const int CONTROL_AWAY_INDEX = 27; + private const int CONTROL_LBUTTON_DOWN_INDEX = 28; + private const int CONTROL_LBUTTON_UP_INDEX = 29; + private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30; + private const int CONTROL_ML_LBUTTON_UP_INDEX = 31; + private const int TOTAL_CONTROLS = 32; + + #endregion AgentUpdate Constants + + + /// + /// Constructor, setup callbacks for packets related to our avatar + /// + /// + public MainAvatar(SecondLife client) + { + Client = client; + Status = new MainAvatarStatus(Client); + NetworkManager.PacketCallback callback; + + // Teleport callbacks + callback = new NetworkManager.PacketCallback(TeleportHandler); + Client.Network.RegisterCallback(PacketType.TeleportStart, callback); + Client.Network.RegisterCallback(PacketType.TeleportProgress, callback); + Client.Network.RegisterCallback(PacketType.TeleportFailed, callback); + Client.Network.RegisterCallback(PacketType.TeleportFinish, callback); + Client.Network.RegisterCallback(PacketType.TeleportCancel, callback); + Client.Network.RegisterCallback(PacketType.TeleportLocal, callback); + + // Instant Message callback + Client.Network.RegisterCallback(PacketType.ImprovedInstantMessage, new NetworkManager.PacketCallback(InstantMessageHandler)); + + // Chat callback + Client.Network.RegisterCallback(PacketType.ChatFromSimulator, new NetworkManager.PacketCallback(ChatHandler)); + + // Script dialog callback + Client.Network.RegisterCallback(PacketType.ScriptDialog, new NetworkManager.PacketCallback(ScriptDialogHandler)); + + // Movement complete callback + Client.Network.RegisterCallback(PacketType.AgentMovementComplete, new NetworkManager.PacketCallback(MovementCompleteHandler)); + + // Health callback + Client.Network.RegisterCallback(PacketType.HealthMessage, new NetworkManager.PacketCallback(HealthHandler)); + + // Money callbacks + callback = new NetworkManager.PacketCallback(BalanceHandler); + Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, callback); + Client.Network.RegisterCallback(PacketType.MoneySummaryReply, callback); + Client.Network.RegisterCallback(PacketType.AdjustBalance, callback); + + Client.Network.RegisterCallback(PacketType.MoneyBalanceReply, new NetworkManager.PacketCallback(MoneyBalanceReplyHandler)); + + // Group callbacks + Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupHandler)); + Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupHandler)); + Client.Network.RegisterCallback(PacketType.AgentDropGroup, new NetworkManager.PacketCallback(DropGroupHandler)); + + //Agent Update Callback + Client.Network.RegisterCallback(PacketType.AgentDataUpdate, new NetworkManager.PacketCallback(AgentDataUpdateHandler)); + + // Event queue callback (used for Caps teleports currently) + Client.Network.RegisterEventCallback(new Caps.EventQueueCallback(EventQueueHandler)); + } + + /// + /// Send an Instant Message + /// + /// Target of the Instant Message + /// Text message being sent + public void InstantMessage(LLUUID target, string message) + { + InstantMessage(FirstName + " " + LastName, target, message, LLUUID.Random(), + InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, + LLUUID.Zero, new byte[0]); + } + + /// + /// Send an Instant Message + /// + /// Target of the Instant Message + /// Text message being sent + /// IM session ID (to differentiate between IM windows) + public void InstantMessage(LLUUID target, string message, LLUUID imSessionID) + { + InstantMessage(FirstName + " " + LastName, target, message, imSessionID, + InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, this.Position, + LLUUID.Zero, new byte[0]); + } + + /// + /// Send an Instant Message + /// + /// The name this IM will show up as being from + /// Key of Avatar + /// Text message being sent + /// IM session ID (to differentiate between IM windows) + /// + public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID, + LLUUID[] conferenceIDs) + { + byte[] binaryBucket; + + if (conferenceIDs != null && conferenceIDs.Length > 0) + { + binaryBucket = new byte[16 * conferenceIDs.Length]; + for (int i = 0; i < conferenceIDs.Length; ++i) + Array.Copy(conferenceIDs[i].Data, 0, binaryBucket, i * 16, 16); + } + else + { + binaryBucket = new byte[0]; + } + + InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent, + InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, binaryBucket); + } + + /// + /// Send an Instant Message + /// + /// The name this IM will show up as being from + /// Key of Avatar + /// Text message being sent + /// IM session ID (to differentiate between IM windows) + /// Type of instant message to send + /// Whether to IM offline avatars as well + /// + /// + /// Packed binary data that is specific to + /// the dialog type + public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID, + InstantMessageDialog dialog, InstantMessageOnline offline, LLVector3 position, LLUUID regionID, + byte[] binaryBucket) + { + ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); + + im.AgentData.AgentID = Client.Network.AgentID; + im.AgentData.SessionID = Client.Network.SessionID; + + im.MessageBlock.Dialog = (byte)dialog; + im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); + im.MessageBlock.FromGroup = false; + im.MessageBlock.ID = imSessionID; + im.MessageBlock.Message = Helpers.StringToField(message); + im.MessageBlock.Offline = (byte)offline; + im.MessageBlock.ToAgentID = target; + + if (binaryBucket != null) + im.MessageBlock.BinaryBucket = binaryBucket; + else + im.MessageBlock.BinaryBucket = new byte[0]; + + // These fields are mandatory, even if we don't have valid values for them + im.MessageBlock.Position = LLVector3.Zero; + //TODO: Allow region id to be correctly set by caller or fetched from Client.* + im.MessageBlock.RegionID = regionID; + + // Send the message + Client.Network.SendPacket(im); + } + + /// + /// Send an Instant Message to a group + /// + /// Key of Group + /// Text Message being sent. + public void InstantMessageGroup(LLUUID groupUUID, string message) + { + InstantMessageGroup(FirstName + " " + LastName, groupUUID, message); + } + + /// + /// Send an Instant Message to a group + /// + /// The name this IM will show up as being from + /// Key of the group + /// Text message being sent + /// This does not appear to function with groups the agent is not in + public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message) + { + ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket(); + + im.AgentData.AgentID = Client.Network.AgentID; + im.AgentData.SessionID = Client.Network.SessionID; + im.MessageBlock.Dialog = (byte)MainAvatar.InstantMessageDialog.SessionSend; + im.MessageBlock.FromAgentName = Helpers.StringToField(fromName); + im.MessageBlock.FromGroup = false; + im.MessageBlock.Message = Helpers.StringToField(message); + im.MessageBlock.Offline = 0; + im.MessageBlock.ID = groupUUID; + im.MessageBlock.ToAgentID = groupUUID; + im.MessageBlock.BinaryBucket = new byte[0]; + im.MessageBlock.Position = LLVector3.Zero; + im.MessageBlock.RegionID = LLUUID.Zero; + + // Send the message + Client.Network.SendPacket(im); + } + + /// + /// + /// + /// + /// + /// + /// + public void PointAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, PointAtType type) + { + ViewerEffectPacket effect = new ViewerEffectPacket(); + + effect.AgentData.AgentID = Client.Network.AgentID; + effect.AgentData.SessionID = Client.Network.SessionID; + + effect.Effect = new ViewerEffectPacket.EffectBlock[1]; + effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); + effect.Effect[0].AgentID = Client.Network.AgentID; + effect.Effect[0].Color = LLColor.Black.GetBytes(); + effect.Effect[0].Duration = (type == PointAtType.Clear) ? 0.0f : Single.MaxValue / 4.0f; + effect.Effect[0].ID = LLUUID.Random(); + effect.Effect[0].Type = (byte)EffectType.PointAt; + + byte[] typeData = new byte[57]; + if (sourceAvatar != null) + Array.Copy(sourceAvatar.GetBytes(), typeData, 16); + if (targetObject != null) + Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); + Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24); + typeData[56] = (byte)type; + + effect.Effect[0].TypeData = typeData; + + Client.Network.SendPacket(effect); + } + + /// + /// + /// + /// + /// + /// + /// + public void LookAtEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LookAtTarget type) + { + ViewerEffectPacket effect = new ViewerEffectPacket(); + + effect.AgentData.AgentID = Client.Network.AgentID; + effect.AgentData.SessionID = Client.Network.SessionID; + + float duration; + + switch (type) + { + case LookAtTarget.Clear: + duration = 0.0f; + break; + case LookAtTarget.Hover: + duration = 1.0f; + break; + case LookAtTarget.FreeLook: + duration = 2.0f; + break; + case LookAtTarget.Idle: + duration = 3.0f; + break; + case LookAtTarget.AutoListen: + case LookAtTarget.Respond: + duration = 4.0f; + break; + case LookAtTarget.None: + case LookAtTarget.Conversation: + case LookAtTarget.Select: + case LookAtTarget.Focus: + case LookAtTarget.Mouselook: + duration = Single.MaxValue / 2.0f; + break; + default: + duration = 0.0f; + break; + } + + effect.Effect = new ViewerEffectPacket.EffectBlock[1]; + effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); + effect.Effect[0].AgentID = Client.Network.AgentID; + effect.Effect[0].Color = LLColor.Black.GetBytes(); + effect.Effect[0].Duration = duration; + effect.Effect[0].ID = LLUUID.Random(); + effect.Effect[0].Type = (byte)EffectType.LookAt; + + byte[] typeData = new byte[57]; + if (sourceAvatar != null) + Array.Copy(sourceAvatar.GetBytes(), typeData, 16); + if (targetObject != null) + Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); + typeData[56] = (byte)type; + + effect.Effect[0].TypeData = typeData; + + Client.Network.SendPacket(effect); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public void BeamEffect(LLUUID sourceAvatar, LLUUID targetObject, LLVector3d globalOffset, LLColor color, + float duration) + { + ViewerEffectPacket effect = new ViewerEffectPacket(); + + effect.AgentData.AgentID = Client.Network.AgentID; + effect.AgentData.SessionID = Client.Network.SessionID; + + effect.Effect = new ViewerEffectPacket.EffectBlock[1]; + effect.Effect[0] = new ViewerEffectPacket.EffectBlock(); + effect.Effect[0].AgentID = Client.Network.AgentID; + effect.Effect[0].Color = color.GetBytes(); + effect.Effect[0].Duration = duration; + effect.Effect[0].ID = LLUUID.Random(); + effect.Effect[0].Type = (byte)EffectType.Beam; + + byte[] typeData = new byte[56]; + if (sourceAvatar != null) + Array.Copy(sourceAvatar.GetBytes(), 0, typeData, 0, 16); + if (targetObject != null) + Array.Copy(targetObject.GetBytes(), 0, typeData, 16, 16); + Array.Copy(globalOffset.GetBytes(), 0, typeData, 32, 24); + + effect.Effect[0].TypeData = typeData; + + Client.Network.SendPacket(effect); + } + + /// + /// Synchronize the local profile and interests information to the server + /// + public void SetAvatarInformation() + { + // Basic profile properties + AvatarPropertiesUpdatePacket apup = new AvatarPropertiesUpdatePacket(); + + apup.AgentData.AgentID = this.ID; + apup.AgentData.SessionID = Client.Network.SessionID; + apup.PropertiesData.AboutText = Helpers.StringToField(this.ProfileProperties.AboutText); + apup.PropertiesData.AllowPublish = this.ProfileProperties.AllowPublish; + apup.PropertiesData.FLAboutText = Helpers.StringToField(this.ProfileProperties.FirstLifeText); + apup.PropertiesData.FLImageID = this.ProfileProperties.FirstLifeImage; + apup.PropertiesData.ImageID = this.ProfileProperties.ProfileImage; + apup.PropertiesData.MaturePublish = this.ProfileProperties.MaturePublish; + apup.PropertiesData.ProfileURL = Helpers.StringToField(this.ProfileProperties.ProfileURL); + + // Interests + AvatarInterestsUpdatePacket aiup = new AvatarInterestsUpdatePacket(); + + aiup.AgentData.AgentID = this.ID; + aiup.AgentData.SessionID = Client.Network.SessionID; + aiup.PropertiesData.LanguagesText = Helpers.StringToField(this.ProfileInterests.LanguagesText); + aiup.PropertiesData.SkillsMask = this.ProfileInterests.SkillsMask; + aiup.PropertiesData.SkillsText = Helpers.StringToField(this.ProfileInterests.SkillsText); + aiup.PropertiesData.WantToMask = this.ProfileInterests.WantToMask; + aiup.PropertiesData.WantToText = Helpers.StringToField(this.ProfileInterests.WantToText); + + //Send packets + Client.Network.SendPacket(apup); + Client.Network.SendPacket(aiup); + } + + /// + /// Send a chat message + /// + /// The Message you're sending out. + /// Channel number (0 would be default 'Say' message, other numbers + /// denote the equivalent of /# in normal client). + /// Chat Type, see above. + public void Chat(string message, int channel, ChatType type) + { + ChatFromViewerPacket chat = new ChatFromViewerPacket(); + chat.AgentData.AgentID = this.ID; + chat.AgentData.SessionID = Client.Network.SessionID; + chat.ChatData.Channel = channel; + chat.ChatData.Message = Helpers.StringToField(message); + chat.ChatData.Type = (byte)type; + + Client.Network.SendPacket(chat); + } + + /// + /// Set the height and the width of the client window. This is used + /// by the server to build a virtual camera frustum for our avatar + /// + /// New height of the viewer window + /// New width of the viewer window + public void SetHeightWidth(ushort height, ushort width) + { + AgentHeightWidthPacket heightwidth = new AgentHeightWidthPacket(); + heightwidth.AgentData.AgentID = Client.Network.AgentID; + heightwidth.AgentData.SessionID = Client.Network.SessionID; + heightwidth.AgentData.CircuitCode = Client.Network.CircuitCode; + heightwidth.HeightWidthBlock.Height = height; + heightwidth.HeightWidthBlock.Width = width; + heightwidth.HeightWidthBlock.GenCounter = HeightWidthGenCounter++; + + Client.Network.SendPacket(heightwidth); + } + + /// + /// Sends a request to sit on the specified object + /// + /// LLUUID of the object to sit on + /// Sit at offset + public void RequestSit(LLUUID targetID, LLVector3 offset) + { + AgentRequestSitPacket requestSit = new AgentRequestSitPacket(); + requestSit.AgentData.AgentID = Client.Network.AgentID; + requestSit.AgentData.SessionID = Client.Network.SessionID; + requestSit.TargetObject.TargetID = targetID; + requestSit.TargetObject.Offset = offset; + Client.Network.SendPacket(requestSit); + } + + /// + /// Request the list of muted things for this avatar + /// + public void RequestMuteList() + { + MuteListRequestPacket mute = new MuteListRequestPacket(); + mute.AgentData.AgentID = Client.Network.AgentID; + mute.AgentData.SessionID = Client.Network.SessionID; + mute.MuteData.MuteCRC = 0; + + Client.Network.SendPacket(mute); + } + + /// + /// Request the current L$ balance + /// + public void RequestBalance() + { + MoneyBalanceRequestPacket money = new MoneyBalanceRequestPacket(); + money.AgentData.AgentID = Client.Network.AgentID; + money.AgentData.SessionID = Client.Network.SessionID; + money.MoneyData.TransactionID = LLUUID.Zero; + + Client.Network.SendPacket(money); + } + + /// + /// Follows a call to RequestSit() to actually sit on the object + /// + public void Sit() + { + AgentSitPacket sit = new AgentSitPacket(); + sit.AgentData.AgentID = Client.Network.AgentID; + sit.AgentData.SessionID = Client.Network.SessionID; + Client.Network.SendPacket(sit); + } + + /// + /// Give Money to destination Avatar + /// + /// UUID of the Target Avatar + /// Amount in L$ + /// Reason (optional normally) + public void GiveMoney(LLUUID target, int amount, string description) + { + // 5001 - transaction type for av to av money transfers + if (amount > 0) + GiveMoney(target, amount, description, 5001); + else + Client.Log("Attempted to pay zero or negative value " + amount, Helpers.LogLevel.Warning); + } + + /// + /// Give Money to destionation Object or Avatar + /// + /// UUID of the Target Object/Avatar + /// Amount in L$ + /// Reason (Optional normally) + /// The type of transaction. Currently only 5001 is + /// documented for Av->Av money transfers. + public void GiveMoney(LLUUID target, int amount, string description, int transactiontype) + { + MoneyTransferRequestPacket money = new MoneyTransferRequestPacket(); + money.AgentData.AgentID = this.ID; + money.AgentData.SessionID = Client.Network.SessionID; + money.MoneyData.Description = Helpers.StringToField(description); + money.MoneyData.DestID = target; + money.MoneyData.SourceID = this.ID; + money.MoneyData.TransactionType = transactiontype; + money.MoneyData.AggregatePermInventory = 0; //TODO: whats this? + money.MoneyData.AggregatePermNextOwner = 0; //TODO: whats this? + money.MoneyData.Flags = 0; //TODO: whats this? + money.MoneyData.Amount = amount; + + Client.Network.SendPacket(money); + } + + /// + /// Send an AgentAnimation packet that toggles a single animation on + /// + /// The animation to start playing + public void AnimationStart(LLUUID animation) + { + Dictionary animations = new Dictionary(); + animations[animation] = true; + + Animate(animations); + } + + /// + /// Send an AgentAnimation packet that toggles a single animation off + /// + /// The animation to stop playing + public void AnimationStop(LLUUID animation) + { + Dictionary animations = new Dictionary(); + animations[animation] = false; + + Animate(animations); + } + + /// + /// Send an AgentAnimation packet that will toggle animations on or off + /// + /// A list of animation UUIDs, and whether to + /// turn that animation on or off + public void Animate(Dictionary animations) + { + AgentAnimationPacket animate = new AgentAnimationPacket(); + + animate.AgentData.AgentID = Client.Network.AgentID; + animate.AgentData.SessionID = Client.Network.SessionID; + animate.AnimationList = new AgentAnimationPacket.AnimationListBlock[animations.Count]; + int i = 0; + + foreach (KeyValuePair animation in animations) + { + animate.AnimationList[i] = new AgentAnimationPacket.AnimationListBlock(); + animate.AnimationList[i].AnimID = animation.Key; + animate.AnimationList[i].StartAnim = animation.Value; + + i++; + } + + Client.Network.SendPacket(animate); + } + + /// + /// Use the autopilot sim function to move the avatar to a new position + /// + /// The z value is currently not handled properly by the simulator + /// Integer value for the global X coordinate to move to + /// Integer value for the global Y coordinate to move to + /// Floating-point value for the Z coordinate to move to + /// AutoPilot(252620, 247078, 20.2674); + public void AutoPilot(ulong globalX, ulong globalY, float z) + { + GenericMessagePacket autopilot = new GenericMessagePacket(); + + autopilot.AgentData.AgentID = Client.Network.AgentID; + autopilot.AgentData.SessionID = Client.Network.SessionID; + autopilot.AgentData.TransactionID = LLUUID.Zero; + autopilot.MethodData.Invoice = LLUUID.Zero; + autopilot.MethodData.Method = Helpers.StringToField("autopilot"); + autopilot.ParamList = new GenericMessagePacket.ParamListBlock[3]; + autopilot.ParamList[0] = new GenericMessagePacket.ParamListBlock(); + autopilot.ParamList[0].Parameter = Helpers.StringToField(globalX.ToString()); + autopilot.ParamList[1] = new GenericMessagePacket.ParamListBlock(); + autopilot.ParamList[1].Parameter = Helpers.StringToField(globalY.ToString()); + autopilot.ParamList[2] = new GenericMessagePacket.ParamListBlock(); + // TODO: Do we need to prevent z coordinates from being sent in 1.4827e-18 notation? + autopilot.ParamList[2].Parameter = Helpers.StringToField(z.ToString()); + + Client.Network.SendPacket(autopilot); + } + + /// + /// Use the autopilot sim function to move the avatar to a new position + /// + /// The z value is currently not handled properly by the simulator + /// Integer value for the local X coordinate to move to + /// Integer value for the local Y coordinate to move to + /// Floating-point value for the Z coordinate to move to + public void AutoPilotLocal(int localX, int localY, float z) + { + uint x, y; + Helpers.LongToUInts(Client.Network.CurrentSim.Handle, out x, out y); + AutoPilot((ulong)(x + localX), (ulong)(y + localY), z); + } + + /// + /// Attempt to look up a simulator name and teleport to the discovered + /// destination + /// + /// Region name to look up + /// Position to teleport to + /// True if the lookup and teleport were successful, otherwise + /// false + public bool Teleport(string simName, LLVector3 position) + { + return Teleport(simName, position, new LLVector3(0, 1.0f, 0)); + } + + /// + /// Attempt to look up a simulator name and teleport to the discovered + /// destination + /// + /// Region name to look up + /// Position to teleport to + /// Target to look at + /// True if the lookup and teleport were successful, otherwise + /// false + public bool Teleport(string simName, LLVector3 position, LLVector3 lookAt) + { + TeleportStat = TeleportStatus.None; + simName = simName.ToLower(); + + if (simName != Client.Network.CurrentSim.Name.ToLower()) + { + // Teleporting to a foreign sim + GridRegion region = Client.Grid.GetGridRegion(simName); + + if (region != null) + { + return Teleport(region.RegionHandle, position, lookAt); + } + else + { + teleportMessage = "Unable to resolve name: " + simName; + TeleportStat = TeleportStatus.Failed; + return false; + } + } + else + { + // Teleporting to the sim we're already in + return Teleport(Client.Network.CurrentSim.Handle, position, lookAt); + } + } + + /// + /// Start a teleport process + /// + /// + /// Position for Teleport + /// + public bool Teleport(ulong regionHandle, LLVector3 position) + { + return Teleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); + } + + /// + /// Start a teleport process + /// + /// + /// Position for Teleport + /// Target to look at + /// + public bool Teleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) + { + TeleportStat = TeleportStatus.None; + TeleportEvent.Reset(); + + RequestTeleport(regionHandle, position, lookAt); + + TeleportEvent.WaitOne(Client.Settings.TELEPORT_TIMEOUT, false); + + if (TeleportStat == TeleportStatus.None || + TeleportStat == TeleportStatus.Start || + TeleportStat == TeleportStatus.Progress) + { + teleportMessage = "Teleport timed out."; + TeleportStat = TeleportStatus.Failed; + } + + return (TeleportStat == TeleportStatus.Finished); + } + + /// + /// Start a teleport process + /// + /// + /// Position for Teleport + public void RequestTeleport(ulong regionHandle, LLVector3 position) + { + RequestTeleport(regionHandle, position, new LLVector3(0.0f, 1.0f, 0.0f)); + } + + /// + /// Start a teleport process + /// + /// + /// Position for Teleport + /// Target to look at + public void RequestTeleport(ulong regionHandle, LLVector3 position, LLVector3 lookAt) + { + TeleportLocationRequestPacket teleport = new TeleportLocationRequestPacket(); + teleport.AgentData.AgentID = Client.Network.AgentID; + teleport.AgentData.SessionID = Client.Network.SessionID; + teleport.Info.LookAt = lookAt; + teleport.Info.Position = position; + teleport.Info.RegionHandle = regionHandle; + + Client.Log("Teleporting to region " + regionHandle.ToString(), Helpers.LogLevel.Info); + + Client.Network.SendPacket(teleport); + } + + /// + /// Respond to a teleport lure by either accepting it and initiating + /// the teleport, or denying it + /// + /// UUID of the avatar requesting the teleport + /// Accept the teleport request or deny it + public void TeleportLureRespond(LLUUID requesterID, bool accept) + { + InstantMessage(FirstName + " " + LastName, requesterID, String.Empty, LLUUID.Random(), + accept ? InstantMessageDialog.AcceptTeleport : InstantMessageDialog.DenyTeleport, + InstantMessageOnline.Offline, this.Position, LLUUID.Zero, new byte[0]); + + if (accept) + { + TeleportLureRequestPacket lure = new TeleportLureRequestPacket(); + + lure.Info.AgentID = Client.Network.AgentID; + lure.Info.SessionID = Client.Network.SessionID; + lure.Info.LureID = Client.Network.AgentID; + lure.Info.TeleportFlags = (uint)TeleportFlags.ViaLure; + + Client.Network.SendPacket(lure); + } + } + + /// + /// Grabs an object + /// + /// Local ID of Object to grab + public void Grab(uint objectLocalID) + { + ObjectGrabPacket grab = new ObjectGrabPacket(); + grab.AgentData.AgentID = Client.Network.AgentID; + grab.AgentData.SessionID = Client.Network.SessionID; + grab.ObjectData.LocalID = objectLocalID; + grab.ObjectData.GrabOffset = new LLVector3(0, 0, 0); + Client.Network.SendPacket(grab); + } + + /// + /// Drags on an object + /// + /// Strangely, LLUID instead of local ID + /// Drag target in region coordinates + public void GrabUpdate(LLUUID objectID, LLVector3 grabPosition) + { + ObjectGrabUpdatePacket grab = new ObjectGrabUpdatePacket(); + grab.AgentData.AgentID = Client.Network.AgentID; + grab.AgentData.SessionID = Client.Network.SessionID; + grab.ObjectData.ObjectID = objectID; + grab.ObjectData.GrabOffsetInitial = new LLVector3(0, 0, 0); + grab.ObjectData.GrabPosition = grabPosition; + grab.ObjectData.TimeSinceLast = 0; + Client.Network.SendPacket(grab); + } + + /// + /// Releases a grabbed object + /// + public void DeGrab(uint objectLocalID) + { + ObjectDeGrabPacket degrab = new ObjectDeGrabPacket(); + degrab.AgentData.AgentID = Client.Network.AgentID; + degrab.AgentData.SessionID = Client.Network.SessionID; + degrab.ObjectData.LocalID = objectLocalID; + Client.Network.SendPacket(degrab); + } + + /// + /// Touches an object + /// + public void Touch(uint objectLocalID) + { + Client.Self.Grab(objectLocalID); + Client.Self.DeGrab(objectLocalID); + } + + /// + /// Request to join a group. If there is an enrollment fee it will + /// automatically be deducted from your balance + /// + /// The group to attempt to join + public void RequestJoinGroup(LLUUID groupID) + { + JoinGroupRequestPacket join = new JoinGroupRequestPacket(); + + join.AgentData.AgentID = Client.Network.AgentID; + join.AgentData.SessionID = Client.Network.SessionID; + join.GroupData.GroupID = groupID; + + Client.Network.SendPacket(join); + } + + /// + /// Request to leave a group + /// + /// The group to attempt to leave + public void RequestLeaveGroup(LLUUID groupID) + { + LeaveGroupRequestPacket leave = new LeaveGroupRequestPacket(); + + leave.AgentData.AgentID = Client.Network.AgentID; + leave.AgentData.SessionID = Client.Network.SessionID; + leave.GroupData.GroupID = groupID; + + Client.Network.SendPacket(leave); + } + + /// + /// Set our current active group + /// + /// The group we are a member of that we want to + /// activate + public void ActivateGroup(LLUUID groupID) + { + ActivateGroupPacket activate = new ActivateGroupPacket(); + + activate.AgentData.AgentID = Client.Network.AgentID; + activate.AgentData.SessionID = Client.Network.SessionID; + activate.AgentData.GroupID = groupID; + + Client.Network.SendPacket(activate); + } + + /// + /// Move an agent in to a simulator. This packet is the last packet + /// needed to complete the transition in to a new simulator + /// + /// + public void CompleteAgentMovement(Simulator simulator) + { + CompleteAgentMovementPacket move = new CompleteAgentMovementPacket(); + + move.AgentData.AgentID = Client.Network.AgentID; + move.AgentData.SessionID = Client.Network.SessionID; + move.AgentData.CircuitCode = Client.Network.CircuitCode; + + Client.Network.SendPacket(move, simulator); + } + + /// + /// Sends camera and action updates to the server including the + /// position and orientation of our camera, and a ControlFlags field + /// specifying our current movement actions + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void UpdateCamera(MainAvatar.AgentUpdateFlags controlFlags, LLVector3 position, LLVector3 forwardAxis, + LLVector3 leftAxis, LLVector3 upAxis, LLQuaternion bodyRotation, LLQuaternion headRotation, float farClip, + bool reliable) + { + AgentUpdatePacket update = new AgentUpdatePacket(); + + update.AgentData.AgentID = Client.Network.AgentID; + update.AgentData.SessionID = Client.Network.SessionID; + update.AgentData.State = 0; + update.AgentData.BodyRotation = bodyRotation; + update.AgentData.HeadRotation = headRotation; + update.AgentData.CameraCenter = position; + update.AgentData.CameraAtAxis = forwardAxis; + update.AgentData.CameraLeftAxis = leftAxis; + update.AgentData.CameraUpAxis = upAxis; + update.AgentData.Far = farClip; + update.AgentData.ControlFlags = (uint)controlFlags; + update.AgentData.Flags = 0; + update.Header.Reliable = reliable; + + Client.Network.SendPacket(update); + } + + /// + /// Take an incoming ImprovedInstantMessage packet, auto-parse, and if + /// OnInstantMessage is defined call that with the appropriate arguments + /// + /// Incoming ImprovedInstantMessagePacket + /// Unused + private void InstantMessageHandler(Packet packet, Simulator simulator) + { + if (packet.Type == PacketType.ImprovedInstantMessage) + { + ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)packet; + + if (OnInstantMessage != null) + { + OnInstantMessage( + im.AgentData.AgentID + , Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName), + im.MessageBlock.ToAgentID + , im.MessageBlock.ParentEstateID + , im.MessageBlock.RegionID + , im.MessageBlock.Position + , (InstantMessageDialog)im.MessageBlock.Dialog + , im.MessageBlock.FromGroup + , im.MessageBlock.ID + , new DateTime(im.MessageBlock.Timestamp) + , Helpers.FieldToUTF8String(im.MessageBlock.Message) + , (InstantMessageOnline)im.MessageBlock.Offline + , im.MessageBlock.BinaryBucket + ); + } + } + } + + /// + /// Take an incoming Chat packet, auto-parse, and if OnChat is defined call + /// that with the appropriate arguments. + /// + /// Incoming ChatFromSimulatorPacket + /// Unused + private void ChatHandler(Packet packet, Simulator simulator) + { + if (OnChat != null) + { + ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)packet; + + OnChat(Helpers.FieldToUTF8String(chat.ChatData.Message) + , (ChatAudibleLevel)chat.ChatData.Audible + , (ChatType)chat.ChatData.ChatType + , (ChatSourceType)chat.ChatData.SourceType + , Helpers.FieldToUTF8String(chat.ChatData.FromName) + , chat.ChatData.SourceID + , chat.ChatData.OwnerID + , chat.ChatData.Position + ); + } + } + + /// + /// Used for parsing llDialog's + /// + /// Incoming ScriptDialog packet + /// Unused + private void ScriptDialogHandler(Packet packet, Simulator simulator) + { + if (OnScriptDialog != null) + { + ScriptDialogPacket dialog = (ScriptDialogPacket)packet; + List buttons = new List(); + + foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons) + { + buttons.Add(Helpers.FieldToString(button.ButtonLabel)); + } + + OnScriptDialog(Helpers.FieldToUTF8String(dialog.Data.Message), + Helpers.FieldToString(dialog.Data.ObjectName), + dialog.Data.ImageID, + dialog.Data.ObjectID, + Helpers.FieldToUTF8String(dialog.Data.FirstName), + Helpers.FieldToUTF8String(dialog.Data.LastName), + dialog.Data.ChatChannel, + buttons); + } + } + + /// + /// Update client's Position, LookAt and region handle from incoming packet + /// + /// Incoming AgentMovementCompletePacket + /// Unused + private void MovementCompleteHandler(Packet packet, Simulator simulator) + { + AgentMovementCompletePacket movement = (AgentMovementCompletePacket)packet; + + this.Position = movement.Data.Position; + this.LookAt = movement.Data.LookAt; + simulator.Handle = movement.Data.RegionHandle; + } + + /// + /// Update Client Avatar's health via incoming packet + /// + /// Incoming HealthMessagePacket + /// Unused + private void HealthHandler(Packet packet, Simulator simulator) + { + health = ((HealthMessagePacket)packet).HealthData.Health; + } + + private void JoinGroupHandler(Packet packet, Simulator simulator) + { + if (OnJoinGroup != null) + { + JoinGroupReplyPacket reply = (JoinGroupReplyPacket)packet; + + OnJoinGroup(reply.GroupData.GroupID, reply.GroupData.Success); + } + } + + private void LeaveGroupHandler(Packet packet, Simulator simulator) + { + if (OnLeaveGroup != null) + { + LeaveGroupReplyPacket reply = (LeaveGroupReplyPacket)packet; + + OnLeaveGroup(reply.GroupData.GroupID, reply.GroupData.Success); + } + } + + public void AgentDataUpdateHandler(Packet packet, Simulator simulator) + { + AgentDataUpdatePacket p = (AgentDataUpdatePacket)packet; + if (p.AgentData.AgentID == simulator.Client.Network.AgentID) { + activeGroup = p.AgentData.ActiveGroupID; + } + } + + private void DropGroupHandler(Packet packet, Simulator simulator) + { + if (OnGroupDropped != null) + { + OnGroupDropped(((AgentDropGroupPacket)packet).AgentData.GroupID); + } + } + + /// + /// Update Client Avatar's L$ balance from incoming packet + /// + /// Incoming MoneyBalanceReplyPacket + /// Unused + private void BalanceHandler(Packet packet, Simulator simulator) + { + if (packet.Type == PacketType.MoneySummaryReply) + { + balance = ((MoneySummaryReplyPacket)packet).MoneyData.Balance; + } + else if (packet.Type == PacketType.AdjustBalance) + { + balance += ((AdjustBalancePacket)packet).AgentData.Delta; + } + + if (OnBalanceUpdated != null) + { + OnBalanceUpdated(balance); + } + } + + /// + /// Update Client Avatar's L$ balance from incoming packet, and + /// trigger a complete callback with all available fields. + /// + /// + /// + private void MoneyBalanceReplyHandler(Packet packet, Simulator simulator) + { + MoneyBalanceReplyPacket mbrp = (MoneyBalanceReplyPacket)packet; + balance = mbrp.MoneyData.MoneyBalance; + + if (OnMoneyBalanceReplyReceived != null) + { + OnMoneyBalanceReplyReceived(mbrp.MoneyData.TransactionID, mbrp.MoneyData.TransactionSuccess, mbrp.MoneyData.MoneyBalance, mbrp.MoneyData.SquareMetersCredit, mbrp.MoneyData.SquareMetersCommitted, Helpers.FieldToString(mbrp.MoneyData.Description)); + } + + if (OnBalanceUpdated != null) + { + OnBalanceUpdated(balance); + } + + } + + private void EventQueueHandler(string message, object body) + { + if (message == "TeleportFinish") + { + Hashtable tpt = (Hashtable)body; + Hashtable info = (Hashtable)tpt["Info"]; + + // Backwards compatibility hack + TeleportFinishPacket packet = new TeleportFinishPacket(); + + packet.Info.SimIP = Helpers.BytesToUIntBig((byte[])info["SimIP"]); + packet.Info.LocationID = Helpers.BytesToUInt((byte[])info["LocationID"]); + packet.Info.TeleportFlags = Helpers.BytesToUInt((byte[])info["TeleportFlags"]); + packet.Info.AgentID = (LLUUID)info["AgentID"]; + packet.Info.RegionHandle = Helpers.BytesToUInt64((byte[])info["RegionHandle"]); + packet.Info.SeedCapability = Helpers.StringToField((string)info["SeedCapability"]); + packet.Info.SimPort = (ushort)(long)info["SimPort"]; + packet.Info.SimAccess = (byte)(long)info["SimAccess"]; + + Client.DebugLog("Received a TeleportFinish event, SimIP: " + new IPAddress(packet.Info.SimIP) + + ", LocationID: " + packet.Info.LocationID + ", RegionHandle: " + packet.Info.RegionHandle); + + TeleportHandler(packet, Client.Network.CurrentSim); + } + else + { + Client.Log("Received unhandled event " + message + " in the EventQueueHandler", + Helpers.LogLevel.Warning); + } + } + + /// + /// Handler for teleport Requests + /// + /// Incoming TeleportHandler packet + /// Simulator sending teleport information + private void TeleportHandler(Packet packet, Simulator simulator) + { + bool finished = false; + TeleportFlags flags = TeleportFlags.Default; + + if (packet.Type == PacketType.TeleportStart) + { + TeleportStartPacket start = (TeleportStartPacket)packet; + + teleportMessage = "Teleport started"; + flags = (TeleportFlags)start.Info.TeleportFlags; + TeleportStat = TeleportStatus.Start; + + Client.DebugLog("TeleportStart received from " + simulator.ToString() + ", Flags: " + flags.ToString()); + } + else if (packet.Type == PacketType.TeleportProgress) + { + TeleportProgressPacket progress = (TeleportProgressPacket)packet; + + teleportMessage = Helpers.FieldToUTF8String(progress.Info.Message); + flags = (TeleportFlags)progress.Info.TeleportFlags; + TeleportStat = TeleportStatus.Progress; + + Client.DebugLog("TeleportProgress received from " + simulator.ToString() + ", Flags: " + flags.ToString()); + } + else if (packet.Type == PacketType.TeleportFailed) + { + TeleportFailedPacket failed = (TeleportFailedPacket)packet; + + teleportMessage = Helpers.FieldToUTF8String(failed.Info.Reason); + TeleportStat = TeleportStatus.Failed; + finished = true; + + Client.DebugLog("TeleportFailed received from " + simulator.ToString() + ", Reason: " + teleportMessage); + } + else if (packet.Type == PacketType.TeleportFinish) + { + TeleportFinishPacket finish = (TeleportFinishPacket)packet; + + Simulator previousSim = Client.Network.CurrentSim; + flags = (TeleportFlags)finish.Info.TeleportFlags; + string seedcaps = Helpers.FieldToUTF8String(finish.Info.SeedCapability); + IPAddress simIP = new IPAddress(finish.Info.SimIP); + finished = true; + + Client.DebugLog("TeleportFinish received from " + simulator.ToString() + ", Flags: " + flags.ToString()); + + // Disable CAPS on the current sim since we are moving + if (Client.Network.CurrentCaps != null) Client.Network.CurrentCaps.Dead = true; + + // Connect to the new sim + Simulator sim = Client.Network.Connect(simIP, finish.Info.SimPort, true, seedcaps); + + if (sim != null) + { + teleportMessage = "Teleport finished"; + TeleportStat = TeleportStatus.Finished; + + // Disconnect from the previous sim + Client.Network.DisconnectSim(previousSim); + + Client.Log("Moved to new sim " + sim.ToString(), Helpers.LogLevel.Info); + } + else + { + teleportMessage = "Failed to connect to the new sim after a teleport"; + TeleportStat = TeleportStatus.Failed; + + // Attempt to reconnect to the previous simulator + // TODO: This hasn't been tested at all + Client.Network.Connect(previousSim.IPEndPoint.Address, (ushort)previousSim.IPEndPoint.Port, + true, Client.Network.CurrentCaps.Seedcaps); + + Client.Log(teleportMessage, Helpers.LogLevel.Warning); + } + } + else if (packet.Type == PacketType.TeleportCancel) + { + //TeleportCancelPacket cancel = (TeleportCancelPacket)packet; + + teleportMessage = "Cancelled."; + TeleportStat = TeleportStatus.Cancelled; + finished = true; + + Client.DebugLog("TeleportCancel received from " + simulator.ToString()); + } + else if (packet.Type == PacketType.TeleportLocal) + { + TeleportLocalPacket local = (TeleportLocalPacket)packet; + + teleportMessage = "Teleport finished"; + flags = (TeleportFlags)local.Info.TeleportFlags; + TeleportStat = TeleportStatus.Finished; + LookAt = local.Info.LookAt; + Position = local.Info.Position; + // This field is apparently not used for anything + //local.Info.LocationID; + finished = true; + + Client.DebugLog("TeleportLocal received from " + simulator.ToString() + ", Flags: " + flags.ToString()); + } + + if (OnTeleport != null) + { + try { OnTeleport(teleportMessage, TeleportStat, flags); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + + if (finished) TeleportEvent.Set(); + } + } +} diff --git a/libsecondlife-cs/MainAvatarStatus.cs b/libsecondlife-cs/MainAvatarStatus.cs index e26d3d32..d4f0d8a5 100644 --- a/libsecondlife-cs/MainAvatarStatus.cs +++ b/libsecondlife-cs/MainAvatarStatus.cs @@ -1,391 +1,391 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Timers; -using libsecondlife; -using libsecondlife.Packets; - -namespace libsecondlife -{ - public partial class MainAvatar - { - /// - /// Holds current camera and control key status - /// - public class MainAvatarStatus - { - /// - /// Contains properties for client control key states - /// - public struct ControlStatus - { - /// - public bool AtPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_POS, value); } - } - /// - public bool AtNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_NEG, value); } - } - /// - public bool LeftPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_POS, value); } - } - /// - public bool LeftNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_NEG, value); } - } - /// - public bool UpPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_POS, value); } - } - /// - public bool UpNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_NEG, value); } - } - /// - public bool PitchPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_POS, value); } - } - /// - public bool PitchNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_NEG, value); } - } - /// - public bool YawPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_POS, value); } - } - /// - public bool YawNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_NEG, value); } - } - /// - public bool FastAt - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_AT); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_AT, value); } - } - /// - public bool FastLeft - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_LEFT); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_LEFT, value); } - } - /// - public bool FastUp - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_UP); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_UP, value); } - } - /// - public bool Fly - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FLY); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FLY, value); } - } - /// - public bool Stop - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STOP); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STOP, value); } - } - /// - public bool FinishAnim - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FINISH_ANIM); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FINISH_ANIM, value); } - } - /// - public bool StandUp - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STAND_UP); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STAND_UP, value); } - } - /// - public bool SitOnGround - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_SIT_ON_GROUND); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_SIT_ON_GROUND, value); } - } - /// - public bool Mouselook - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_MOUSELOOK); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_MOUSELOOK, value); } - } - /// - public bool NudgeAtPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_POS, value); } - } - /// - public bool NudgeAtNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_NEG, value); } - } - /// - public bool NudgeLeftPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_POS, value); } - } - /// - public bool NudgeLeftNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_NEG, value); } - } - /// - public bool NudgeUpPos - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_POS); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_POS, value); } - } - /// - public bool NudgeUpNeg - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_NEG); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_NEG, value); } - } - /// - public bool TurnLeft - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_LEFT); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_LEFT, value); } - } - /// - public bool TurnRight - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_RIGHT); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_RIGHT, value); } - } - /// - public bool Away - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AWAY); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AWAY, value); } - } - /// - public bool LButtonDown - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_DOWN); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_DOWN, value); } - } - /// - public bool LButtonUp - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_UP); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_UP, value); } - } - /// - public bool MLButtonDown - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_DOWN); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_DOWN, value); } - } - /// - public bool MLButtonUp - { - get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_UP); } - set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_UP, value); } - } - } - - /// - /// - /// - public struct CameraStatus - { - /// - public LLQuaternion BodyRotation; - /// - public LLQuaternion HeadRotation; - /// - public LLVector3 CameraAtAxis; - /// - public LLVector3 CameraCenter; - /// - public LLVector3 CameraLeftAxis; - /// - public LLVector3 CameraUpAxis; - /// - public float Far; - } - - - /// - /// Timer for sending AgentUpdate packets, disabled by default - /// - public Timer UpdateTimer; - /// - /// Holds control flags - /// - public ControlStatus Controls; - /// - /// Holds camera flags - /// - public CameraStatus Camera; - - /// - /// Returns "always run" value, or changes it by sending a SetAlwaysRunPacket - /// - public bool AlwaysRun - { - get - { - return alwaysRun; - } - set - { - alwaysRun = value; - SetAlwaysRunPacket run = new SetAlwaysRunPacket(); - run.AgentData.AgentID = Client.Network.AgentID; - run.AgentData.SessionID = Client.Network.SessionID; - run.AgentData.AlwaysRun = alwaysRun; - Client.Network.SendPacket(run); - } - } - /// The current value of the agent control flags - public uint AgentControls { get { return agentControls; } } - - - private bool alwaysRun = false; - private SecondLife Client; - private static uint agentControls; - - - /// Constructor for class MainAvatarStatus - public MainAvatarStatus(SecondLife client) - { - Client = client; - - // Lets set some default camera values - Camera.BodyRotation = LLQuaternion.Identity; - Camera.HeadRotation = LLQuaternion.Identity; - Camera.CameraCenter = new LLVector3(128,128,20); - Camera.CameraAtAxis = new LLVector3(0, 0.9999f, 0); - Camera.CameraLeftAxis = new LLVector3(0.9999f, 0, 0); - Camera.CameraUpAxis = new LLVector3(0, 0, 0.9999f); - Camera.Far = 128.0f; - - UpdateTimer = new Timer(Client.Settings.AGENT_UPDATE_INTERVAL); - UpdateTimer.Elapsed += new ElapsedEventHandler(UpdateTimer_Elapsed); - UpdateTimer.Enabled = Client.Settings.SEND_AGENT_UPDATES; - } - - /// - /// Send new AgentUpdate packet to update our current camera - /// position and rotation - /// - public void SendUpdate() - { - SendUpdate(false, Client.Network.CurrentSim); - } - - /// - /// Send new AgentUpdate packet to update our current camera - /// position and rotation - /// - /// Whether to require server acknowledgement - /// of this packet - public void SendUpdate(bool reliable) - { - SendUpdate(reliable, Client.Network.CurrentSim); - } - - /// - /// Send new AgentUpdate packet to update our current camera - /// position and rotation - /// - /// Whether to require server acknowledgement - /// of this packet - /// Simulator to send the update to - public void SendUpdate(bool reliable, Simulator simulator) - { - AgentUpdatePacket update = new AgentUpdatePacket(); - update.Header.Reliable = reliable; - - update.AgentData.AgentID = Client.Network.AgentID; - update.AgentData.SessionID = Client.Network.SessionID; - update.AgentData.HeadRotation = Camera.HeadRotation; - update.AgentData.BodyRotation = Camera.BodyRotation; - update.AgentData.CameraAtAxis = Camera.CameraAtAxis; - update.AgentData.CameraCenter = Camera.CameraCenter; - update.AgentData.CameraLeftAxis = Camera.CameraLeftAxis; - update.AgentData.CameraUpAxis = Camera.CameraUpAxis; - update.AgentData.ControlFlags = agentControls; - update.AgentData.Far = Camera.Far; - - Client.Network.SendPacket(update, simulator); - } - - private static bool GetControlFlag(MainAvatar.AgentUpdateFlags flag) - { - uint control = (uint)flag; - return ((agentControls & control) == control); - } - - private static void SetControlFlag(MainAvatar.AgentUpdateFlags flag, bool value) - { - uint control = (uint)flag; - if (value && ((agentControls & control) != control)) agentControls ^= control; - else if (!value && ((agentControls & control) == control)) agentControls ^= control; - } - - private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) - { - if (Client.Network.Connected) - { - //Send an AgentUpdate packet - SendUpdate(); - } - } - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Timers; +using libsecondlife; +using libsecondlife.Packets; + +namespace libsecondlife +{ + public partial class MainAvatar + { + /// + /// Holds current camera and control key status + /// + public class MainAvatarStatus + { + /// + /// Contains properties for client control key states + /// + public struct ControlStatus + { + /// + public bool AtPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_POS, value); } + } + /// + public bool AtNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AT_NEG, value); } + } + /// + public bool LeftPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_POS, value); } + } + /// + public bool LeftNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LEFT_NEG, value); } + } + /// + public bool UpPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_POS, value); } + } + /// + public bool UpNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_UP_NEG, value); } + } + /// + public bool PitchPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_POS, value); } + } + /// + public bool PitchNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_PITCH_NEG, value); } + } + /// + public bool YawPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_POS, value); } + } + /// + public bool YawNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_YAW_NEG, value); } + } + /// + public bool FastAt + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_AT); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_AT, value); } + } + /// + public bool FastLeft + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_LEFT); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_LEFT, value); } + } + /// + public bool FastUp + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_UP); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FAST_UP, value); } + } + /// + public bool Fly + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FLY); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FLY, value); } + } + /// + public bool Stop + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STOP); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STOP, value); } + } + /// + public bool FinishAnim + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FINISH_ANIM); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_FINISH_ANIM, value); } + } + /// + public bool StandUp + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STAND_UP); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_STAND_UP, value); } + } + /// + public bool SitOnGround + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_SIT_ON_GROUND); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_SIT_ON_GROUND, value); } + } + /// + public bool Mouselook + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_MOUSELOOK); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_MOUSELOOK, value); } + } + /// + public bool NudgeAtPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_POS, value); } + } + /// + public bool NudgeAtNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_AT_NEG, value); } + } + /// + public bool NudgeLeftPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_POS, value); } + } + /// + public bool NudgeLeftNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_LEFT_NEG, value); } + } + /// + public bool NudgeUpPos + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_POS); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_POS, value); } + } + /// + public bool NudgeUpNeg + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_NEG); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_NUDGE_UP_NEG, value); } + } + /// + public bool TurnLeft + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_LEFT); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_LEFT, value); } + } + /// + public bool TurnRight + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_RIGHT); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_TURN_RIGHT, value); } + } + /// + public bool Away + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AWAY); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_AWAY, value); } + } + /// + public bool LButtonDown + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_DOWN); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_DOWN, value); } + } + /// + public bool LButtonUp + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_UP); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_LBUTTON_UP, value); } + } + /// + public bool MLButtonDown + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_DOWN); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_DOWN, value); } + } + /// + public bool MLButtonUp + { + get { return GetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_UP); } + set { SetControlFlag(MainAvatar.AgentUpdateFlags.AGENT_CONTROL_ML_LBUTTON_UP, value); } + } + } + + /// + /// + /// + public struct CameraStatus + { + /// + public LLQuaternion BodyRotation; + /// + public LLQuaternion HeadRotation; + /// + public LLVector3 CameraAtAxis; + /// + public LLVector3 CameraCenter; + /// + public LLVector3 CameraLeftAxis; + /// + public LLVector3 CameraUpAxis; + /// + public float Far; + } + + + /// + /// Timer for sending AgentUpdate packets, disabled by default + /// + public Timer UpdateTimer; + /// + /// Holds control flags + /// + public ControlStatus Controls; + /// + /// Holds camera flags + /// + public CameraStatus Camera; + + /// + /// Returns "always run" value, or changes it by sending a SetAlwaysRunPacket + /// + public bool AlwaysRun + { + get + { + return alwaysRun; + } + set + { + alwaysRun = value; + SetAlwaysRunPacket run = new SetAlwaysRunPacket(); + run.AgentData.AgentID = Client.Network.AgentID; + run.AgentData.SessionID = Client.Network.SessionID; + run.AgentData.AlwaysRun = alwaysRun; + Client.Network.SendPacket(run); + } + } + /// The current value of the agent control flags + public uint AgentControls { get { return agentControls; } } + + + private bool alwaysRun = false; + private SecondLife Client; + private static uint agentControls; + + + /// Constructor for class MainAvatarStatus + public MainAvatarStatus(SecondLife client) + { + Client = client; + + // Lets set some default camera values + Camera.BodyRotation = LLQuaternion.Identity; + Camera.HeadRotation = LLQuaternion.Identity; + Camera.CameraCenter = new LLVector3(128,128,20); + Camera.CameraAtAxis = new LLVector3(0, 0.9999f, 0); + Camera.CameraLeftAxis = new LLVector3(0.9999f, 0, 0); + Camera.CameraUpAxis = new LLVector3(0, 0, 0.9999f); + Camera.Far = 128.0f; + + UpdateTimer = new Timer(Client.Settings.AGENT_UPDATE_INTERVAL); + UpdateTimer.Elapsed += new ElapsedEventHandler(UpdateTimer_Elapsed); + UpdateTimer.Enabled = Client.Settings.SEND_AGENT_UPDATES; + } + + /// + /// Send new AgentUpdate packet to update our current camera + /// position and rotation + /// + public void SendUpdate() + { + SendUpdate(false, Client.Network.CurrentSim); + } + + /// + /// Send new AgentUpdate packet to update our current camera + /// position and rotation + /// + /// Whether to require server acknowledgement + /// of this packet + public void SendUpdate(bool reliable) + { + SendUpdate(reliable, Client.Network.CurrentSim); + } + + /// + /// Send new AgentUpdate packet to update our current camera + /// position and rotation + /// + /// Whether to require server acknowledgement + /// of this packet + /// Simulator to send the update to + public void SendUpdate(bool reliable, Simulator simulator) + { + AgentUpdatePacket update = new AgentUpdatePacket(); + update.Header.Reliable = reliable; + + update.AgentData.AgentID = Client.Network.AgentID; + update.AgentData.SessionID = Client.Network.SessionID; + update.AgentData.HeadRotation = Camera.HeadRotation; + update.AgentData.BodyRotation = Camera.BodyRotation; + update.AgentData.CameraAtAxis = Camera.CameraAtAxis; + update.AgentData.CameraCenter = Camera.CameraCenter; + update.AgentData.CameraLeftAxis = Camera.CameraLeftAxis; + update.AgentData.CameraUpAxis = Camera.CameraUpAxis; + update.AgentData.ControlFlags = agentControls; + update.AgentData.Far = Camera.Far; + + Client.Network.SendPacket(update, simulator); + } + + private static bool GetControlFlag(MainAvatar.AgentUpdateFlags flag) + { + uint control = (uint)flag; + return ((agentControls & control) == control); + } + + private static void SetControlFlag(MainAvatar.AgentUpdateFlags flag, bool value) + { + uint control = (uint)flag; + if (value && ((agentControls & control) != control)) agentControls ^= control; + else if (!value && ((agentControls & control) == control)) agentControls ^= control; + } + + private void UpdateTimer_Elapsed(object sender, ElapsedEventArgs e) + { + if (Client.Network.Connected) + { + //Send an AgentUpdate packet + SendUpdate(); + } + } + } + } +} diff --git a/libsecondlife-cs/NameValue.cs b/libsecondlife-cs/NameValue.cs index 983744fd..17ca5851 100644 --- a/libsecondlife-cs/NameValue.cs +++ b/libsecondlife-cs/NameValue.cs @@ -1,310 +1,310 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; - -namespace libsecondlife -{ - /// - /// A Name Value pair with additional settings - /// - [Serializable] - public class NameValue - { - /// Type of the value - public enum ValueType - { - /// Unknown - Unknown = -1, - /// String value - String, - /// - F32, - /// - S32, - /// - VEC3, - /// - U32, - /// Deprecated - [Obsolete] - CAMERA, - /// String value, but designated as an asset - Asset, - /// - U64 - } - - /// - /// - /// - public enum ClassType - { - /// - Unknown = -1, - /// - ReadOnly, - /// - ReadWrite, - /// - Callback - } - - /// - /// - /// - public enum SendtoType - { - /// - Unknown = -1, - /// - Sim, - /// - DataSim, - /// - SimViewer, - /// - DataSimViewer - } - - - /// - public string Name = String.Empty; - /// - public ValueType Type = ValueType.Unknown; - /// - public ClassType Class = ClassType.Unknown; - /// - public SendtoType Sendto = SendtoType.Unknown; - /// - public object Value = null; - - - private static readonly string[] TypeStrings = new string[] - { - "STRING", - "F32", - "S32", - "VEC3", - "U32", - "CAMERA", // Obsolete - "ASSET", - "U64" - }; - private static readonly string[] ClassStrings = new string[] - { - "R", // Read-only - "RW", // Read-write - "CB" // Callback - }; - private static readonly string[] SendtoStrings = new string[] - { - "S", // Sim - "DS", // Data Sim - "SV", // Sim Viewer - "DSV" // Data Sim Viewer - }; - - - /// - /// Default constructor - /// - public NameValue() - { - } - - /// - /// Constructor that takes all the fields as parameters - /// - /// - /// - /// - /// - /// - public NameValue(string name, ValueType valueType, ClassType classType, SendtoType sendtoType, object value) - { - Name = name; - Value = valueType; - Class = classType; - Sendto = sendtoType; - Value = value; - } - - /// - /// Constructor that takes a single line from a NameValue field - /// - /// - public NameValue(string data) - { - int i; - char[] seps = new char[]{ ' ', '\n', '\t', '\r' }; - - // Name - i = data.IndexOfAny(seps); - if (i < 1) - return; - Name = data.Substring(0, i); - data = data.Substring(i + 1); - - // Type - i = data.IndexOfAny(seps); - if (i > 0) - { - Type = GetValueType(data.Substring(0, i)); - data = data.Substring(i + 1); - - // Class - i = data.IndexOfAny(seps); - if (i > 0) - { - Class = GetClassType(data.Substring(0, i)); - data = data.Substring(i + 1); - - // Sendto - i = data.IndexOfAny(seps); - if (i > 0) - { - Sendto = GetSendtoType(data.Substring(0, 1)); - data = data.Substring(i + 1); - } - } - } - - // Value - Type = ValueType.String; - Class = ClassType.ReadOnly; - Sendto = SendtoType.Sim; - SetValue(data); - } - - private void SetValue(string value) - { - switch (Type) - { - case ValueType.Asset: - case ValueType.String: - Value = value; - break; - case ValueType.F32: - { - float temp = 0.0f; - Single.TryParse(value, out temp); - Value = temp; - break; - } - case ValueType.S32: - { - int temp = 0; - Int32.TryParse(value, out temp); - Value = temp; - break; - } - case ValueType.U32: - { - uint temp = 0; - UInt32.TryParse(value, out temp); - Value = temp; - break; - } - case ValueType.U64: - { - ulong temp = 0; - UInt64.TryParse(value, out temp); - Value = temp; - break; - } - case ValueType.VEC3: - { - LLVector3 temp = LLVector3.Zero; - LLVector3.TryParse(value, out temp); - Value = temp; - break; - } - default: - Value = null; - break; - } - } - - private ValueType GetValueType(string value) - { - ValueType type = ValueType.Unknown; - - for (int i = 0; i < TypeStrings.Length; i++) - { - if (value == TypeStrings[i]) - { - type = (ValueType)i; - break; - } - } - - if (type == ValueType.Unknown) - type = ValueType.String; - - return type; - } - - private ClassType GetClassType(string value) - { - ClassType type = ClassType.Unknown; - - for (int i = 0; i < ClassStrings.Length; i++) - { - if (value == ClassStrings[i]) - { - type = (ClassType)i; - break; - } - } - - if (type == ClassType.Unknown) - type = ClassType.ReadOnly; - - return type; - } - - private SendtoType GetSendtoType(string value) - { - SendtoType type = SendtoType.Unknown; - - for (int i = 0; i < SendtoStrings.Length; i++) - { - if (value == SendtoStrings[i]) - { - type = (SendtoType)i; - break; - } - } - - if (type == SendtoType.Unknown) - type = SendtoType.Sim; - - return type; - } - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; + +namespace libsecondlife +{ + /// + /// A Name Value pair with additional settings + /// + [Serializable] + public class NameValue + { + /// Type of the value + public enum ValueType + { + /// Unknown + Unknown = -1, + /// String value + String, + /// + F32, + /// + S32, + /// + VEC3, + /// + U32, + /// Deprecated + [Obsolete] + CAMERA, + /// String value, but designated as an asset + Asset, + /// + U64 + } + + /// + /// + /// + public enum ClassType + { + /// + Unknown = -1, + /// + ReadOnly, + /// + ReadWrite, + /// + Callback + } + + /// + /// + /// + public enum SendtoType + { + /// + Unknown = -1, + /// + Sim, + /// + DataSim, + /// + SimViewer, + /// + DataSimViewer + } + + + /// + public string Name = String.Empty; + /// + public ValueType Type = ValueType.Unknown; + /// + public ClassType Class = ClassType.Unknown; + /// + public SendtoType Sendto = SendtoType.Unknown; + /// + public object Value = null; + + + private static readonly string[] TypeStrings = new string[] + { + "STRING", + "F32", + "S32", + "VEC3", + "U32", + "CAMERA", // Obsolete + "ASSET", + "U64" + }; + private static readonly string[] ClassStrings = new string[] + { + "R", // Read-only + "RW", // Read-write + "CB" // Callback + }; + private static readonly string[] SendtoStrings = new string[] + { + "S", // Sim + "DS", // Data Sim + "SV", // Sim Viewer + "DSV" // Data Sim Viewer + }; + + + /// + /// Default constructor + /// + public NameValue() + { + } + + /// + /// Constructor that takes all the fields as parameters + /// + /// + /// + /// + /// + /// + public NameValue(string name, ValueType valueType, ClassType classType, SendtoType sendtoType, object value) + { + Name = name; + Value = valueType; + Class = classType; + Sendto = sendtoType; + Value = value; + } + + /// + /// Constructor that takes a single line from a NameValue field + /// + /// + public NameValue(string data) + { + int i; + char[] seps = new char[]{ ' ', '\n', '\t', '\r' }; + + // Name + i = data.IndexOfAny(seps); + if (i < 1) + return; + Name = data.Substring(0, i); + data = data.Substring(i + 1); + + // Type + i = data.IndexOfAny(seps); + if (i > 0) + { + Type = GetValueType(data.Substring(0, i)); + data = data.Substring(i + 1); + + // Class + i = data.IndexOfAny(seps); + if (i > 0) + { + Class = GetClassType(data.Substring(0, i)); + data = data.Substring(i + 1); + + // Sendto + i = data.IndexOfAny(seps); + if (i > 0) + { + Sendto = GetSendtoType(data.Substring(0, 1)); + data = data.Substring(i + 1); + } + } + } + + // Value + Type = ValueType.String; + Class = ClassType.ReadOnly; + Sendto = SendtoType.Sim; + SetValue(data); + } + + private void SetValue(string value) + { + switch (Type) + { + case ValueType.Asset: + case ValueType.String: + Value = value; + break; + case ValueType.F32: + { + float temp = 0.0f; + Single.TryParse(value, out temp); + Value = temp; + break; + } + case ValueType.S32: + { + int temp = 0; + Int32.TryParse(value, out temp); + Value = temp; + break; + } + case ValueType.U32: + { + uint temp = 0; + UInt32.TryParse(value, out temp); + Value = temp; + break; + } + case ValueType.U64: + { + ulong temp = 0; + UInt64.TryParse(value, out temp); + Value = temp; + break; + } + case ValueType.VEC3: + { + LLVector3 temp = LLVector3.Zero; + LLVector3.TryParse(value, out temp); + Value = temp; + break; + } + default: + Value = null; + break; + } + } + + private ValueType GetValueType(string value) + { + ValueType type = ValueType.Unknown; + + for (int i = 0; i < TypeStrings.Length; i++) + { + if (value == TypeStrings[i]) + { + type = (ValueType)i; + break; + } + } + + if (type == ValueType.Unknown) + type = ValueType.String; + + return type; + } + + private ClassType GetClassType(string value) + { + ClassType type = ClassType.Unknown; + + for (int i = 0; i < ClassStrings.Length; i++) + { + if (value == ClassStrings[i]) + { + type = (ClassType)i; + break; + } + } + + if (type == ClassType.Unknown) + type = ClassType.ReadOnly; + + return type; + } + + private SendtoType GetSendtoType(string value) + { + SendtoType type = SendtoType.Unknown; + + for (int i = 0; i < SendtoStrings.Length; i++) + { + if (value == SendtoStrings[i]) + { + type = (SendtoType)i; + break; + } + } + + if (type == SendtoType.Unknown) + type = SendtoType.Sim; + + return type; + } + } +} diff --git a/libsecondlife-cs/ParticleSystem.cs b/libsecondlife-cs/ParticleSystem.cs index 390a8a49..3bd0c189 100644 --- a/libsecondlife-cs/ParticleSystem.cs +++ b/libsecondlife-cs/ParticleSystem.cs @@ -1,188 +1,188 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Xml.Serialization; - -namespace libsecondlife -{ - public partial class Primitive - { - /// - /// - /// - [Serializable] - public class ParticleSystem - { - /// - /// - /// - public enum SourcePattern : byte - { - /// - None = 0, - /// - Drop = 0x01, - /// - Explode = 0x02, - /// - Angle = 0x04, - /// - AngleCone = 0x08, - /// - AngleConeEmpty = 0x10 - } - - /// - /// Flags for the particle system behavior - /// - [Flags] - public enum ParticleSystemFlags : uint - { - /// - None = 0, - /// Acceleration and velocity for particles are - /// relative to the object rotation - ObjectRelative = 0x01, - /// Particles use new 'correct' angle parameters - UseNewAngle = 0x02 - } - - /// - /// Flags for the particles in this particle system - /// - [Flags] - public enum ParticleFlags : uint - { - /// - None = 0, - /// - InterpColor = 0x001, - /// - InterpScale = 0x002, - /// - Bounce = 0x004, - /// - Wind = 0x008, - /// - FollowSrc = 0x010, - /// - FollowVelocity = 0x20, - /// - TargetPos = 0x40, - /// - TargetLinear = 0x080, - /// - Emissive = 0x100, - /// - Beam = 0x200 - } - - - public uint CRC; - public SourcePattern Pattern = SourcePattern.None; - public ParticleSystemFlags Flags = ParticleSystemFlags.None; - public float MaxAge; - public float StartAge; - public float InnerAngle; - public float OuterAngle; - public float BurstRate; - public float BurstRadius; - public float BurstSpeedMin; - public float BurstSpeedMax; - public float BurstPartCount; - public LLVector3 AngularVelocity; - public LLVector3 PartAcceleration; - public LLUUID Texture; - public LLUUID Target; - public LLColor PartStartColor; - public LLColor PartEndColor; - public float PartStartScaleX; - public float PartStartScaleY; - public float PartEndScaleX; - public float PartEndScaleY; - public float PartMaxAge; - public ParticleFlags PartFlags = ParticleFlags.None; - - - /// - /// - /// - public ParticleSystem() - { - } - - /// - /// - /// - /// - /// - public ParticleSystem(byte[] data, int pos) - { - FromBytes(data, pos); - } - - /// - /// - /// - /// - public byte[] GetBytes() - { - byte[] bytes = new byte[0]; - // FIXME: Finish ParticleSystem.GetBytes() - return bytes; - } - - /// - /// - /// - /// - /// - private void FromBytes(byte[] data, int pos) - { - if (data.Length == 0) - return; - - BitPack pack = new BitPack(data, pos); - - CRC = pack.UnpackUBits(32); - Flags = (ParticleSystemFlags)pack.UnpackUBits(32); - Pattern = (SourcePattern)pack.UnpackByte(); - MaxAge = pack.UnpackFixed(false, 8, 8); - StartAge = pack.UnpackFixed(false, 8, 8); - InnerAngle = pack.UnpackFixed(false, 3, 5); - OuterAngle = pack.UnpackFixed(false, 3, 5); - BurstRate = pack.UnpackFixed(false, 8, 8); - BurstRadius = pack.UnpackFixed(false, 8, 8); - BurstSpeedMin = pack.UnpackFixed(false, 8, 8); - BurstSpeedMax = pack.UnpackFixed(false, 8, 8); - BurstPartCount = (uint)pack.UnpackByte(); - float x = pack.UnpackFixed(true, 8, 7); - float y = pack.UnpackFixed(true, 8, 7); - float z = pack.UnpackFixed(true, 8, 7); - AngularVelocity = new LLVector3(x, y, z); - x = pack.UnpackFixed(true, 8, 7); - y = pack.UnpackFixed(true, 8, 7); - z = pack.UnpackFixed(true, 8, 7); - PartAcceleration = new LLVector3(x, y, z); - Texture = pack.UnpackUUID(); - Target = pack.UnpackUUID(); - PartFlags = (ParticleFlags)pack.UnpackUBits(32); - PartMaxAge = pack.UnpackFixed(false, 8, 8); - byte r = pack.UnpackByte(); - byte g = pack.UnpackByte(); - byte b = pack.UnpackByte(); - byte a = pack.UnpackByte(); - PartStartColor = new LLColor(r, g, b, a); - r = pack.UnpackByte(); - g = pack.UnpackByte(); - b = pack.UnpackByte(); - a = pack.UnpackByte(); - PartEndColor = new LLColor(r, g, b, a); - PartStartScaleX = pack.UnpackFixed(false, 3, 5); - PartStartScaleY = pack.UnpackFixed(false, 3, 5); - PartEndScaleX = pack.UnpackFixed(false, 3, 5); - PartEndScaleY = pack.UnpackFixed(false, 3, 5); - } - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Xml.Serialization; + +namespace libsecondlife +{ + public partial class Primitive + { + /// + /// + /// + [Serializable] + public class ParticleSystem + { + /// + /// + /// + public enum SourcePattern : byte + { + /// + None = 0, + /// + Drop = 0x01, + /// + Explode = 0x02, + /// + Angle = 0x04, + /// + AngleCone = 0x08, + /// + AngleConeEmpty = 0x10 + } + + /// + /// Flags for the particle system behavior + /// + [Flags] + public enum ParticleSystemFlags : uint + { + /// + None = 0, + /// Acceleration and velocity for particles are + /// relative to the object rotation + ObjectRelative = 0x01, + /// Particles use new 'correct' angle parameters + UseNewAngle = 0x02 + } + + /// + /// Flags for the particles in this particle system + /// + [Flags] + public enum ParticleFlags : uint + { + /// + None = 0, + /// + InterpColor = 0x001, + /// + InterpScale = 0x002, + /// + Bounce = 0x004, + /// + Wind = 0x008, + /// + FollowSrc = 0x010, + /// + FollowVelocity = 0x20, + /// + TargetPos = 0x40, + /// + TargetLinear = 0x080, + /// + Emissive = 0x100, + /// + Beam = 0x200 + } + + + public uint CRC; + public SourcePattern Pattern = SourcePattern.None; + public ParticleSystemFlags Flags = ParticleSystemFlags.None; + public float MaxAge; + public float StartAge; + public float InnerAngle; + public float OuterAngle; + public float BurstRate; + public float BurstRadius; + public float BurstSpeedMin; + public float BurstSpeedMax; + public float BurstPartCount; + public LLVector3 AngularVelocity; + public LLVector3 PartAcceleration; + public LLUUID Texture; + public LLUUID Target; + public LLColor PartStartColor; + public LLColor PartEndColor; + public float PartStartScaleX; + public float PartStartScaleY; + public float PartEndScaleX; + public float PartEndScaleY; + public float PartMaxAge; + public ParticleFlags PartFlags = ParticleFlags.None; + + + /// + /// + /// + public ParticleSystem() + { + } + + /// + /// + /// + /// + /// + public ParticleSystem(byte[] data, int pos) + { + FromBytes(data, pos); + } + + /// + /// + /// + /// + public byte[] GetBytes() + { + byte[] bytes = new byte[0]; + // FIXME: Finish ParticleSystem.GetBytes() + return bytes; + } + + /// + /// + /// + /// + /// + private void FromBytes(byte[] data, int pos) + { + if (data.Length == 0) + return; + + BitPack pack = new BitPack(data, pos); + + CRC = pack.UnpackUBits(32); + Flags = (ParticleSystemFlags)pack.UnpackUBits(32); + Pattern = (SourcePattern)pack.UnpackByte(); + MaxAge = pack.UnpackFixed(false, 8, 8); + StartAge = pack.UnpackFixed(false, 8, 8); + InnerAngle = pack.UnpackFixed(false, 3, 5); + OuterAngle = pack.UnpackFixed(false, 3, 5); + BurstRate = pack.UnpackFixed(false, 8, 8); + BurstRadius = pack.UnpackFixed(false, 8, 8); + BurstSpeedMin = pack.UnpackFixed(false, 8, 8); + BurstSpeedMax = pack.UnpackFixed(false, 8, 8); + BurstPartCount = (uint)pack.UnpackByte(); + float x = pack.UnpackFixed(true, 8, 7); + float y = pack.UnpackFixed(true, 8, 7); + float z = pack.UnpackFixed(true, 8, 7); + AngularVelocity = new LLVector3(x, y, z); + x = pack.UnpackFixed(true, 8, 7); + y = pack.UnpackFixed(true, 8, 7); + z = pack.UnpackFixed(true, 8, 7); + PartAcceleration = new LLVector3(x, y, z); + Texture = pack.UnpackUUID(); + Target = pack.UnpackUUID(); + PartFlags = (ParticleFlags)pack.UnpackUBits(32); + PartMaxAge = pack.UnpackFixed(false, 8, 8); + byte r = pack.UnpackByte(); + byte g = pack.UnpackByte(); + byte b = pack.UnpackByte(); + byte a = pack.UnpackByte(); + PartStartColor = new LLColor(r, g, b, a); + r = pack.UnpackByte(); + g = pack.UnpackByte(); + b = pack.UnpackByte(); + a = pack.UnpackByte(); + PartEndColor = new LLColor(r, g, b, a); + PartStartScaleX = pack.UnpackFixed(false, 3, 5); + PartStartScaleY = pack.UnpackFixed(false, 3, 5); + PartEndScaleX = pack.UnpackFixed(false, 3, 5); + PartEndScaleY = pack.UnpackFixed(false, 3, 5); + } + } + } +} diff --git a/libsecondlife-cs/SerializableDictionary.cs b/libsecondlife-cs/SerializableDictionary.cs index 4253a59b..b66dc7e4 100644 --- a/libsecondlife-cs/SerializableDictionary.cs +++ b/libsecondlife-cs/SerializableDictionary.cs @@ -1,115 +1,115 @@ -/* - * Adapted from code by Paul Welter at - * http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace libsecondlife -{ - /// - /// A generic dictionary that can be serialized to XML - /// - /// Key type, used as indices to values - /// Value type - [Serializable] - public class SerializableDictionary : Dictionary, IXmlSerializable - { - #region IXmlSerializable Members - - /// - /// - /// - /// - public System.Xml.Schema.XmlSchema GetSchema() - { - return null; - } - - /// - /// - /// - /// - public void ReadXml(System.Xml.XmlReader reader) - { - XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); - XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); - - bool wasEmpty = reader.IsEmptyElement; - reader.Read(); - - if (wasEmpty) - return; - - while (reader.NodeType != System.Xml.XmlNodeType.EndElement) - { - reader.ReadStartElement("item"); - - reader.ReadStartElement("key"); - TKey key = (TKey)keySerializer.Deserialize(reader); - reader.ReadEndElement(); - - reader.ReadStartElement("value"); - TValue value = (TValue)valueSerializer.Deserialize(reader); - reader.ReadEndElement(); - - this.Add(key, value); - - reader.ReadEndElement(); - reader.MoveToContent(); - } - - reader.ReadEndElement(); - } - - /// - /// - /// - /// - public void WriteXml(System.Xml.XmlWriter writer) - { - XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); - XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); - - foreach (TKey key in this.Keys) - { - writer.WriteStartElement("item"); - - writer.WriteStartElement("key"); - keySerializer.Serialize(writer, key); - writer.WriteEndElement(); - - writer.WriteStartElement("value"); - TValue value = this[key]; - valueSerializer.Serialize(writer, value); - writer.WriteEndElement(); - writer.WriteEndElement(); - } - } - - #endregion - } -} +/* + * Adapted from code by Paul Welter at + * http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace libsecondlife +{ + /// + /// A generic dictionary that can be serialized to XML + /// + /// Key type, used as indices to values + /// Value type + [Serializable] + public class SerializableDictionary : Dictionary, IXmlSerializable + { + #region IXmlSerializable Members + + /// + /// + /// + /// + public System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + + /// + /// + /// + /// + public void ReadXml(System.Xml.XmlReader reader) + { + XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); + XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); + + bool wasEmpty = reader.IsEmptyElement; + reader.Read(); + + if (wasEmpty) + return; + + while (reader.NodeType != System.Xml.XmlNodeType.EndElement) + { + reader.ReadStartElement("item"); + + reader.ReadStartElement("key"); + TKey key = (TKey)keySerializer.Deserialize(reader); + reader.ReadEndElement(); + + reader.ReadStartElement("value"); + TValue value = (TValue)valueSerializer.Deserialize(reader); + reader.ReadEndElement(); + + this.Add(key, value); + + reader.ReadEndElement(); + reader.MoveToContent(); + } + + reader.ReadEndElement(); + } + + /// + /// + /// + /// + public void WriteXml(System.Xml.XmlWriter writer) + { + XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); + XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); + + foreach (TKey key in this.Keys) + { + writer.WriteStartElement("item"); + + writer.WriteStartElement("key"); + keySerializer.Serialize(writer, key); + writer.WriteEndElement(); + + writer.WriteStartElement("value"); + TValue value = this[key]; + valueSerializer.Serialize(writer, value); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + } + + #endregion + } +} diff --git a/libsecondlife-cs/Settings.cs b/libsecondlife-cs/Settings.cs index 80bafef5..20a03946 100644 --- a/libsecondlife-cs/Settings.cs +++ b/libsecondlife-cs/Settings.cs @@ -1,126 +1,126 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Class for controlling various system settings. - /// - public class Settings - { - /// The version of libsecondlife (not the SL protocol itself) - public string VERSION = "libsecondlife 0.0.9"; - /// XML-RPC login server to connect to - public string LOGIN_SERVER = "https://login.agni.lindenlab.com/cgi-bin/login.cgi"; - - /// Maximum size of packet that we want to send over the wire - public readonly int MAX_PACKET_SIZE = 1200; - /// Millisecond interval between ticks, where all ACKs are - /// sent out and the age of unACKed packets is checked - public readonly int NETWORK_TICK_LENGTH = 500; - /// The maximum value of a packet sequence number. After that - /// we assume the sequence number just rolls over? Or maybe the - /// protocol isn't able to sustain a connection past that - public readonly int MAX_SEQUENCE = 0xFFFFFF; - /// Number of milliseconds before a teleport attempt will time - /// out - public readonly int TELEPORT_TIMEOUT = 25 * 1000; - - /// Number of milliseconds before NetworkManager.Logout() will time out - public int LOGOUT_TIMEOUT = 5 * 1000; - /// Number of milliseconds for xml-rpc to timeout - public int LOGIN_TIMEOUT = 60 * 1000; - /// The maximum size of the sequence number inbox, used to - /// check for resent and/or duplicate packets - public int INBOX_SIZE = 100; - /// Milliseconds before a packet is assumed lost and resent - public int RESEND_TIMEOUT = 4000; - /// Milliseconds without receiving a packet before the - /// connection to a simulator is assumed lost - public int SIMULATOR_TIMEOUT = 5 * 1000; - /// Maximum number of queued ACKs to be sent before SendAcks() - /// is forced - public int MAX_PENDING_ACKS = 10; - /// Maximum number of ACKs to append to a packet - public int MAX_APPENDED_ACKS = 10; - /// Cost of uploading an asset - public int UPLOAD_COST { get { return priceUpload; } } - /// Enable/disable debugging log messages - public bool DEBUG = true; - /// Enable/disable storing terrain heightmaps in the TerrainManager - public bool STORE_LAND_PATCHES = false; - /// Enable/disable sending periodic camera updates - public bool SEND_AGENT_UPDATES = true; - /// Number of milliseconds between sending camera updates - public int AGENT_UPDATE_INTERVAL = 500; - /// Enable/disable libsecondlife automatically setting the - /// bandwidth throttle after connecting to each simulator - /// The default libsecondlife throttle uses the equivalent of - /// the maximum bandwidth setting in the official client. If you do not - /// set a throttle your connection will by default be throttled well - /// below the minimum values and you may experience connection problems +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Class for controlling various system settings. + /// + public class Settings + { + /// The version of libsecondlife (not the SL protocol itself) + public string VERSION = "libsecondlife 0.0.9"; + /// XML-RPC login server to connect to + public string LOGIN_SERVER = "https://login.agni.lindenlab.com/cgi-bin/login.cgi"; + + /// Maximum size of packet that we want to send over the wire + public readonly int MAX_PACKET_SIZE = 1200; + /// Millisecond interval between ticks, where all ACKs are + /// sent out and the age of unACKed packets is checked + public readonly int NETWORK_TICK_LENGTH = 500; + /// The maximum value of a packet sequence number. After that + /// we assume the sequence number just rolls over? Or maybe the + /// protocol isn't able to sustain a connection past that + public readonly int MAX_SEQUENCE = 0xFFFFFF; + /// Number of milliseconds before a teleport attempt will time + /// out + public readonly int TELEPORT_TIMEOUT = 25 * 1000; + + /// Number of milliseconds before NetworkManager.Logout() will time out + public int LOGOUT_TIMEOUT = 5 * 1000; + /// Number of milliseconds for xml-rpc to timeout + public int LOGIN_TIMEOUT = 60 * 1000; + /// The maximum size of the sequence number inbox, used to + /// check for resent and/or duplicate packets + public int INBOX_SIZE = 100; + /// Milliseconds before a packet is assumed lost and resent + public int RESEND_TIMEOUT = 4000; + /// Milliseconds without receiving a packet before the + /// connection to a simulator is assumed lost + public int SIMULATOR_TIMEOUT = 5 * 1000; + /// Maximum number of queued ACKs to be sent before SendAcks() + /// is forced + public int MAX_PENDING_ACKS = 10; + /// Maximum number of ACKs to append to a packet + public int MAX_APPENDED_ACKS = 10; + /// Cost of uploading an asset + public int UPLOAD_COST { get { return priceUpload; } } + /// Enable/disable debugging log messages + public bool DEBUG = true; + /// Enable/disable storing terrain heightmaps in the TerrainManager + public bool STORE_LAND_PATCHES = false; + /// Enable/disable sending periodic camera updates + public bool SEND_AGENT_UPDATES = true; + /// Number of milliseconds between sending camera updates + public int AGENT_UPDATE_INTERVAL = 500; + /// Enable/disable libsecondlife automatically setting the + /// bandwidth throttle after connecting to each simulator + /// The default libsecondlife throttle uses the equivalent of + /// the maximum bandwidth setting in the official client. If you do not + /// set a throttle your connection will by default be throttled well + /// below the minimum values and you may experience connection problems public bool SEND_THROTTLE = true; - /// Enable/disable the sending of pings to monitor lag and packet lossNumber of milliseconds between sending pings to each sim - public int PING_INTERVAL = 550; - /// Ping-time change beyond which a warning is displayed - public float LAG_WARNING_DELTA = 0.50f; - /// If this is true, connection will be killed if we stop receiving pongs - public bool USE_WATCHDOG = false; - /// Number of seconds to wait for pong before killing - public int WATCHDOG_SECONDS = 5; - private SecondLife Client; - private int priceUpload = 0; - - /// - /// Constructor - /// - /// Client connection Object to use - public Settings(SecondLife client) - { - Client = client; - - Client.Network.RegisterCallback(Packets.PacketType.EconomyData, new NetworkManager.PacketCallback(EconomyDataHandler)); - } - - /// - /// Presumably for outputting asset upload costs. - /// - /// - /// - private void EconomyDataHandler(Packet packet, Simulator simulator) - { - EconomyDataPacket econ = (EconomyDataPacket)packet; - - priceUpload = econ.Info.PriceUpload; - } - } -} + /// Enable/disable the sending of pings to monitor lag and packet lossNumber of milliseconds between sending pings to each sim + public int PING_INTERVAL = 1100; + /// Ping-time change beyond which a warning is displayed + public float LAG_WARNING_DELTA = 0.50f; + /// If this is true, connection will be killed if we stop receiving pongs + public bool USE_WATCHDOG = false; + /// Number of seconds to wait for pong before killing + public int WATCHDOG_SECONDS = 10; + private SecondLife Client; + private int priceUpload = 0; + + /// + /// Constructor + /// + /// Client connection Object to use + public Settings(SecondLife client) + { + Client = client; + + Client.Network.RegisterCallback(Packets.PacketType.EconomyData, new NetworkManager.PacketCallback(EconomyDataHandler)); + } + + /// + /// Presumably for outputting asset upload costs. + /// + /// + /// + private void EconomyDataHandler(Packet packet, Simulator simulator) + { + EconomyDataPacket econ = (EconomyDataPacket)packet; + + priceUpload = econ.Info.PriceUpload; + } + } +} diff --git a/libsecondlife-cs/Simulator.cs b/libsecondlife-cs/Simulator.cs index 4195494e..f26efe2c 100644 --- a/libsecondlife-cs/Simulator.cs +++ b/libsecondlife-cs/Simulator.cs @@ -1,399 +1,399 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Net; -using System.Net.Sockets; -using libsecondlife.Packets; - -namespace libsecondlife -{ - /// - /// Simulator is a wrapper for a network connection to a simulator and the - /// Region class representing the block of land in the metaverse - /// - public class Simulator - { - /// A public reference to the client that this Simulator object - /// is attached to - public SecondLife Client; - /// - public LLUUID ID = LLUUID.Zero; - /// - public ulong Handle; - /// - public string Name = String.Empty; - /// - public byte[] ParcelOverlay = new byte[4096]; - /// - public int ParcelOverlaysReceived; - /// - public float TerrainHeightRange00; - /// - public float TerrainHeightRange01; - /// - public float TerrainHeightRange10; - /// - public float TerrainHeightRange11; - /// - public float TerrainStartHeight00; - /// - public float TerrainStartHeight01; - /// - public float TerrainStartHeight10; - /// - public float TerrainStartHeight11; - /// - public float WaterHeight; - /// - public LLUUID SimOwner = LLUUID.Zero; - /// - public LLUUID TerrainBase0 = LLUUID.Zero; - /// - public LLUUID TerrainBase1 = LLUUID.Zero; - /// - public LLUUID TerrainBase2 = LLUUID.Zero; - /// - public LLUUID TerrainBase3 = LLUUID.Zero; - /// - public LLUUID TerrainDetail0 = LLUUID.Zero; - /// - public LLUUID TerrainDetail1 = LLUUID.Zero; - /// - public LLUUID TerrainDetail2 = LLUUID.Zero; - /// - public LLUUID TerrainDetail3 = LLUUID.Zero; - /// - public bool IsEstateManager; - /// - public EstateTools Estate; - /// Current time dilation of this simulator - public float Dilation; - - /// The IP address and port of the server - public IPEndPoint IPEndPoint { get { return ipEndPoint; } } - /// Whether there is a working connection to the simulator or - /// not - public bool Connected { get { return connected; } } - /// Coarse locations of avatars in this simulator - public List AvatarPositions { get { return avatarPositions; } } - - /// Used internally to track sim disconnections - internal bool DisconnectCandidate = false; - /// - internal ManualResetEvent ConnectedEvent = new ManualResetEvent(false); - /// - internal bool connected; - /// Coarse locations of avatars in this simulator - internal List avatarPositions = new List(); +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using libsecondlife.Packets; + +namespace libsecondlife +{ + /// + /// Simulator is a wrapper for a network connection to a simulator and the + /// Region class representing the block of land in the metaverse + /// + public class Simulator + { + /// A public reference to the client that this Simulator object + /// is attached to + public SecondLife Client; + /// + public LLUUID ID = LLUUID.Zero; + /// + public ulong Handle; + /// + public string Name = String.Empty; + /// + public byte[] ParcelOverlay = new byte[4096]; + /// + public int ParcelOverlaysReceived; + /// + public float TerrainHeightRange00; + /// + public float TerrainHeightRange01; + /// + public float TerrainHeightRange10; + /// + public float TerrainHeightRange11; + /// + public float TerrainStartHeight00; + /// + public float TerrainStartHeight01; + /// + public float TerrainStartHeight10; + /// + public float TerrainStartHeight11; + /// + public float WaterHeight; + /// + public LLUUID SimOwner = LLUUID.Zero; + /// + public LLUUID TerrainBase0 = LLUUID.Zero; + /// + public LLUUID TerrainBase1 = LLUUID.Zero; + /// + public LLUUID TerrainBase2 = LLUUID.Zero; + /// + public LLUUID TerrainBase3 = LLUUID.Zero; + /// + public LLUUID TerrainDetail0 = LLUUID.Zero; + /// + public LLUUID TerrainDetail1 = LLUUID.Zero; + /// + public LLUUID TerrainDetail2 = LLUUID.Zero; + /// + public LLUUID TerrainDetail3 = LLUUID.Zero; + /// + public bool IsEstateManager; + /// + public EstateTools Estate; + /// Current time dilation of this simulator + public float Dilation; + + /// The IP address and port of the server + public IPEndPoint IPEndPoint { get { return ipEndPoint; } } + /// Whether there is a working connection to the simulator or + /// not + public bool Connected { get { return connected; } } + /// Coarse locations of avatars in this simulator + public List AvatarPositions { get { return avatarPositions; } } + + /// Used internally to track sim disconnections + internal bool DisconnectCandidate = false; + /// + internal ManualResetEvent ConnectedEvent = new ManualResetEvent(false); + /// + internal bool connected; + /// Coarse locations of avatars in this simulator + internal List avatarPositions = new List(); public ulong SentPackets = 0; // <-- these could probably be uints public ulong RecvPackets = 0; public ulong SentBytes = 0; public ulong RecvBytes = 0; public int ConnectTime = 0; public int ResentPackets = 0; - - private NetworkManager Network; - private uint Sequence = 0; - private object SequenceLock = new object(); - private byte[] RecvBuffer = new byte[4096]; - private byte[] ZeroBuffer = new byte[8192]; - private byte[] ZeroOutBuffer = new byte[4096]; - private Socket Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - private AsyncCallback ReceivedData; - // Packets we sent out that need ACKs from the simulator - private Dictionary NeedAck = new Dictionary(); - // Sequence numbers of packets we've received from the simulator - private Queue Inbox; - // ACKs that are queued up to be sent to the simulator - private SortedList PendingAcks = new SortedList(); - private IPEndPoint ipEndPoint; - private EndPoint endPoint; - private System.Timers.Timer AckTimer; + + private NetworkManager Network; + private uint Sequence = 0; + private object SequenceLock = new object(); + private byte[] RecvBuffer = new byte[4096]; + private byte[] ZeroBuffer = new byte[8192]; + private byte[] ZeroOutBuffer = new byte[4096]; + private Socket Connection = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + private AsyncCallback ReceivedData; + // Packets we sent out that need ACKs from the simulator + private Dictionary NeedAck = new Dictionary(); + // Sequence numbers of packets we've received from the simulator + private Queue Inbox; + // ACKs that are queued up to be sent to the simulator + private SortedList PendingAcks = new SortedList(); + private IPEndPoint ipEndPoint; + private EndPoint endPoint; + private System.Timers.Timer AckTimer; private System.Timers.Timer PingTimer; - + /* Ping processing, to measure link health */ public int LastPingSent = 0; public byte LastPingID = 0; public int LastLag = 0; public int MissedPings =0; - - /// - /// Default constructor - /// - /// Reference to the SecondLife client - /// IP address of the simulator - /// Port on the simulator to connect to - /// Whether to move our agent in to this sim or not - public Simulator(SecondLife client, IPAddress ip, int port, bool moveToSim) - { - Client = client; - Estate = new EstateTools(Client); - Network = client.Network; - Inbox = new Queue(Client.Settings.INBOX_SIZE); - - // Start the ACK timer - AckTimer = new System.Timers.Timer(Client.Settings.NETWORK_TICK_LENGTH); - AckTimer.Elapsed += new System.Timers.ElapsedEventHandler(AckTimer_Elapsed); - AckTimer.Start(); - + + /// + /// Default constructor + /// + /// Reference to the SecondLife client + /// IP address of the simulator + /// Port on the simulator to connect to + /// Whether to move our agent in to this sim or not + public Simulator(SecondLife client, IPAddress ip, int port, bool moveToSim) + { + Client = client; + Estate = new EstateTools(Client); + Network = client.Network; + Inbox = new Queue(Client.Settings.INBOX_SIZE); + + // Start the ACK timer + AckTimer = new System.Timers.Timer(Client.Settings.NETWORK_TICK_LENGTH); + AckTimer.Elapsed += new System.Timers.ElapsedEventHandler(AckTimer_Elapsed); + AckTimer.Start(); + if(Client.Settings.SEND_PINGS) { - // Start the PING timer - PingTimer = new System.Timers.Timer(Client.Settings.PING_INTERVAL); - PingTimer.Elapsed += new System.Timers.ElapsedEventHandler(PingTimer_Elapsed); - PingTimer.Start(); + // Start the PING timer + PingTimer = new System.Timers.Timer(Client.Settings.PING_INTERVAL); + PingTimer.Elapsed += new System.Timers.ElapsedEventHandler(PingTimer_Elapsed); + PingTimer.Start(); } - // Initialize the callback for receiving a new packet - ReceivedData = new AsyncCallback(OnReceivedData); - - Client.Log("Connecting to " + ip.ToString() + ":" + port, Helpers.LogLevel.Info); - - try - { - ConnectedEvent.Reset(); - - // Create an endpoint that we will be communicating with (need it in two - // types due to .NET weirdness) - ipEndPoint = new IPEndPoint(ip, port); - endPoint = (EndPoint)ipEndPoint; - - // Associate this simulator's socket with the given ip/port and start listening - Connection.Connect(endPoint); - Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); - - // Send the UseCircuitCode packet to initiate the connection - UseCircuitCodePacket use = new UseCircuitCodePacket(); - use.CircuitCode.Code = Network.CircuitCode; - use.CircuitCode.ID = Network.AgentID; - use.CircuitCode.SessionID = Network.SessionID; - - // Send the initial packet out - SendPacket(use, true); - - ConnectTime = Environment.TickCount; - // Move our agent in to the sim to complete the connection - if (moveToSim) Client.Self.CompleteAgentMovement(this); - - if (Client.Settings.SEND_AGENT_UPDATES) - Client.Self.Status.SendUpdate(true, this); + // Initialize the callback for receiving a new packet + ReceivedData = new AsyncCallback(OnReceivedData); - SendPing(); - if (!ConnectedEvent.WaitOne(Client.Settings.SIMULATOR_TIMEOUT, false)) - { - Client.Log("Giving up on waiting for RegionHandshake", Helpers.LogLevel.Warning); - connected = true; - } - } - catch (Exception e) - { - Client.Log(e.ToString(), Helpers.LogLevel.Error); - } - } - - /// - /// Disconnect a Simulator - /// - public void Disconnect() - { - if (connected) - { - connected = false; - AckTimer.Stop(); - if (Client.Settings.SEND_PINGS) PingTimer.Stop(); - - // Send the CloseCircuit notice - CloseCircuitPacket close = new CloseCircuitPacket(); - - if (Connection.Connected) - { - try - { - Connection.Send(close.ToBytes()); - } - catch (SocketException) - { - // There's a high probability of this failing if the network is - // disconnecting, so don't even bother logging the error - } - } - - try - { - // Shut the socket communication down - Connection.Shutdown(SocketShutdown.Both); - } - catch (SocketException) - { - } - } - } - - /// - /// Sends a packet - /// - /// Packet to be sent - /// Increment sequence number? - public void SendPacket(Packet packet, bool incrementSequence) - { - byte[] buffer; - int bytes; - - if (packet.Header.AckList.Length > 0) - { - // Scrub any appended ACKs since all of the ACK handling is done here - packet.Header.AckList = new uint[0]; - } - packet.Header.AppendedAcks = false; - - // Keep track of when this packet was sent out - packet.TickCount = Environment.TickCount; - - if (incrementSequence) - { - // Set the sequence number - lock (SequenceLock) - { - if (Sequence > Client.Settings.MAX_SEQUENCE) - Sequence = 1; - else - Sequence++; - packet.Header.Sequence = Sequence; - } - - if (packet.Header.Reliable) - { - lock (NeedAck) - { - if (!NeedAck.ContainsKey(packet.Header.Sequence)) - { - NeedAck.Add(packet.Header.Sequence, packet); - } - else - { - Client.Log("Attempted to add a duplicate sequence number (" + - packet.Header.Sequence + ") to the NeedAck dictionary for packet type " + - packet.Type.ToString(), Helpers.LogLevel.Warning); - } - } - - // Don't append ACKs to resent packets, in case that's what was causing the - // delivery to fail - if (!packet.Header.Resent) - { - // Append any ACKs that need to be sent out to this packet - lock (PendingAcks) - { - if (PendingAcks.Count > 0 && PendingAcks.Count < Client.Settings.MAX_APPENDED_ACKS && - packet.Type != PacketType.PacketAck && - packet.Type != PacketType.LogoutRequest) - { - packet.Header.AckList = new uint[PendingAcks.Count]; - - for (int i = 0; i < PendingAcks.Count; i++) - { - packet.Header.AckList[i] = PendingAcks.Values[i]; - } - - PendingAcks.Clear(); - packet.Header.AppendedAcks = true; - } - } - } - } - } - - // Serialize the packet - buffer = packet.ToBytes(); - bytes = buffer.Length; - SentBytes += (ulong)bytes; - SentPackets++; - - try - { - // Zerocode if needed - if (packet.Header.Zerocoded) - { - lock (ZeroOutBuffer) - { - bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer); - Connection.Send(ZeroOutBuffer, bytes, SocketFlags.None); - } - } - else - { - Connection.Send(buffer, bytes, SocketFlags.None); - } - } - catch (SocketException) - { - Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket, shutting down " + - this.ToString(), Helpers.LogLevel.Info); - - Network.DisconnectSim(this); - return; - } - } - - /// - /// Send a raw byte array payload as a packet - /// - /// The packet payload - /// Whether the second, third, and fourth bytes - /// should be modified to the current stream sequence number - public void SendPacket(byte[] payload, bool setSequence) - { - try - { - if (setSequence && payload.Length > 3) - { - lock (SequenceLock) - { - payload[1] = (byte)(Sequence >> 16); - payload[2] = (byte)(Sequence >> 8); - payload[3] = (byte)(Sequence % 256); - Sequence++; - } + Client.Log("Connecting to " + ip.ToString() + ":" + port, Helpers.LogLevel.Info); + + try + { + ConnectedEvent.Reset(); + + // Create an endpoint that we will be communicating with (need it in two + // types due to .NET weirdness) + ipEndPoint = new IPEndPoint(ip, port); + endPoint = (EndPoint)ipEndPoint; + + // Associate this simulator's socket with the given ip/port and start listening + Connection.Connect(endPoint); + Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); + + // Send the UseCircuitCode packet to initiate the connection + UseCircuitCodePacket use = new UseCircuitCodePacket(); + use.CircuitCode.Code = Network.CircuitCode; + use.CircuitCode.ID = Network.AgentID; + use.CircuitCode.SessionID = Network.SessionID; + + // Send the initial packet out + SendPacket(use, true); + + ConnectTime = Environment.TickCount; + // Move our agent in to the sim to complete the connection + if (moveToSim) Client.Self.CompleteAgentMovement(this); + + if (Client.Settings.SEND_AGENT_UPDATES) + Client.Self.Status.SendUpdate(true, this); + + SendPing(); + if (!ConnectedEvent.WaitOne(Client.Settings.SIMULATOR_TIMEOUT, false)) + { + Client.Log("Giving up on waiting for RegionHandshake", Helpers.LogLevel.Warning); + connected = true; } - + } + catch (Exception e) + { + Client.Log(e.ToString(), Helpers.LogLevel.Error); + } + } + + /// + /// Disconnect a Simulator + /// + public void Disconnect() + { + if (connected) + { + connected = false; + AckTimer.Stop(); + if (Client.Settings.SEND_PINGS) PingTimer.Stop(); + + // Send the CloseCircuit notice + CloseCircuitPacket close = new CloseCircuitPacket(); + + if (Connection.Connected) + { + try + { + Connection.Send(close.ToBytes()); + } + catch (SocketException) + { + // There's a high probability of this failing if the network is + // disconnecting, so don't even bother logging the error + } + } + + try + { + // Shut the socket communication down + Connection.Shutdown(SocketShutdown.Both); + } + catch (SocketException) + { + } + } + } + + /// + /// Sends a packet + /// + /// Packet to be sent + /// Increment sequence number? + public void SendPacket(Packet packet, bool incrementSequence) + { + byte[] buffer; + int bytes; + + if (packet.Header.AckList.Length > 0) + { + // Scrub any appended ACKs since all of the ACK handling is done here + packet.Header.AckList = new uint[0]; + } + packet.Header.AppendedAcks = false; + + // Keep track of when this packet was sent out + packet.TickCount = Environment.TickCount; + + if (incrementSequence) + { + // Set the sequence number + lock (SequenceLock) + { + if (Sequence > Client.Settings.MAX_SEQUENCE) + Sequence = 1; + else + Sequence++; + packet.Header.Sequence = Sequence; + } + + if (packet.Header.Reliable) + { + lock (NeedAck) + { + if (!NeedAck.ContainsKey(packet.Header.Sequence)) + { + NeedAck.Add(packet.Header.Sequence, packet); + } + else + { + Client.Log("Attempted to add a duplicate sequence number (" + + packet.Header.Sequence + ") to the NeedAck dictionary for packet type " + + packet.Type.ToString(), Helpers.LogLevel.Warning); + } + } + + // Don't append ACKs to resent packets, in case that's what was causing the + // delivery to fail + if (!packet.Header.Resent) + { + // Append any ACKs that need to be sent out to this packet + lock (PendingAcks) + { + if (PendingAcks.Count > 0 && PendingAcks.Count < Client.Settings.MAX_APPENDED_ACKS && + packet.Type != PacketType.PacketAck && + packet.Type != PacketType.LogoutRequest) + { + packet.Header.AckList = new uint[PendingAcks.Count]; + + for (int i = 0; i < PendingAcks.Count; i++) + { + packet.Header.AckList[i] = PendingAcks.Values[i]; + } + + PendingAcks.Clear(); + packet.Header.AppendedAcks = true; + } + } + } + } + } + + // Serialize the packet + buffer = packet.ToBytes(); + bytes = buffer.Length; + SentBytes += (ulong)bytes; + SentPackets++; + + try + { + // Zerocode if needed + if (packet.Header.Zerocoded) + { + lock (ZeroOutBuffer) + { + bytes = Helpers.ZeroEncode(buffer, bytes, ZeroOutBuffer); + Connection.Send(ZeroOutBuffer, bytes, SocketFlags.None); + } + } + else + { + Connection.Send(buffer, bytes, SocketFlags.None); + } + } + catch (SocketException) + { + Client.Log("Tried to send a " + packet.Type.ToString() + " on a closed socket, shutting down " + + this.ToString(), Helpers.LogLevel.Info); + + Network.DisconnectSim(this); + return; + } + } + + /// + /// Send a raw byte array payload as a packet + /// + /// The packet payload + /// Whether the second, third, and fourth bytes + /// should be modified to the current stream sequence number + public void SendPacket(byte[] payload, bool setSequence) + { + try + { + if (setSequence && payload.Length > 3) + { + lock (SequenceLock) + { + payload[1] = (byte)(Sequence >> 16); + payload[2] = (byte)(Sequence >> 8); + payload[3] = (byte)(Sequence % 256); + Sequence++; + } + } + SentBytes += (ulong)payload.Length; - SentPackets++; - Connection.Send(payload, payload.Length, SocketFlags.None); - } - catch (SocketException) - { - Client.Log("Tried to send a " + payload.Length + " byte payload on a closed socket, shutting down " + - this.ToString(), Helpers.LogLevel.Info); - - Network.DisconnectSim(this); - return; - } - } + SentPackets++; + Connection.Send(payload, payload.Length, SocketFlags.None); + } + catch (SocketException) + { + Client.Log("Tried to send a " + payload.Length + " byte payload on a closed socket, shutting down " + + this.ToString(), Helpers.LogLevel.Info); + + Network.DisconnectSim(this); + return; + } + } public void SendPing() { StartPingCheckPacket ping = new StartPingCheckPacket(); @@ -403,305 +403,305 @@ namespace libsecondlife SendPacket(ping, true); LastPingSent=Environment.TickCount; } - - /// - /// - /// - /// - /// - /// - /// - public void ParcelSubdivide(float west, float south, float east, float north) - { - ParcelDividePacket divide = new ParcelDividePacket(); - divide.AgentData.AgentID = Client.Network.AgentID; - divide.AgentData.SessionID = Client.Network.SessionID; - divide.ParcelData.East = east; - divide.ParcelData.North = north; - divide.ParcelData.South = south; - divide.ParcelData.West = west; - - SendPacket(divide, true); - } - - /// - /// - /// - /// - /// - /// - /// - public void ParcelJoin(float west, float south, float east, float north) - { - ParcelJoinPacket join = new ParcelJoinPacket(); - join.AgentData.AgentID = Client.Network.AgentID; - join.AgentData.SessionID = Client.Network.SessionID; - join.ParcelData.East = east; - join.ParcelData.North = north; - join.ParcelData.South = south; - join.ParcelData.West = west; - - SendPacket(join, true); - } - - /// - /// Returns Simulator Name as a String - /// - /// - public override string ToString() - { - if (Name.Length > 0) - return Name + " (" + ipEndPoint.ToString() + ")"; - else - return "(" + ipEndPoint.ToString() + ")"; - } - - /// - /// - /// - /// - public override int GetHashCode() - { - return ID.GetHashCode(); - } - - /// - /// - /// - /// - /// - public override bool Equals(object obj) - { - Simulator sim = obj as Simulator; - if (sim == null) - return false; - return ID.Equals(sim.ID); - } - - /// - /// Sends out pending acknowledgements - /// - private void SendAcks() - { - lock (PendingAcks) - { - if (PendingAcks.Count > 0) - { - if (PendingAcks.Count > 250) - { - // FIXME: Handle the odd case where we have too many pending ACKs queued up - Client.Log("Too many ACKs queued up!", Helpers.LogLevel.Error); - return; - } - - PacketAckPacket acks = new PacketAckPacket(); - acks.Header.Reliable = false; - acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count]; - - for (int i = 0; i < PendingAcks.Count; i++) - { - acks.Packets[i] = new PacketAckPacket.PacketsBlock(); - acks.Packets[i].ID = PendingAcks.Values[i]; - } - - SendPacket(acks, true); - - PendingAcks.Clear(); - } - } - } - - /// - /// Resend unacknowledged packets - /// - private void ResendUnacked() - { - int now = Environment.TickCount; - - lock (NeedAck) - { - foreach (Packet packet in NeedAck.Values) - { - if (now - packet.TickCount > Client.Settings.RESEND_TIMEOUT) - { - Client.DebugLog("Resending " + packet.Type.ToString() + " packet (" + packet.Header.Sequence + - "), " + (now - packet.TickCount) + "ms have passed"); - + + /// + /// + /// + /// + /// + /// + /// + public void ParcelSubdivide(float west, float south, float east, float north) + { + ParcelDividePacket divide = new ParcelDividePacket(); + divide.AgentData.AgentID = Client.Network.AgentID; + divide.AgentData.SessionID = Client.Network.SessionID; + divide.ParcelData.East = east; + divide.ParcelData.North = north; + divide.ParcelData.South = south; + divide.ParcelData.West = west; + + SendPacket(divide, true); + } + + /// + /// + /// + /// + /// + /// + /// + public void ParcelJoin(float west, float south, float east, float north) + { + ParcelJoinPacket join = new ParcelJoinPacket(); + join.AgentData.AgentID = Client.Network.AgentID; + join.AgentData.SessionID = Client.Network.SessionID; + join.ParcelData.East = east; + join.ParcelData.North = north; + join.ParcelData.South = south; + join.ParcelData.West = west; + + SendPacket(join, true); + } + + /// + /// Returns Simulator Name as a String + /// + /// + public override string ToString() + { + if (Name.Length > 0) + return Name + " (" + ipEndPoint.ToString() + ")"; + else + return "(" + ipEndPoint.ToString() + ")"; + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return ID.GetHashCode(); + } + + /// + /// + /// + /// + /// + public override bool Equals(object obj) + { + Simulator sim = obj as Simulator; + if (sim == null) + return false; + return ID.Equals(sim.ID); + } + + /// + /// Sends out pending acknowledgements + /// + private void SendAcks() + { + lock (PendingAcks) + { + if (PendingAcks.Count > 0) + { + if (PendingAcks.Count > 250) + { + // FIXME: Handle the odd case where we have too many pending ACKs queued up + Client.Log("Too many ACKs queued up!", Helpers.LogLevel.Error); + return; + } + + PacketAckPacket acks = new PacketAckPacket(); + acks.Header.Reliable = false; + acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count]; + + for (int i = 0; i < PendingAcks.Count; i++) + { + acks.Packets[i] = new PacketAckPacket.PacketsBlock(); + acks.Packets[i].ID = PendingAcks.Values[i]; + } + + SendPacket(acks, true); + + PendingAcks.Clear(); + } + } + } + + /// + /// Resend unacknowledged packets + /// + private void ResendUnacked() + { + int now = Environment.TickCount; + + lock (NeedAck) + { + foreach (Packet packet in NeedAck.Values) + { + if (now - packet.TickCount > Client.Settings.RESEND_TIMEOUT) + { + Client.DebugLog("Resending " + packet.Type.ToString() + " packet (" + packet.Header.Sequence + + "), " + (now - packet.TickCount) + "ms have passed"); + packet.Header.Resent = true; - ResentPackets++; - SendPacket(packet, false); - } - } - } - } - - /// - /// Callback handler for incomming data - /// - /// - private void OnReceivedData(IAsyncResult result) - { - Packet packet = null; - int numBytes; - - // Update the disconnect flag so this sim doesn't time out - DisconnectCandidate = false; - - #region Packet Decoding - - lock (RecvBuffer) - { - // Retrieve the incoming packet - try - { - numBytes = Connection.EndReceiveFrom(result, ref endPoint); - - int packetEnd = numBytes - 1; - packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer); - - Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); - } - catch (SocketException) - { - Client.Log(endPoint.ToString() + " socket is closed, shutting down " + this.ToString(), - Helpers.LogLevel.Info); - Network.DisconnectSim(this); - return; - } - } - - // Fail-safe check - if (packet == null) - { - Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning); - return; - } + ResentPackets++; + SendPacket(packet, false); + } + } + } + } + + /// + /// Callback handler for incomming data + /// + /// + private void OnReceivedData(IAsyncResult result) + { + Packet packet = null; + int numBytes; + + // Update the disconnect flag so this sim doesn't time out + DisconnectCandidate = false; + + #region Packet Decoding + + lock (RecvBuffer) + { + // Retrieve the incoming packet + try + { + numBytes = Connection.EndReceiveFrom(result, ref endPoint); + + int packetEnd = numBytes - 1; + packet = Packet.BuildPacket(RecvBuffer, ref packetEnd, ZeroBuffer); + + Connection.BeginReceiveFrom(RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref endPoint, ReceivedData, null); + } + catch (SocketException) + { + Client.Log(endPoint.ToString() + " socket is closed, shutting down " + this.ToString(), + Helpers.LogLevel.Info); + Network.DisconnectSim(this); + return; + } + } + + // Fail-safe check + if (packet == null) + { + Client.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning); + return; + } RecvBytes += (ulong)numBytes; RecvPackets++; - - #endregion Packet Decoding - - #region Reliable Handling - - if (packet.Header.Reliable) - { - // Queue up ACKs for resent packets - lock (PendingAcks) - { - uint sequence = (uint)packet.Header.Sequence; - if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence; - } - - // Send out ACKs if we have a lot of them - if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS) - SendAcks(); - } - - #endregion Reliable Handling - - #region Inbox Tracking - - // The Inbox doesn't serve a functional purpose any more, it's only used for debugging - lock (Inbox) - { - // Check if we already received this packet - if (Inbox.Contains(packet.Header.Sequence)) - { - Client.DebugLog("Received a duplicate " + packet.Type.ToString() + ", sequence=" + - packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") + - ", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count); - - // Avoid firing a callback twice for the same packet - return; - } - else - { - // Keep the Inbox size within a certain capacity - while (Inbox.Count >= Client.Settings.INBOX_SIZE) - { - Inbox.Dequeue(); Inbox.Dequeue(); - Inbox.Dequeue(); Inbox.Dequeue(); - } - - // Add this packet to the inbox - Inbox.Enqueue(packet.Header.Sequence); - } - } - - #endregion Inbox Tracking - - #region ACK handling - - // Handle appended ACKs - if (packet.Header.AppendedAcks) - { - lock (NeedAck) - { - for (int i = 0; i < packet.Header.AckList.Length; i++) - NeedAck.Remove(packet.Header.AckList[i]); - } - } - - // Handle PacketAck packets - if (packet.Type == PacketType.PacketAck) - { - PacketAckPacket ackPacket = (PacketAckPacket)packet; - - lock (NeedAck) - { - for (int i = 0; i < ackPacket.Packets.Length; i++) - NeedAck.Remove(ackPacket.Packets[i].ID); - } - } - - #endregion ACK handling - - #region FireCallbacks - - if (Network.Callbacks.ContainsKey(packet.Type)) - { - List callbackArray = Network.Callbacks[packet.Type]; - - // Fire any registered callbacks - for (int i = 0; i < callbackArray.Count; i++) - { - if (callbackArray[i] != null) - { - try { callbackArray[i](packet, this); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - } - - if (Network.Callbacks.ContainsKey(PacketType.Default)) - { - List callbackArray = Network.Callbacks[PacketType.Default]; - - // Fire any registered callbacks - for (int i = 0; i < callbackArray.Count; i++) - { - if (callbackArray[i] != null) - { - try { callbackArray[i](packet, this); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - } - } - } - - #endregion FireCallbacks - } - - private void AckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea) - { - SendAcks(); - ResendUnacked(); - } + + #endregion Packet Decoding - private void PingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea) - { + #region Reliable Handling + + if (packet.Header.Reliable) + { + // Queue up ACKs for resent packets + lock (PendingAcks) + { + uint sequence = (uint)packet.Header.Sequence; + if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence; + } + + // Send out ACKs if we have a lot of them + if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS) + SendAcks(); + } + + #endregion Reliable Handling + + #region Inbox Tracking + + // The Inbox doesn't serve a functional purpose any more, it's only used for debugging + lock (Inbox) + { + // Check if we already received this packet + if (Inbox.Contains(packet.Header.Sequence)) + { + Client.DebugLog("Received a duplicate " + packet.Type.ToString() + ", sequence=" + + packet.Header.Sequence + ", resent=" + ((packet.Header.Resent) ? "Yes" : "No") + + ", Inbox.Count=" + Inbox.Count + ", NeedAck.Count=" + NeedAck.Count); + + // Avoid firing a callback twice for the same packet + return; + } + else + { + // Keep the Inbox size within a certain capacity + while (Inbox.Count >= Client.Settings.INBOX_SIZE) + { + Inbox.Dequeue(); Inbox.Dequeue(); + Inbox.Dequeue(); Inbox.Dequeue(); + } + + // Add this packet to the inbox + Inbox.Enqueue(packet.Header.Sequence); + } + } + + #endregion Inbox Tracking + + #region ACK handling + + // Handle appended ACKs + if (packet.Header.AppendedAcks) + { + lock (NeedAck) + { + for (int i = 0; i < packet.Header.AckList.Length; i++) + NeedAck.Remove(packet.Header.AckList[i]); + } + } + + // Handle PacketAck packets + if (packet.Type == PacketType.PacketAck) + { + PacketAckPacket ackPacket = (PacketAckPacket)packet; + + lock (NeedAck) + { + for (int i = 0; i < ackPacket.Packets.Length; i++) + NeedAck.Remove(ackPacket.Packets[i].ID); + } + } + + #endregion ACK handling + + #region FireCallbacks + + if (Network.Callbacks.ContainsKey(packet.Type)) + { + List callbackArray = Network.Callbacks[packet.Type]; + + // Fire any registered callbacks + for (int i = 0; i < callbackArray.Count; i++) + { + if (callbackArray[i] != null) + { + try { callbackArray[i](packet, this); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + } + + if (Network.Callbacks.ContainsKey(PacketType.Default)) + { + List callbackArray = Network.Callbacks[PacketType.Default]; + + // Fire any registered callbacks + for (int i = 0; i < callbackArray.Count; i++) + { + if (callbackArray[i] != null) + { + try { callbackArray[i](packet, this); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + } + } + } + + #endregion FireCallbacks + } + + private void AckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea) + { + SendAcks(); + ResendUnacked(); + } + + private void PingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs ea) + { SendPing(); - } + } - } -} + } +} diff --git a/libsecondlife-cs/TerrainManager.cs b/libsecondlife-cs/TerrainManager.cs index 16fa7a32..09219407 100644 --- a/libsecondlife-cs/TerrainManager.cs +++ b/libsecondlife-cs/TerrainManager.cs @@ -1,905 +1,905 @@ -/* - * Copyright (c) 2007, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using libsecondlife.Packets; - -namespace libsecondlife -{ - public class TerrainManager - { - public enum LayerType : byte - { - Land = 0x4C, - Water = 0x57, - Wind = 0x37, - Cloud = 0x38 - } - - public struct GroupHeader - { - public int Stride; - public int PatchSize; - public LayerType Type; - } - - public struct PatchHeader - { - public float DCOffset; - public int Range; - public int QuantWBits; - public int PatchIDs; - public uint WordBits; - } - - public class Patch - { - public float[] Heightmap; - } - - - /// - /// - /// - /// - /// - /// - /// - /// - public delegate void LandPatchCallback(Simulator simulator, int x, int y, int width, float[] data); - - - /// - /// - /// - public event LandPatchCallback OnLandPatch; - - private const int PATCHES_PER_EDGE = 16; - private const float OO_SQRT2 = 0.7071067811865475244008443621049f; - private const int STRIDE = 264; - - // Bit packing codes - private const int ZERO_CODE = 0x0; - private const int ZERO_EOB = 0x2; - private const int POSITIVE_VALUE = 0x6; - private const int NEGATIVE_VALUE = 0x7; - private const int END_OF_PATCHES = 97; - - private SecondLife Client; - private Dictionary SimPatches = new Dictionary(); - private float[] DequantizeTable16 = new float[16 * 16]; - private float[] DequantizeTable32 = new float[32 * 32]; - private float[] CosineTable16 = new float[16 * 16]; - private float[] CosineTable32 = new float[32 * 32]; - private int[] CopyMatrix16 = new int[16 * 16]; - private int[] CopyMatrix32 = new int[32 * 32]; - - // Not used by clients - private float[] QuantizeTable16 = new float[16 * 16]; - - - /// - /// - /// - /// - public TerrainManager(SecondLife client) - { - Client = client; - - // Initialize the decompression tables - BuildDequantizeTable16(); - BuildDequantizeTable32(); - SetupCosines16(); - SetupCosines32(); - BuildCopyMatrix16(); - BuildCopyMatrix32(); - - // Not used by clients - BuildQuantizeTable16(); - - Client.Network.RegisterCallback(PacketType.LayerData, new NetworkManager.PacketCallback(LayerDataHandler)); - } - - /// - /// Retrieve the terrain height at a given coordinate - /// - /// The region that the point of interest is in - /// Sim X coordinate, valid range is from 0 to 255 - /// Sim Y coordinate, valid range is from 0 to 255 - /// The terrain height at the given point if the - /// lookup was successful, otherwise 0.0f - /// True if the lookup was successful, otherwise false - public bool TerrainHeightAtPoint(ulong regionHandle, int x, int y, out float height) - { - if (x > 0 && x < 256 && y > 0 && y < 256) - { - lock (SimPatches) - { - if (SimPatches.ContainsKey(regionHandle)) - { - int patchX = (int)Math.DivRem(x, 16, out x); - int patchY = (int)Math.DivRem(y, 16, out y); - - if (SimPatches[regionHandle][patchY * 16 + patchX] != null) - { - height = SimPatches[regionHandle][patchY * 16 + patchX].Heightmap[y * 16 + x]; - return true; - } - } - } - } - - height = 0.0f; - return false; - } - - /// - /// Creates a LayerData packet for compressed land data given a full - /// simulator heightmap and an array of indices of patches to compress - /// - /// A 256 * 256 array of floating point values - /// specifying the height at each meter in the simulator - /// Array of indexes in the 16x16 grid of patches - /// for this simulator. For example if 1 and 17 are specified, patches - /// x=1,y=0 and x=1,y=1 are sent - /// - public LayerDataPacket CreateLandPacket(float[] heightmap, int[] patches) - { - LayerDataPacket layer = new LayerDataPacket(); - layer.LayerID.Type = (byte)LayerType.Land; - - GroupHeader header = new GroupHeader(); - header.Stride = STRIDE; - header.PatchSize = 16; - header.Type = LayerType.Land; - - byte[] data = new byte[1536]; - BitPack bitpack = new BitPack(data, 0); - bitpack.PackBits(header.Stride, 16); - bitpack.PackBits(header.PatchSize, 8); - bitpack.PackBits((int)header.Type, 8); - - for (int i = 0; i < patches.Length; i++) - { - CreatePatch(bitpack, heightmap, patches[i] % 16, (patches[i] - (patches[i] % 16)) / 16); - } - - bitpack.PackBits(END_OF_PATCHES, 8); - - layer.LayerData.Data = new byte[bitpack.BytePos + 1]; - Array.Copy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1); - - return layer; - } - - /// - /// Add a patch of terrain to a BitPacker - /// - /// BitPacker to write the patch to - /// Heightmap of the simulator, must be a 256 * - /// 256 float array - /// X offset of the patch to create, valid values are - /// from 0 to 15 - /// Y offset of the patch to create, valid values are - /// from 0 to 15 - public void CreatePatch(BitPack output, float[] heightmap, int x, int y) - { - if (heightmap.Length != 256 * 256) - { - Client.Log("Invalid heightmap value of " + heightmap.Length + " passed to CreatePatch()", - Helpers.LogLevel.Error); - return; - } - - if (x < 0 || x > 15 || y < 0 || y > 15) - { - Client.Log("Invalid x or y patch offset passed to CreatePatch(), x=" + x + ", y=" + y, - Helpers.LogLevel.Error); - return; - } - - PatchHeader header = PrescanPatch(heightmap, x, y); - header.QuantWBits = 136; - header.PatchIDs = (y & 0x1F); - header.PatchIDs += (x << 5); - - // TODO: What is prequant? - int[] patch = CompressPatch(heightmap, x, y, header, 10); - int wbits = EncodePatchHeader(output, header, patch); - // TODO: What is postquant? - EncodePatch(output, patch, 0, wbits); - } - - private void BuildDequantizeTable16() - { - for (int j = 0; j < 16; j++) - { - for (int i = 0; i < 16; i++) - { - DequantizeTable16[j * 16 + i] = 1.0f + 2.0f * (float)(i + j); - } - } - } - - private void BuildDequantizeTable32() - { - for (int j = 0; j < 32; j++) - { - for (int i = 0; i < 32; i++) - { - DequantizeTable32[j * 32 + i] = 1.0f + 2.0f * (float)(i + j); - } - } - } - - private void BuildQuantizeTable16() - { - for (int j = 0; j < 16; j++) - { - for (int i = 0; i < 16; i++) - { - QuantizeTable16[j * 16 + i] = 1.0f / (1.0f + 2.0f * ((float)i + (float)j)); - } - } - } - - private void SetupCosines16() - { - const float hposz = (float)Math.PI * 0.5f / 16.0f; - - for (int u = 0; u < 16; u++) - { - for (int n = 0; n < 16; n++) - { - CosineTable16[u * 16 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz); - } - } - } - - private void SetupCosines32() - { - const float hposz = (float)Math.PI * 0.5f / 32.0f; - - for (int u = 0; u < 32; u++) - { - for (int n = 0; n < 32; n++) - { - CosineTable32[u * 32 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz); - } - } - } - - private void BuildCopyMatrix16() - { - bool diag = false; - bool right = true; - int i = 0; - int j = 0; - int count = 0; - - while (i < 16 && j < 16) - { - CopyMatrix16[j * 16 + i] = count++; - - if (!diag) - { - if (right) - { - if (i < 16 - 1) i++; - else j++; - - right = false; - diag = true; - } - else - { - if (j < 16 - 1) j++; - else i++; - - right = true; - diag = true; - } - } - else - { - if (right) - { - i++; - j--; - if (i == 16 - 1 || j == 0) diag = false; - } - else - { - i--; - j++; - if (j == 16 - 1 || i == 0) diag = false; - } - } - } - } - - private void BuildCopyMatrix32() - { - bool diag = false; - bool right = true; - int i = 0; - int j = 0; - int count = 0; - - while (i < 32 && j < 32) - { - CopyMatrix32[j * 32 + i] = count++; - - if (!diag) - { - if (right) - { - if (i < 32 - 1) i++; - else j++; - - right = false; - diag = true; - } - else - { - if (j < 32 - 1) j++; - else i++; - - right = true; - diag = true; - } - } - else - { - if (right) - { - i++; - j--; - if (i == 32 - 1 || j == 0) diag = false; - } - else - { - i--; - j++; - if (j == 32 - 1 || i == 0) diag = false; - } - } - } - } - - private PatchHeader PrescanPatch(float[] heightmap, int patchX, int patchY) - { - PatchHeader header = new PatchHeader(); - float zmax = -99999999.0f; - float zmin = 99999999.0f; - - for (int j = patchY * 16; j < (patchY + 1) * 16; j++) - { - for (int i = patchX * 16; i < (patchX + 1) * 16; i++) - { - if (heightmap[j * 256 + i] > zmax) zmax = heightmap[j * 256 + i]; - if (heightmap[j * 256 + i] < zmin) zmin = heightmap[j * 256 + i]; - } - } - - header.DCOffset = zmin; - header.Range = (int)((zmax - zmin) + 1.0f); - - return header; - } - - private PatchHeader DecodePatchHeader(BitPack bitpack) - { - PatchHeader header = new PatchHeader(); - - // Quantized word bits - header.QuantWBits = bitpack.UnpackBits(8); - if (header.QuantWBits == END_OF_PATCHES) - return header; - - // DC offset - header.DCOffset = bitpack.UnpackFloat(); - - // Range - header.Range = bitpack.UnpackBits(16); - - // Patch IDs (10 bits) - header.PatchIDs = bitpack.UnpackBits(10); - - // Word bits - header.WordBits = (uint)((header.QuantWBits & 0x0f) + 2); - - return header; - } - - /// - /// - /// - /// - /// - /// - /// wbits - private int EncodePatchHeader(BitPack output, PatchHeader header, int[] patch) - { - int temp; - int wbits = (header.QuantWBits & 0x0f) + 2; - uint maxWbits = (uint)wbits + 5; - uint minWbits = ((uint)wbits >> 1); - - wbits = (int)minWbits; - - for (int i = 0; i < patch.Length; i++) - { - temp = patch[i]; - - if (temp != 0) - { - // Get the absolute value - if (temp < 0) temp *= -1; - - for (int j = (int)maxWbits; j > (int)minWbits; j--) - { - if ((temp & (1 << j)) != 0) - { - if (j > wbits) wbits = j; - break; - } - } - } - } - - wbits += 1; - - header.QuantWBits &= 0xf0; - - if (wbits > 17 || wbits < 2) - { - Client.Log("Bits needed per word in EncodePatchHeader() are outside the allowed range", - Helpers.LogLevel.Error); - } - - header.QuantWBits |= (wbits - 2); - - output.PackBits(header.QuantWBits, 8); - output.PackFloat(header.DCOffset); - output.PackBits(header.Range, 16); - output.PackBits(header.PatchIDs, 10); - - return wbits; - } - - private void IDCTColumn16(float[] linein, float[] lineout, int column) - { - float total; - int usize; - - for (int n = 0; n < 16; n++) - { - total = OO_SQRT2 * linein[column]; - - for (int u = 1; u < 16; u++) - { - usize = u * 16; - total += linein[usize + column] * CosineTable16[usize + n]; - } - - lineout[16 * n + column] = total; - } - } - - private void IDCTColumn32(float[] linein, float[] lineout, int column) - { - float total; - int usize; - - for (int n = 0; n < 32; n++) - { - total = OO_SQRT2 * linein[column]; - - for (int u = 1; u < 32; u++) - { - usize = u * 32; - total += linein[usize + column] * CosineTable32[usize + n]; - } - - lineout[32 * n + column] = total; - } - } - - private void IDCTLine16(float[] linein, float[] lineout, int line) - { - const float oosob = 2.0f / 16.0f; - int lineSize = line * 16; - float total; - - for (int n = 0; n < 16; n++) - { - total = OO_SQRT2 * linein[lineSize]; - - for (int u = 1; u < 16; u++) - { - total += linein[lineSize + u] * CosineTable16[u * 16 + n]; - } - - lineout[lineSize + n] = total * oosob; - } - } - - private void IDCTLine32(float[] linein, float[] lineout, int line) - { - const float oosob = 2.0f / 32.0f; - int lineSize = line * 32; - float total; - - for (int n = 0; n < 32; n++) - { - total = OO_SQRT2 * linein[lineSize]; - - for (int u = 1; u < 32; u++) - { - total += linein[lineSize + u] * CosineTable32[u * 32 + n]; - } - - lineout[lineSize + n] = total * oosob; - } - } - - private void DCTLine16(float[] linein, float[] lineout, int line) - { - float total = 0.0f; - int lineSize = line * 16; - - for (int n = 0; n < 16; n++) - { - total += linein[lineSize + n]; - } - - lineout[lineSize] = OO_SQRT2 * total; - - for (int u = 1; u < 16; u++) - { - total = 0.0f; - - for (int n = 0; n < 16; n++) - { - total += linein[lineSize + n] * CosineTable16[u * 16 + n]; - } - - lineout[lineSize + u] = total; - } - } - - private void DCTColumn16(float[] linein, int[] lineout, int column) - { - float total = 0.0f; - const float oosob = 2.0f / 16.0f; - - for (int n = 0; n < 16; n++) - { - total += linein[16 * n + column]; - } - - lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]); - - for (int u = 1; u < 16; u++) - { - total = 0.0f; - - for (int n = 0; n < 16; n++) - { - total += linein[16 * n + column] * CosineTable16[u * 16 + n]; - } - - lineout[CopyMatrix16[16 * u + column]] = (int)(total * oosob * QuantizeTable16[16 * u + column]); - } - } - - private void DecodePatch(int[] patches, BitPack bitpack, PatchHeader header, int size) - { - int temp; - for (int n = 0; n < size * size; n++) - { - // ? - temp = bitpack.UnpackBits(1); - if (temp != 0) - { - // Value or EOB - temp = bitpack.UnpackBits(1); - if (temp != 0) - { - // Value - temp = bitpack.UnpackBits(1); - if (temp != 0) - { - // Negative - temp = bitpack.UnpackBits((int)header.WordBits); - patches[n] = temp * -1; - } - else - { - // Positive - temp = bitpack.UnpackBits((int)header.WordBits); - patches[n] = temp; - } - } - else - { - // Set the rest to zero - // TODO: This might not be necessary - for (int o = n; o < size * size; o++) - { - patches[o] = 0; - } - break; - } - } - else - { - patches[n] = 0; - } - } - } - - private void EncodePatch(BitPack output, int[] patch, int postquant, int wbits) - { - int temp; - bool eob; - - if (postquant > 16 * 16 || postquant < 0) - { - Client.Log("Postquant is outside the range of allowed values in EncodePatch()", Helpers.LogLevel.Error); - return; - } - - if (postquant != 0) patch[16 * 16 - postquant] = 0; - - for (int i = 0; i < 16 * 16; i++) - { - eob = false; - temp = patch[i]; - - if (temp == 0) - { - eob = true; - - for (int j = i; j < 16 * 16 - postquant; j++) - { - if (patch[j] != 0) - { - eob = false; - break; - } - } - - if (eob) - { - output.PackBits(ZERO_EOB, 2); - return; - } - else - { - output.PackBits(ZERO_CODE, 1); - } - } - else - { - if (temp < 0) - { - temp *= -1; - - if (temp > (1 << wbits)) temp = (1 << wbits); - - output.PackBits(NEGATIVE_VALUE, 3); - output.PackBits(temp, wbits); - } - else - { - if (temp > (1 << wbits)) temp = (1 << wbits); - - output.PackBits(POSITIVE_VALUE, 3); - output.PackBits(temp, wbits); - } - } - } - } - - private float[] DecompressPatch(int[] patches, PatchHeader header, GroupHeader group) - { - float[] block = new float[group.PatchSize * group.PatchSize]; - float[] output = new float[group.PatchSize * group.PatchSize]; - int prequant = (header.QuantWBits >> 4) + 2; - int quantize = 1 << prequant; - float ooq = 1.0f / (float)quantize; - float mult = ooq * (float)header.Range; - float addval = mult * (float)(1 << (prequant - 1)) + header.DCOffset; - - if (group.PatchSize == 16) - { - for (int n = 0; n < 16 * 16; n++) - { - block[n] = patches[CopyMatrix16[n]] * DequantizeTable16[n]; - } - - float[] ftemp = new float[16 * 16]; - - for (int o = 0; o < 16; o++) - IDCTColumn16(block, ftemp, o); - for (int o = 0; o < 16; o++) - IDCTLine16(ftemp, block, o); - } - else - { - for (int n = 0; n < 32 * 32; n++) - { - block[n] = patches[CopyMatrix32[n]] * DequantizeTable32[n]; - } - - Client.Log("Implement IDCTPatchLarge", Helpers.LogLevel.Error); - } - - for (int j = 0; j < block.Length; j++) - { - output[j] = block[j] * mult + addval; - } - - return output; - } - - private int[] CompressPatch(float[] heightmap, int patchX, int patchY, PatchHeader header, int prequant) - { - float[] block = new float[16 * 16]; - int wordsize = prequant; - float oozrange = 1.0f / (float)header.Range; - float range = (float)(1 << prequant); - float premult = oozrange * range; - float sub = (float)(1 << (prequant - 1)) + header.DCOffset * premult; - - header.QuantWBits = wordsize - 2; - header.QuantWBits |= (prequant - 2) << 4; - - int k = 0; - for (int j = patchY * 16; j < (patchY + 1) * 16; j++) - { - for (int i = patchX * 16; i < (patchX + 1) * 16; i++) - { - block[k++] = heightmap[j * 256 + i] * premult - sub; - } - } - - float[] ftemp = new float[16 * 16]; - int[] itemp = new int[16 * 16]; - - for (int o = 0; o < 16; o++) - DCTLine16(block, ftemp, o); - for (int o = 0; o < 16; o++) - DCTColumn16(ftemp, itemp, o); - - return itemp; - } - - private void DecompressLand(Simulator simulator, BitPack bitpack, GroupHeader group) - { - int x; - int y; - int[] patches = new int[32 * 32]; - int count = 0; - - while (true) - { - PatchHeader header = DecodePatchHeader(bitpack); - - if (header.QuantWBits == END_OF_PATCHES) - break; - - x = header.PatchIDs >> 5; - y = header.PatchIDs & 0x1F; - - if (x >= PATCHES_PER_EDGE || y >= PATCHES_PER_EDGE) - { - Client.Log("Invalid LayerData land packet, x = " + x + ", y = " + y + ", dc_offset = " + - header.DCOffset + ", range = " + header.Range + ", quant_wbits = " + header.QuantWBits + - ", patchids = " + header.PatchIDs + ", count = " + count, Helpers.LogLevel.Warning); - return; - } - - // Decode this patch - DecodePatch(patches, bitpack, header, group.PatchSize); - - // Decompress this patch - float[] heightmap = DecompressPatch(patches, header, group); - - count++; - - if (OnLandPatch != null) - { - try { OnLandPatch(simulator, x, y, group.PatchSize, heightmap); } - catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } - - if (Client.Settings.STORE_LAND_PATCHES) - { - lock (SimPatches) - { - if (!SimPatches.ContainsKey(simulator.Handle)) - SimPatches.Add(simulator.Handle, new Patch[16 * 16]); - - SimPatches[simulator.Handle][y * 16 + x] = new Patch(); - SimPatches[simulator.Handle][y * 16 + x].Heightmap = heightmap; - } - } - } - } - } - - private void DecompressWind(Simulator simulator, BitPack bitpack, GroupHeader group) - { - ; - } - - private void DecompressCloud(Simulator simulator, BitPack bitpack, GroupHeader group) - { - ; - } - - private void LayerDataHandler(Packet packet, Simulator simulator) - { - LayerDataPacket layer = (LayerDataPacket)packet; - BitPack bitpack = new BitPack(layer.LayerData.Data, 0); - GroupHeader header = new GroupHeader(); - LayerType type = (LayerType)layer.LayerID.Type; - - // Stride - header.Stride = bitpack.UnpackBits(16); - // Patch size - header.PatchSize = bitpack.UnpackBits(8); - // Layer type - header.Type = (LayerType)bitpack.UnpackBits(8); - - if (type != header.Type) - Client.DebugLog("LayerData: LayerID.Type " + type.ToString() + " does not match decoded type " + - header.Type.ToString()); - - switch (type) - { - case LayerType.Land: - if (OnLandPatch != null) DecompressLand(simulator, bitpack, header); - break; - case LayerType.Water: - Client.Log("Got a Water LayerData packet, implement me!", Helpers.LogLevel.Info); - break; - case LayerType.Wind: - DecompressWind(simulator, bitpack, header); - break; - case LayerType.Cloud: - DecompressCloud(simulator, bitpack, header); - break; - default: - Client.Log("Unrecognized LayerData type " + type.ToString(), Helpers.LogLevel.Warning); - break; - } - } - } -} +/* + * Copyright (c) 2007, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using libsecondlife.Packets; + +namespace libsecondlife +{ + public class TerrainManager + { + public enum LayerType : byte + { + Land = 0x4C, + Water = 0x57, + Wind = 0x37, + Cloud = 0x38 + } + + public struct GroupHeader + { + public int Stride; + public int PatchSize; + public LayerType Type; + } + + public struct PatchHeader + { + public float DCOffset; + public int Range; + public int QuantWBits; + public int PatchIDs; + public uint WordBits; + } + + public class Patch + { + public float[] Heightmap; + } + + + /// + /// + /// + /// + /// + /// + /// + /// + public delegate void LandPatchCallback(Simulator simulator, int x, int y, int width, float[] data); + + + /// + /// + /// + public event LandPatchCallback OnLandPatch; + + private const int PATCHES_PER_EDGE = 16; + private const float OO_SQRT2 = 0.7071067811865475244008443621049f; + private const int STRIDE = 264; + + // Bit packing codes + private const int ZERO_CODE = 0x0; + private const int ZERO_EOB = 0x2; + private const int POSITIVE_VALUE = 0x6; + private const int NEGATIVE_VALUE = 0x7; + private const int END_OF_PATCHES = 97; + + private SecondLife Client; + private Dictionary SimPatches = new Dictionary(); + private float[] DequantizeTable16 = new float[16 * 16]; + private float[] DequantizeTable32 = new float[32 * 32]; + private float[] CosineTable16 = new float[16 * 16]; + private float[] CosineTable32 = new float[32 * 32]; + private int[] CopyMatrix16 = new int[16 * 16]; + private int[] CopyMatrix32 = new int[32 * 32]; + + // Not used by clients + private float[] QuantizeTable16 = new float[16 * 16]; + + + /// + /// + /// + /// + public TerrainManager(SecondLife client) + { + Client = client; + + // Initialize the decompression tables + BuildDequantizeTable16(); + BuildDequantizeTable32(); + SetupCosines16(); + SetupCosines32(); + BuildCopyMatrix16(); + BuildCopyMatrix32(); + + // Not used by clients + BuildQuantizeTable16(); + + Client.Network.RegisterCallback(PacketType.LayerData, new NetworkManager.PacketCallback(LayerDataHandler)); + } + + /// + /// Retrieve the terrain height at a given coordinate + /// + /// The region that the point of interest is in + /// Sim X coordinate, valid range is from 0 to 255 + /// Sim Y coordinate, valid range is from 0 to 255 + /// The terrain height at the given point if the + /// lookup was successful, otherwise 0.0f + /// True if the lookup was successful, otherwise false + public bool TerrainHeightAtPoint(ulong regionHandle, int x, int y, out float height) + { + if (x > 0 && x < 256 && y > 0 && y < 256) + { + lock (SimPatches) + { + if (SimPatches.ContainsKey(regionHandle)) + { + int patchX = (int)Math.DivRem(x, 16, out x); + int patchY = (int)Math.DivRem(y, 16, out y); + + if (SimPatches[regionHandle][patchY * 16 + patchX] != null) + { + height = SimPatches[regionHandle][patchY * 16 + patchX].Heightmap[y * 16 + x]; + return true; + } + } + } + } + + height = 0.0f; + return false; + } + + /// + /// Creates a LayerData packet for compressed land data given a full + /// simulator heightmap and an array of indices of patches to compress + /// + /// A 256 * 256 array of floating point values + /// specifying the height at each meter in the simulator + /// Array of indexes in the 16x16 grid of patches + /// for this simulator. For example if 1 and 17 are specified, patches + /// x=1,y=0 and x=1,y=1 are sent + /// + public LayerDataPacket CreateLandPacket(float[] heightmap, int[] patches) + { + LayerDataPacket layer = new LayerDataPacket(); + layer.LayerID.Type = (byte)LayerType.Land; + + GroupHeader header = new GroupHeader(); + header.Stride = STRIDE; + header.PatchSize = 16; + header.Type = LayerType.Land; + + byte[] data = new byte[1536]; + BitPack bitpack = new BitPack(data, 0); + bitpack.PackBits(header.Stride, 16); + bitpack.PackBits(header.PatchSize, 8); + bitpack.PackBits((int)header.Type, 8); + + for (int i = 0; i < patches.Length; i++) + { + CreatePatch(bitpack, heightmap, patches[i] % 16, (patches[i] - (patches[i] % 16)) / 16); + } + + bitpack.PackBits(END_OF_PATCHES, 8); + + layer.LayerData.Data = new byte[bitpack.BytePos + 1]; + Array.Copy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1); + + return layer; + } + + /// + /// Add a patch of terrain to a BitPacker + /// + /// BitPacker to write the patch to + /// Heightmap of the simulator, must be a 256 * + /// 256 float array + /// X offset of the patch to create, valid values are + /// from 0 to 15 + /// Y offset of the patch to create, valid values are + /// from 0 to 15 + public void CreatePatch(BitPack output, float[] heightmap, int x, int y) + { + if (heightmap.Length != 256 * 256) + { + Client.Log("Invalid heightmap value of " + heightmap.Length + " passed to CreatePatch()", + Helpers.LogLevel.Error); + return; + } + + if (x < 0 || x > 15 || y < 0 || y > 15) + { + Client.Log("Invalid x or y patch offset passed to CreatePatch(), x=" + x + ", y=" + y, + Helpers.LogLevel.Error); + return; + } + + PatchHeader header = PrescanPatch(heightmap, x, y); + header.QuantWBits = 136; + header.PatchIDs = (y & 0x1F); + header.PatchIDs += (x << 5); + + // TODO: What is prequant? + int[] patch = CompressPatch(heightmap, x, y, header, 10); + int wbits = EncodePatchHeader(output, header, patch); + // TODO: What is postquant? + EncodePatch(output, patch, 0, wbits); + } + + private void BuildDequantizeTable16() + { + for (int j = 0; j < 16; j++) + { + for (int i = 0; i < 16; i++) + { + DequantizeTable16[j * 16 + i] = 1.0f + 2.0f * (float)(i + j); + } + } + } + + private void BuildDequantizeTable32() + { + for (int j = 0; j < 32; j++) + { + for (int i = 0; i < 32; i++) + { + DequantizeTable32[j * 32 + i] = 1.0f + 2.0f * (float)(i + j); + } + } + } + + private void BuildQuantizeTable16() + { + for (int j = 0; j < 16; j++) + { + for (int i = 0; i < 16; i++) + { + QuantizeTable16[j * 16 + i] = 1.0f / (1.0f + 2.0f * ((float)i + (float)j)); + } + } + } + + private void SetupCosines16() + { + const float hposz = (float)Math.PI * 0.5f / 16.0f; + + for (int u = 0; u < 16; u++) + { + for (int n = 0; n < 16; n++) + { + CosineTable16[u * 16 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz); + } + } + } + + private void SetupCosines32() + { + const float hposz = (float)Math.PI * 0.5f / 32.0f; + + for (int u = 0; u < 32; u++) + { + for (int n = 0; n < 32; n++) + { + CosineTable32[u * 32 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz); + } + } + } + + private void BuildCopyMatrix16() + { + bool diag = false; + bool right = true; + int i = 0; + int j = 0; + int count = 0; + + while (i < 16 && j < 16) + { + CopyMatrix16[j * 16 + i] = count++; + + if (!diag) + { + if (right) + { + if (i < 16 - 1) i++; + else j++; + + right = false; + diag = true; + } + else + { + if (j < 16 - 1) j++; + else i++; + + right = true; + diag = true; + } + } + else + { + if (right) + { + i++; + j--; + if (i == 16 - 1 || j == 0) diag = false; + } + else + { + i--; + j++; + if (j == 16 - 1 || i == 0) diag = false; + } + } + } + } + + private void BuildCopyMatrix32() + { + bool diag = false; + bool right = true; + int i = 0; + int j = 0; + int count = 0; + + while (i < 32 && j < 32) + { + CopyMatrix32[j * 32 + i] = count++; + + if (!diag) + { + if (right) + { + if (i < 32 - 1) i++; + else j++; + + right = false; + diag = true; + } + else + { + if (j < 32 - 1) j++; + else i++; + + right = true; + diag = true; + } + } + else + { + if (right) + { + i++; + j--; + if (i == 32 - 1 || j == 0) diag = false; + } + else + { + i--; + j++; + if (j == 32 - 1 || i == 0) diag = false; + } + } + } + } + + private PatchHeader PrescanPatch(float[] heightmap, int patchX, int patchY) + { + PatchHeader header = new PatchHeader(); + float zmax = -99999999.0f; + float zmin = 99999999.0f; + + for (int j = patchY * 16; j < (patchY + 1) * 16; j++) + { + for (int i = patchX * 16; i < (patchX + 1) * 16; i++) + { + if (heightmap[j * 256 + i] > zmax) zmax = heightmap[j * 256 + i]; + if (heightmap[j * 256 + i] < zmin) zmin = heightmap[j * 256 + i]; + } + } + + header.DCOffset = zmin; + header.Range = (int)((zmax - zmin) + 1.0f); + + return header; + } + + private PatchHeader DecodePatchHeader(BitPack bitpack) + { + PatchHeader header = new PatchHeader(); + + // Quantized word bits + header.QuantWBits = bitpack.UnpackBits(8); + if (header.QuantWBits == END_OF_PATCHES) + return header; + + // DC offset + header.DCOffset = bitpack.UnpackFloat(); + + // Range + header.Range = bitpack.UnpackBits(16); + + // Patch IDs (10 bits) + header.PatchIDs = bitpack.UnpackBits(10); + + // Word bits + header.WordBits = (uint)((header.QuantWBits & 0x0f) + 2); + + return header; + } + + /// + /// + /// + /// + /// + /// + /// wbits + private int EncodePatchHeader(BitPack output, PatchHeader header, int[] patch) + { + int temp; + int wbits = (header.QuantWBits & 0x0f) + 2; + uint maxWbits = (uint)wbits + 5; + uint minWbits = ((uint)wbits >> 1); + + wbits = (int)minWbits; + + for (int i = 0; i < patch.Length; i++) + { + temp = patch[i]; + + if (temp != 0) + { + // Get the absolute value + if (temp < 0) temp *= -1; + + for (int j = (int)maxWbits; j > (int)minWbits; j--) + { + if ((temp & (1 << j)) != 0) + { + if (j > wbits) wbits = j; + break; + } + } + } + } + + wbits += 1; + + header.QuantWBits &= 0xf0; + + if (wbits > 17 || wbits < 2) + { + Client.Log("Bits needed per word in EncodePatchHeader() are outside the allowed range", + Helpers.LogLevel.Error); + } + + header.QuantWBits |= (wbits - 2); + + output.PackBits(header.QuantWBits, 8); + output.PackFloat(header.DCOffset); + output.PackBits(header.Range, 16); + output.PackBits(header.PatchIDs, 10); + + return wbits; + } + + private void IDCTColumn16(float[] linein, float[] lineout, int column) + { + float total; + int usize; + + for (int n = 0; n < 16; n++) + { + total = OO_SQRT2 * linein[column]; + + for (int u = 1; u < 16; u++) + { + usize = u * 16; + total += linein[usize + column] * CosineTable16[usize + n]; + } + + lineout[16 * n + column] = total; + } + } + + private void IDCTColumn32(float[] linein, float[] lineout, int column) + { + float total; + int usize; + + for (int n = 0; n < 32; n++) + { + total = OO_SQRT2 * linein[column]; + + for (int u = 1; u < 32; u++) + { + usize = u * 32; + total += linein[usize + column] * CosineTable32[usize + n]; + } + + lineout[32 * n + column] = total; + } + } + + private void IDCTLine16(float[] linein, float[] lineout, int line) + { + const float oosob = 2.0f / 16.0f; + int lineSize = line * 16; + float total; + + for (int n = 0; n < 16; n++) + { + total = OO_SQRT2 * linein[lineSize]; + + for (int u = 1; u < 16; u++) + { + total += linein[lineSize + u] * CosineTable16[u * 16 + n]; + } + + lineout[lineSize + n] = total * oosob; + } + } + + private void IDCTLine32(float[] linein, float[] lineout, int line) + { + const float oosob = 2.0f / 32.0f; + int lineSize = line * 32; + float total; + + for (int n = 0; n < 32; n++) + { + total = OO_SQRT2 * linein[lineSize]; + + for (int u = 1; u < 32; u++) + { + total += linein[lineSize + u] * CosineTable32[u * 32 + n]; + } + + lineout[lineSize + n] = total * oosob; + } + } + + private void DCTLine16(float[] linein, float[] lineout, int line) + { + float total = 0.0f; + int lineSize = line * 16; + + for (int n = 0; n < 16; n++) + { + total += linein[lineSize + n]; + } + + lineout[lineSize] = OO_SQRT2 * total; + + for (int u = 1; u < 16; u++) + { + total = 0.0f; + + for (int n = 0; n < 16; n++) + { + total += linein[lineSize + n] * CosineTable16[u * 16 + n]; + } + + lineout[lineSize + u] = total; + } + } + + private void DCTColumn16(float[] linein, int[] lineout, int column) + { + float total = 0.0f; + const float oosob = 2.0f / 16.0f; + + for (int n = 0; n < 16; n++) + { + total += linein[16 * n + column]; + } + + lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]); + + for (int u = 1; u < 16; u++) + { + total = 0.0f; + + for (int n = 0; n < 16; n++) + { + total += linein[16 * n + column] * CosineTable16[u * 16 + n]; + } + + lineout[CopyMatrix16[16 * u + column]] = (int)(total * oosob * QuantizeTable16[16 * u + column]); + } + } + + private void DecodePatch(int[] patches, BitPack bitpack, PatchHeader header, int size) + { + int temp; + for (int n = 0; n < size * size; n++) + { + // ? + temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Value or EOB + temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Value + temp = bitpack.UnpackBits(1); + if (temp != 0) + { + // Negative + temp = bitpack.UnpackBits((int)header.WordBits); + patches[n] = temp * -1; + } + else + { + // Positive + temp = bitpack.UnpackBits((int)header.WordBits); + patches[n] = temp; + } + } + else + { + // Set the rest to zero + // TODO: This might not be necessary + for (int o = n; o < size * size; o++) + { + patches[o] = 0; + } + break; + } + } + else + { + patches[n] = 0; + } + } + } + + private void EncodePatch(BitPack output, int[] patch, int postquant, int wbits) + { + int temp; + bool eob; + + if (postquant > 16 * 16 || postquant < 0) + { + Client.Log("Postquant is outside the range of allowed values in EncodePatch()", Helpers.LogLevel.Error); + return; + } + + if (postquant != 0) patch[16 * 16 - postquant] = 0; + + for (int i = 0; i < 16 * 16; i++) + { + eob = false; + temp = patch[i]; + + if (temp == 0) + { + eob = true; + + for (int j = i; j < 16 * 16 - postquant; j++) + { + if (patch[j] != 0) + { + eob = false; + break; + } + } + + if (eob) + { + output.PackBits(ZERO_EOB, 2); + return; + } + else + { + output.PackBits(ZERO_CODE, 1); + } + } + else + { + if (temp < 0) + { + temp *= -1; + + if (temp > (1 << wbits)) temp = (1 << wbits); + + output.PackBits(NEGATIVE_VALUE, 3); + output.PackBits(temp, wbits); + } + else + { + if (temp > (1 << wbits)) temp = (1 << wbits); + + output.PackBits(POSITIVE_VALUE, 3); + output.PackBits(temp, wbits); + } + } + } + } + + private float[] DecompressPatch(int[] patches, PatchHeader header, GroupHeader group) + { + float[] block = new float[group.PatchSize * group.PatchSize]; + float[] output = new float[group.PatchSize * group.PatchSize]; + int prequant = (header.QuantWBits >> 4) + 2; + int quantize = 1 << prequant; + float ooq = 1.0f / (float)quantize; + float mult = ooq * (float)header.Range; + float addval = mult * (float)(1 << (prequant - 1)) + header.DCOffset; + + if (group.PatchSize == 16) + { + for (int n = 0; n < 16 * 16; n++) + { + block[n] = patches[CopyMatrix16[n]] * DequantizeTable16[n]; + } + + float[] ftemp = new float[16 * 16]; + + for (int o = 0; o < 16; o++) + IDCTColumn16(block, ftemp, o); + for (int o = 0; o < 16; o++) + IDCTLine16(ftemp, block, o); + } + else + { + for (int n = 0; n < 32 * 32; n++) + { + block[n] = patches[CopyMatrix32[n]] * DequantizeTable32[n]; + } + + Client.Log("Implement IDCTPatchLarge", Helpers.LogLevel.Error); + } + + for (int j = 0; j < block.Length; j++) + { + output[j] = block[j] * mult + addval; + } + + return output; + } + + private int[] CompressPatch(float[] heightmap, int patchX, int patchY, PatchHeader header, int prequant) + { + float[] block = new float[16 * 16]; + int wordsize = prequant; + float oozrange = 1.0f / (float)header.Range; + float range = (float)(1 << prequant); + float premult = oozrange * range; + float sub = (float)(1 << (prequant - 1)) + header.DCOffset * premult; + + header.QuantWBits = wordsize - 2; + header.QuantWBits |= (prequant - 2) << 4; + + int k = 0; + for (int j = patchY * 16; j < (patchY + 1) * 16; j++) + { + for (int i = patchX * 16; i < (patchX + 1) * 16; i++) + { + block[k++] = heightmap[j * 256 + i] * premult - sub; + } + } + + float[] ftemp = new float[16 * 16]; + int[] itemp = new int[16 * 16]; + + for (int o = 0; o < 16; o++) + DCTLine16(block, ftemp, o); + for (int o = 0; o < 16; o++) + DCTColumn16(ftemp, itemp, o); + + return itemp; + } + + private void DecompressLand(Simulator simulator, BitPack bitpack, GroupHeader group) + { + int x; + int y; + int[] patches = new int[32 * 32]; + int count = 0; + + while (true) + { + PatchHeader header = DecodePatchHeader(bitpack); + + if (header.QuantWBits == END_OF_PATCHES) + break; + + x = header.PatchIDs >> 5; + y = header.PatchIDs & 0x1F; + + if (x >= PATCHES_PER_EDGE || y >= PATCHES_PER_EDGE) + { + Client.Log("Invalid LayerData land packet, x = " + x + ", y = " + y + ", dc_offset = " + + header.DCOffset + ", range = " + header.Range + ", quant_wbits = " + header.QuantWBits + + ", patchids = " + header.PatchIDs + ", count = " + count, Helpers.LogLevel.Warning); + return; + } + + // Decode this patch + DecodePatch(patches, bitpack, header, group.PatchSize); + + // Decompress this patch + float[] heightmap = DecompressPatch(patches, header, group); + + count++; + + if (OnLandPatch != null) + { + try { OnLandPatch(simulator, x, y, group.PatchSize, heightmap); } + catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } + + if (Client.Settings.STORE_LAND_PATCHES) + { + lock (SimPatches) + { + if (!SimPatches.ContainsKey(simulator.Handle)) + SimPatches.Add(simulator.Handle, new Patch[16 * 16]); + + SimPatches[simulator.Handle][y * 16 + x] = new Patch(); + SimPatches[simulator.Handle][y * 16 + x].Heightmap = heightmap; + } + } + } + } + } + + private void DecompressWind(Simulator simulator, BitPack bitpack, GroupHeader group) + { + ; + } + + private void DecompressCloud(Simulator simulator, BitPack bitpack, GroupHeader group) + { + ; + } + + private void LayerDataHandler(Packet packet, Simulator simulator) + { + LayerDataPacket layer = (LayerDataPacket)packet; + BitPack bitpack = new BitPack(layer.LayerData.Data, 0); + GroupHeader header = new GroupHeader(); + LayerType type = (LayerType)layer.LayerID.Type; + + // Stride + header.Stride = bitpack.UnpackBits(16); + // Patch size + header.PatchSize = bitpack.UnpackBits(8); + // Layer type + header.Type = (LayerType)bitpack.UnpackBits(8); + + if (type != header.Type) + Client.DebugLog("LayerData: LayerID.Type " + type.ToString() + " does not match decoded type " + + header.Type.ToString()); + + switch (type) + { + case LayerType.Land: + if (OnLandPatch != null) DecompressLand(simulator, bitpack, header); + break; + case LayerType.Water: + Client.Log("Got a Water LayerData packet, implement me!", Helpers.LogLevel.Info); + break; + case LayerType.Wind: + DecompressWind(simulator, bitpack, header); + break; + case LayerType.Cloud: + DecompressCloud(simulator, bitpack, header); + break; + default: + Client.Log("Unrecognized LayerData type " + type.ToString(), Helpers.LogLevel.Warning); + break; + } + } + } +} diff --git a/libsecondlife-cs/Textures.cs b/libsecondlife-cs/Textures.cs index 9bad8847..fabfa705 100644 --- a/libsecondlife-cs/Textures.cs +++ b/libsecondlife-cs/Textures.cs @@ -1,981 +1,981 @@ -/* - * Copyright (c) 2006, Second Life Reverse Engineering Team - * All rights reserved. - * - * - Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - Neither the name of the Second Life Reverse Engineering Team nor the names - * of its contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -using System; -using System.Collections.Generic; -using System.Xml; -using System.Xml.Serialization; -using System.ComponentModel; -using System.IO; - -namespace libsecondlife -{ - public abstract partial class LLObject - { - #region Enumerations - - /// - /// The type of bump-mapping applied to a face - /// - public enum Bumpiness - { - /// - [XmlEnum("None")] - None = 0, - /// - [XmlEnum("Brightness")] - Brightness = 1, - /// - [XmlEnum("Darkness")] - Darkness = 2, - /// - [XmlEnum("Woodgrain")] - Woodgrain = 3, - /// - [XmlEnum("Bark")] - Bark = 4, - /// - [XmlEnum("Bricks")] - Bricks = 5, - /// - [XmlEnum("Checker")] - Checker = 6, - /// - [XmlEnum("Concrete")] - Concrete = 7, - /// - [XmlEnum("Crustytile")] - Crustytile = 8, - /// - [XmlEnum("Cutstone")] - Cutstone = 9, - /// - [XmlEnum("Discs")] - Discs = 10, - /// - [XmlEnum("Gravel")] - Gravel = 11, - /// - [XmlEnum("Petridish")] - Petridish = 12, - /// - [XmlEnum("Siding")] - Siding = 13, - /// - [XmlEnum("Stonetile")] - Stonetile = 14, - /// - [XmlEnum("Stucco")] - Stucco = 15, - /// - [XmlEnum("Suction")] - Suction = 16, - /// - [XmlEnum("Weave")] - Weave = 17 - } - - /// - /// The level of shininess applied to a face - /// - public enum Shininess - { - /// - [XmlEnum("None")] - None = 0, - /// - [XmlEnum("Low")] - Low = 0x40, - /// - [XmlEnum("Medium")] - Medium = 0x80, - /// - [XmlEnum("High")] - High = 0xC0 - } - - /// - /// The texture mapping style used for a face - /// - public enum Mapping - { - /// - [XmlEnum("Default")] - Default = 0, - /// - [XmlEnum("Planar")] - Planar = 2 - } - - /// - /// Flags in the TextureEntry block that describe which properties are - /// set - /// - [Flags] - public enum TextureAttributes : uint - { - /// - None = 0, - /// - TextureID = 1 << 0, - /// - RGBA = 1 << 1, - /// - RepeatU = 1 << 2, - /// - RepeatV = 1 << 3, - /// - OffsetU = 1 << 4, - /// - OffsetV = 1 << 5, - /// - Rotation = 1 << 6, - /// - Flags1 = 1 << 7, - /// - Flags2 = 1 << 8, - /// - All = 0xFFFFFFFF - } - - #endregion Enumerations - - - /// - /// Represents all of the texturable faces for an object - /// - /// Objects in Second Life have infinite faces, with each face - /// using the properties of the default face unless set otherwise. So if - /// you have a TextureEntry with a default texture uuid of X, and face 72 - /// has a texture UUID of Y, every face would be textured with X except for - /// face 72 that uses Y. In practice however, primitives utilize a maximum - /// of nine faces and avatars utilize - [Serializable] - public class TextureEntry - { - /// - [XmlElement("default")] - public TextureEntryFace DefaultTexture = null; - /// - [XmlElement("faces")] - public SerializableDictionary FaceTextures = new SerializableDictionary(); - - /// - /// Default constructor, DefaultTexture will remain null - /// - public TextureEntry() - { - } - - /// - /// Constructor that takes a default texture UUID - /// - /// Texture UUID to use as the default texture - public TextureEntry(LLUUID defaultTextureID) - { - DefaultTexture = new TextureEntryFace(null); - DefaultTexture.TextureID = defaultTextureID; - } - - /// - /// Constructor that creates the TextureEntry class from a byte array - /// - /// Byte array containing the TextureEntry field - /// Starting position of the TextureEntry field in - /// the byte array - /// Length of the TextureEntry field, in bytes - public TextureEntry(byte[] data, int pos, int length) - { - FromBytes(data, pos, length); - } - - /// - /// Returns the TextureEntryFace that is applied to the specified - /// index. If a custom texture is not set for this face that would be - /// the default texture for this TextureEntry. Do not modify the - /// returned TextureEntryFace, it will have undefined results. Use - /// CreateFace() to get a TextureEntryFace that is safe for writing - /// - /// The index number of the face to retrieve - /// A TextureEntryFace containing all the properties for that - /// face, suitable for read-only operations - public TextureEntryFace GetFace(uint index) - { - if (FaceTextures.ContainsKey(index)) - return FaceTextures[index]; - else - return DefaultTexture; - } - - /// - /// Check whether a custom face is defined for a particular index - /// - /// The index to check whether a custom face is - /// defined for - /// True if this face has it's own TextureEntryFace, otherwise - /// false - public bool FaceExists(uint index) - { - return FaceTextures.ContainsKey(index); - } - - /// - /// This will either create a new face if a custom face for the given - /// index is not defined, or return the custom face for that index if - /// it already exists - /// - /// The index number of the face to create or - /// retrieve - /// A TextureEntryFace containing all the properties for that - /// face - public TextureEntryFace CreateFace(uint index) - { - if (!FaceTextures.ContainsKey(index)) - FaceTextures[index] = new TextureEntryFace(this.DefaultTexture); - - return FaceTextures[index]; - } - - /// - /// - /// - /// - public byte[] ToBytes() - { - if (DefaultTexture == null) - { - return new byte[0]; - } - - MemoryStream memStream = new MemoryStream(); - BinaryWriter binWriter = new BinaryWriter(memStream); - - Dictionary TextureIDs = new Dictionary(); - Dictionary RGBAs = new Dictionary(); - Dictionary RepeatUs = new Dictionary(); - Dictionary RepeatVs = new Dictionary(); - Dictionary OffsetUs = new Dictionary(); - Dictionary OffsetVs = new Dictionary(); - Dictionary Rotations = new Dictionary(); - Dictionary Flag1s = new Dictionary(); - Dictionary Flag2s = new Dictionary(); - foreach (KeyValuePair face in FaceTextures) - { - if (face.Value.TextureID != DefaultTexture.TextureID) - { - if (TextureIDs.ContainsKey(face.Value.TextureID)) - TextureIDs[face.Value.TextureID] |= (uint)(1 << (int)face.Key); - else - TextureIDs[face.Value.TextureID] = (uint)(1 << (int)face.Key); - } - - if (face.Value.RGBA != DefaultTexture.RGBA) - { - if (RGBAs.ContainsKey(face.Value.RGBA)) - RGBAs[face.Value.RGBA] |= (uint)(1 << (int)face.Key); - else - RGBAs[face.Value.RGBA] = (uint)(1 << (int)face.Key); - } - - short value; - short defaultValue; - - value = RepeatShort(face.Value.RepeatU); - defaultValue = RepeatShort(DefaultTexture.RepeatU); - if (value != defaultValue) - { - if (RepeatUs.ContainsKey(value)) - RepeatUs[value] |= (uint)(1 << (int)face.Key); - else - RepeatUs[value] = (uint)(1 << (int)face.Key); - } - - value = RepeatShort(face.Value.RepeatV); - defaultValue = RepeatShort(DefaultTexture.RepeatV); - if (value != defaultValue) - { - if (RepeatVs.ContainsKey(value)) - RepeatVs[value] |= (uint)(1 << (int)face.Key); - else - RepeatVs[value] = (uint)(1 << (int)face.Key); - } - - value = OffsetShort(face.Value.OffsetU); - defaultValue = OffsetShort(DefaultTexture.OffsetU); - if (value != defaultValue) - { - if (OffsetUs.ContainsKey(value)) - OffsetUs[value] |= (uint)(1 << (int)face.Key); - else - OffsetUs[value] = (uint)(1 << (int)face.Key); - } - - value = OffsetShort(face.Value.OffsetV); - defaultValue = OffsetShort(DefaultTexture.OffsetV); - if (value != defaultValue) - { - if (OffsetVs.ContainsKey(value)) - OffsetVs[value] |= (uint)(1 << (int)face.Key); - else - OffsetVs[value] = (uint)(1 << (int)face.Key); - } - - value = RotationShort(face.Value.Rotation); - defaultValue = RotationShort(DefaultTexture.Rotation); - if (value != defaultValue) - { - if (Rotations.ContainsKey(value)) - Rotations[value] |= (uint)(1 << (int)face.Key); - else - Rotations[value] = (uint)(1 << (int)face.Key); - } - - if (face.Value.Flags1 != DefaultTexture.Flags1) - { - if (Flag1s.ContainsKey(face.Value.Flags1)) - Flag1s[face.Value.Flags1] |= (uint)(1 << (int)face.Key); - else - Flag1s[face.Value.Flags1] = (uint)(1 << (int)face.Key); - } - - if (face.Value.Flags2 != DefaultTexture.Flags2) - { - if (Flag2s.ContainsKey(face.Value.Flags2)) - Flag2s[face.Value.Flags2] |= (uint)(1 << (int)face.Key); - else - Flag2s[face.Value.Flags2] = (uint)(1 << (int)face.Key); - } - } - - if (DefaultTexture.TextureID != null) - binWriter.Write(DefaultTexture.TextureID.Data); - else - binWriter.Write(LLUUID.Zero.Data); - foreach (KeyValuePair kv in TextureIDs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key.Data); - } - - binWriter.Write((byte)0); - binWriter.Write(DefaultTexture.RGBA); - foreach (KeyValuePair kv in RGBAs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(RepeatShort(DefaultTexture.RepeatU)); - foreach (KeyValuePair kv in RepeatUs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(RepeatShort(DefaultTexture.RepeatV)); - foreach (KeyValuePair kv in RepeatVs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(OffsetShort(DefaultTexture.OffsetU)); - foreach (KeyValuePair kv in OffsetUs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(OffsetShort(DefaultTexture.OffsetV)); - foreach (KeyValuePair kv in OffsetVs) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(RotationShort(DefaultTexture.Rotation)); - foreach (KeyValuePair kv in Rotations) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(DefaultTexture.Flags1); - foreach (KeyValuePair kv in Flag1s) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - binWriter.Write((byte)0); - binWriter.Write(DefaultTexture.Flags2); - foreach (KeyValuePair kv in Flag2s) - { - binWriter.Write(GetFaceBitfieldBytes(kv.Value)); - binWriter.Write(kv.Key); - } - - return memStream.ToArray(); - } - - private byte[] GetFaceBitfieldBytes(uint bitfield) - { - int byteLength = 0; - uint tmpBitfield = bitfield; - while (tmpBitfield != 0) - { - tmpBitfield >>= 7; - byteLength++; - } - - if (byteLength == 0) - return new byte[1] { 0 }; - - byte[] bytes = new byte[byteLength]; - for (int i = 0; i < byteLength; i++) - { - bytes[i] = (byte)((bitfield >> (7 * (byteLength - i - 1))) & 0x7F); - if (i < byteLength - 1) - bytes[i] |= 0x80; - } - return bytes; - } - - private bool ReadFaceBitfield(byte[] data, ref int pos, ref uint faceBits, ref uint bitfieldSize) - { - faceBits = 0; - bitfieldSize = 0; - - if (pos >= data.Length) - return false; - - byte b = 0; - do - { - b = data[pos]; - faceBits = (faceBits << 7) | (uint)(b & 0x7F); - bitfieldSize += 7; - pos++; - } - while ((b & 0x80) != 0); - - return (faceBits != 0); - } - - private float DequantizeSigned(byte[] byteArray, int pos, float upper) - { - short value = (short)(byteArray[pos] | (byteArray[pos + 1] << 8)); - float QV = (float)value; - float QF = upper / 32767.0f; - return (float)(QV * QF); - } - - private short QuantizeSigned(float f, float upper) - { - float QF = 32767.0F / upper; - return (short)(f * QF); - } - - private short RepeatShort(float value) - { - return QuantizeSigned(value - 1.0f, 101.0f); - } - - private short OffsetShort(float value) - { - return QuantizeSigned(value, 1.0f); - } - - private short RotationShort(float value) - { - return QuantizeSigned(value, 359.995f); - } - - private float RepeatFloat(byte[] data, int pos) - { - return DequantizeSigned(data, pos, 101.0f) + 1.0f; - } - - private float OffsetFloat(byte[] data, int pos) - { - return DequantizeSigned(data, pos, 1.0f); - } - - private float RotationFloat(byte[] data, int pos) - { - return DequantizeSigned(data, pos, 359.995f); - } - - private void FromBytes(byte[] data, int pos, int length) - { - FaceTextures = new SerializableDictionary(); - DefaultTexture = new TextureEntryFace(null); - - if (length <= 0) - return; // No TextureEntry to process - - uint BitfieldSize = 0; - uint faceBits = 0; - int i = pos; - - //Read TextureID --------------------------------------- - DefaultTexture.TextureID = new LLUUID(data, i); - i += 16; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - LLUUID tmpUUID = new LLUUID(data, i); - i += 16; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).TextureID = tmpUUID; - } - //Read RGBA -------------------------------------------- - DefaultTexture.RGBA = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); - i += 4; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - uint tmpUint = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); - i += 4; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).RGBA = tmpUint; - } - //Read RepeatU ----------------------------------------- - DefaultTexture.RepeatU = RepeatFloat(data, i); - i += 2; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - float tmpFloat = RepeatFloat(data, i); - i += 2; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).RepeatU = tmpFloat; - } - //Read RepeatV ----------------------------------------- - DefaultTexture.RepeatV = RepeatFloat(data, i); - i += 2; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - float tmpFloat = RepeatFloat(data, i); - i += 2; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).RepeatV = tmpFloat; - } - //Read OffsetU ----------------------------------------- - DefaultTexture.OffsetU = OffsetFloat(data, i); - i += 2; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - float tmpFloat = OffsetFloat(data, i); - i += 2; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).OffsetU = tmpFloat; - } - //Read OffsetV ----------------------------------------- - DefaultTexture.OffsetV = OffsetFloat(data, i); - i += 2; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - float tmpFloat = OffsetFloat(data, i); - i += 2; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).OffsetV = tmpFloat; - } - //Read Rotation ---------------------------------------- - DefaultTexture.Rotation = RotationFloat(data, i); - i += 2; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - float tmpFloat = RotationFloat(data, i); - i += 2; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).Rotation = tmpFloat; - } - //Read Flags1 ------------------------------------------ - DefaultTexture.Flags1 = data[i]; - i++; - - while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - byte tmpByte = data[i]; - i++; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).Flags1 = tmpByte; - } - //Read Flags2 ------------------------------------------ - DefaultTexture.Flags2 = data[i]; - i++; - - while (i - pos < length && ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) - { - byte tmpByte = data[i]; - i++; - - for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) - if ((faceBits & bit) != 0) - CreateFace(face).Flags2 = tmpByte; - } - } - } - - /// - /// A single textured face. Don't instantiate this class yourself, use the - /// methods in TextureEntry - /// - [Serializable] - public class TextureEntryFace - { - [XmlAttribute("rgba")] - private uint rgba; - [XmlAttribute("repeatu")] - private float repeatU = 1.0f; - [XmlAttribute("repeatv")] - private float repeatV = 1.0f; - [XmlAttribute("offsetu")] - private float offsetU; - [XmlAttribute("offsetv")] - private float offsetV; - [XmlAttribute("rotation")] - private float rotation; - [XmlAttribute("flags1")] - private byte flags1; - [XmlAttribute("flags2")] - private byte flags2; - [XmlAttribute("textureattributes")] - private TextureAttributes hasAttribute; - [XmlText] - private LLUUID textureID; - [XmlElement("defaulttexture")] - private TextureEntryFace DefaultTexture = null; - - ////////////////////// - ///// Properties ///// - ////////////////////// - - /// - [XmlAttribute("rgba")] - public uint RGBA - { - get - { - if ((hasAttribute & TextureAttributes.RGBA) != 0) - return rgba; - else - return DefaultTexture.rgba; - } - set - { - rgba = value; - hasAttribute |= TextureAttributes.RGBA; - } - } - - /// - [XmlAttribute("repeatu")] - public float RepeatU - { - get - { - if ((hasAttribute & TextureAttributes.RepeatU) != 0) - return repeatU; - else - return DefaultTexture.repeatU; - } - set - { - repeatU = value; - hasAttribute |= TextureAttributes.RepeatU; - } - } - - /// - [XmlAttribute("repeatv")] - public float RepeatV - { - get - { - if ((hasAttribute & TextureAttributes.RepeatV) != 0) - return repeatV; - else - return DefaultTexture.repeatV; - } - set - { - repeatV = value; - hasAttribute |= TextureAttributes.RepeatV; - } - } - - /// - [XmlAttribute("offsetu")] - public float OffsetU - { - get - { - if ((hasAttribute & TextureAttributes.OffsetU) != 0) - return offsetU; - else - return DefaultTexture.offsetU; - } - set - { - offsetU = value; - hasAttribute |= TextureAttributes.OffsetU; - } - } - - /// - [XmlAttribute("offsetv")] - public float OffsetV - { - get - { - if ((hasAttribute & TextureAttributes.OffsetV) != 0) - return offsetV; - else - return DefaultTexture.offsetV; - } - set - { - offsetV = value; - hasAttribute |= TextureAttributes.OffsetV; - } - } - - /// - [XmlAttribute("rotation")] - public float Rotation - { - get - { - if ((hasAttribute & TextureAttributes.Rotation) != 0) - return rotation; - else - return DefaultTexture.rotation; - } - set - { - rotation = value; - hasAttribute |= TextureAttributes.Rotation; - } - } - - /// - [XmlAttribute("flags1")] - public byte Flags1 - { - get - { - if ((hasAttribute & TextureAttributes.Flags1) != 0) - return flags1; - else - return DefaultTexture.flags1; - } - set - { - flags1 = value; - hasAttribute |= TextureAttributes.Flags1; - } - } - - /// - [XmlAttribute("flags2")] - public byte Flags2 - { - get - { - if ((hasAttribute & TextureAttributes.Flags2) != 0) - return flags2; - else - return DefaultTexture.flags2; - } - set - { - flags2 = value; - hasAttribute |= TextureAttributes.Flags2; - } - } - - /// - [XmlElement("id")] - public LLUUID TextureID - { - get - { - if ((hasAttribute & TextureAttributes.TextureID) != 0) - return textureID; - else - return DefaultTexture.textureID; - } - set - { - textureID = value; - hasAttribute |= TextureAttributes.TextureID; - } - } - - ///////////////////////////// - ///// End of properties ///// - ///////////////////////////// - - /// - /// - /// - public TextureEntryFace() - { - } - - /// - /// Contains the definition for individual faces - /// - /// - public TextureEntryFace(TextureEntryFace defaultTexture) - { - DefaultTexture = defaultTexture; - if (DefaultTexture == null) - hasAttribute = TextureAttributes.All; - else - hasAttribute = TextureAttributes.None; - } - - public void ToXml(XmlWriter xmlWriter) - { - XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); - ns.Add("", ""); - XmlSerializer serializer = new XmlSerializer(typeof(TextureEntryFace)); - serializer.Serialize(xmlWriter, this, ns); - } - - } - - /// - /// Controls the texture animation of a particular prim - /// - [Serializable] - public class TextureAnimation - { - /// - [XmlAttribute("flags"), DefaultValue(0)] - public uint Flags; - /// - [XmlAttribute("face"), DefaultValue(0)] - public uint Face; - /// - [XmlAttribute("sizex"), DefaultValue(0)] - public uint SizeX; - /// - [XmlAttribute("sizey"), DefaultValue(0)] - public uint SizeY; - /// - [XmlAttribute("start"), DefaultValue(0)] - public float Start; - /// - [XmlAttribute("length"), DefaultValue(0)] - public float Length; - /// - [XmlAttribute("rate"), DefaultValue(0)] - public float Rate; - - /// - /// Default constructor - /// - public TextureAnimation() - { - } - - /// - /// - /// - /// - /// - public TextureAnimation(byte[] data, int pos) - { - FromBytes(data, pos); - } - - /// - /// - /// - /// - public byte[] GetBytes() - { - byte[] bytes = new byte[0]; - // FIXME: Finish TextureAnimation GetBytes() function - return bytes; - } - - private void FromBytes(byte[] data, int pos) - { - int i = pos; - - if (data.Length == 0) - return; - - Flags = (uint)data[i++]; - Face = (uint)data[i++]; - SizeX = (uint)data[i++]; - SizeY = (uint)data[i++]; - - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(data, i, 4); - Array.Reverse(data, i + 4, 4); - Array.Reverse(data, i + 8, 4); - } - - Start = BitConverter.ToSingle(data, i); - Length = BitConverter.ToSingle(data, i + 4); - Rate = BitConverter.ToSingle(data, i + 8); - } - } - } -} +/* + * Copyright (c) 2006, Second Life Reverse Engineering Team + * All rights reserved. + * + * - Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * - Neither the name of the Second Life Reverse Engineering Team nor the names + * of its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.Serialization; +using System.ComponentModel; +using System.IO; + +namespace libsecondlife +{ + public abstract partial class LLObject + { + #region Enumerations + + /// + /// The type of bump-mapping applied to a face + /// + public enum Bumpiness + { + /// + [XmlEnum("None")] + None = 0, + /// + [XmlEnum("Brightness")] + Brightness = 1, + /// + [XmlEnum("Darkness")] + Darkness = 2, + /// + [XmlEnum("Woodgrain")] + Woodgrain = 3, + /// + [XmlEnum("Bark")] + Bark = 4, + /// + [XmlEnum("Bricks")] + Bricks = 5, + /// + [XmlEnum("Checker")] + Checker = 6, + /// + [XmlEnum("Concrete")] + Concrete = 7, + /// + [XmlEnum("Crustytile")] + Crustytile = 8, + /// + [XmlEnum("Cutstone")] + Cutstone = 9, + /// + [XmlEnum("Discs")] + Discs = 10, + /// + [XmlEnum("Gravel")] + Gravel = 11, + /// + [XmlEnum("Petridish")] + Petridish = 12, + /// + [XmlEnum("Siding")] + Siding = 13, + /// + [XmlEnum("Stonetile")] + Stonetile = 14, + /// + [XmlEnum("Stucco")] + Stucco = 15, + /// + [XmlEnum("Suction")] + Suction = 16, + /// + [XmlEnum("Weave")] + Weave = 17 + } + + /// + /// The level of shininess applied to a face + /// + public enum Shininess + { + /// + [XmlEnum("None")] + None = 0, + /// + [XmlEnum("Low")] + Low = 0x40, + /// + [XmlEnum("Medium")] + Medium = 0x80, + /// + [XmlEnum("High")] + High = 0xC0 + } + + /// + /// The texture mapping style used for a face + /// + public enum Mapping + { + /// + [XmlEnum("Default")] + Default = 0, + /// + [XmlEnum("Planar")] + Planar = 2 + } + + /// + /// Flags in the TextureEntry block that describe which properties are + /// set + /// + [Flags] + public enum TextureAttributes : uint + { + /// + None = 0, + /// + TextureID = 1 << 0, + /// + RGBA = 1 << 1, + /// + RepeatU = 1 << 2, + /// + RepeatV = 1 << 3, + /// + OffsetU = 1 << 4, + /// + OffsetV = 1 << 5, + /// + Rotation = 1 << 6, + /// + Flags1 = 1 << 7, + /// + Flags2 = 1 << 8, + /// + All = 0xFFFFFFFF + } + + #endregion Enumerations + + + /// + /// Represents all of the texturable faces for an object + /// + /// Objects in Second Life have infinite faces, with each face + /// using the properties of the default face unless set otherwise. So if + /// you have a TextureEntry with a default texture uuid of X, and face 72 + /// has a texture UUID of Y, every face would be textured with X except for + /// face 72 that uses Y. In practice however, primitives utilize a maximum + /// of nine faces and avatars utilize + [Serializable] + public class TextureEntry + { + /// + [XmlElement("default")] + public TextureEntryFace DefaultTexture = null; + /// + [XmlElement("faces")] + public SerializableDictionary FaceTextures = new SerializableDictionary(); + + /// + /// Default constructor, DefaultTexture will remain null + /// + public TextureEntry() + { + } + + /// + /// Constructor that takes a default texture UUID + /// + /// Texture UUID to use as the default texture + public TextureEntry(LLUUID defaultTextureID) + { + DefaultTexture = new TextureEntryFace(null); + DefaultTexture.TextureID = defaultTextureID; + } + + /// + /// Constructor that creates the TextureEntry class from a byte array + /// + /// Byte array containing the TextureEntry field + /// Starting position of the TextureEntry field in + /// the byte array + /// Length of the TextureEntry field, in bytes + public TextureEntry(byte[] data, int pos, int length) + { + FromBytes(data, pos, length); + } + + /// + /// Returns the TextureEntryFace that is applied to the specified + /// index. If a custom texture is not set for this face that would be + /// the default texture for this TextureEntry. Do not modify the + /// returned TextureEntryFace, it will have undefined results. Use + /// CreateFace() to get a TextureEntryFace that is safe for writing + /// + /// The index number of the face to retrieve + /// A TextureEntryFace containing all the properties for that + /// face, suitable for read-only operations + public TextureEntryFace GetFace(uint index) + { + if (FaceTextures.ContainsKey(index)) + return FaceTextures[index]; + else + return DefaultTexture; + } + + /// + /// Check whether a custom face is defined for a particular index + /// + /// The index to check whether a custom face is + /// defined for + /// True if this face has it's own TextureEntryFace, otherwise + /// false + public bool FaceExists(uint index) + { + return FaceTextures.ContainsKey(index); + } + + /// + /// This will either create a new face if a custom face for the given + /// index is not defined, or return the custom face for that index if + /// it already exists + /// + /// The index number of the face to create or + /// retrieve + /// A TextureEntryFace containing all the properties for that + /// face + public TextureEntryFace CreateFace(uint index) + { + if (!FaceTextures.ContainsKey(index)) + FaceTextures[index] = new TextureEntryFace(this.DefaultTexture); + + return FaceTextures[index]; + } + + /// + /// + /// + /// + public byte[] ToBytes() + { + if (DefaultTexture == null) + { + return new byte[0]; + } + + MemoryStream memStream = new MemoryStream(); + BinaryWriter binWriter = new BinaryWriter(memStream); + + Dictionary TextureIDs = new Dictionary(); + Dictionary RGBAs = new Dictionary(); + Dictionary RepeatUs = new Dictionary(); + Dictionary RepeatVs = new Dictionary(); + Dictionary OffsetUs = new Dictionary(); + Dictionary OffsetVs = new Dictionary(); + Dictionary Rotations = new Dictionary(); + Dictionary Flag1s = new Dictionary(); + Dictionary Flag2s = new Dictionary(); + foreach (KeyValuePair face in FaceTextures) + { + if (face.Value.TextureID != DefaultTexture.TextureID) + { + if (TextureIDs.ContainsKey(face.Value.TextureID)) + TextureIDs[face.Value.TextureID] |= (uint)(1 << (int)face.Key); + else + TextureIDs[face.Value.TextureID] = (uint)(1 << (int)face.Key); + } + + if (face.Value.RGBA != DefaultTexture.RGBA) + { + if (RGBAs.ContainsKey(face.Value.RGBA)) + RGBAs[face.Value.RGBA] |= (uint)(1 << (int)face.Key); + else + RGBAs[face.Value.RGBA] = (uint)(1 << (int)face.Key); + } + + short value; + short defaultValue; + + value = RepeatShort(face.Value.RepeatU); + defaultValue = RepeatShort(DefaultTexture.RepeatU); + if (value != defaultValue) + { + if (RepeatUs.ContainsKey(value)) + RepeatUs[value] |= (uint)(1 << (int)face.Key); + else + RepeatUs[value] = (uint)(1 << (int)face.Key); + } + + value = RepeatShort(face.Value.RepeatV); + defaultValue = RepeatShort(DefaultTexture.RepeatV); + if (value != defaultValue) + { + if (RepeatVs.ContainsKey(value)) + RepeatVs[value] |= (uint)(1 << (int)face.Key); + else + RepeatVs[value] = (uint)(1 << (int)face.Key); + } + + value = OffsetShort(face.Value.OffsetU); + defaultValue = OffsetShort(DefaultTexture.OffsetU); + if (value != defaultValue) + { + if (OffsetUs.ContainsKey(value)) + OffsetUs[value] |= (uint)(1 << (int)face.Key); + else + OffsetUs[value] = (uint)(1 << (int)face.Key); + } + + value = OffsetShort(face.Value.OffsetV); + defaultValue = OffsetShort(DefaultTexture.OffsetV); + if (value != defaultValue) + { + if (OffsetVs.ContainsKey(value)) + OffsetVs[value] |= (uint)(1 << (int)face.Key); + else + OffsetVs[value] = (uint)(1 << (int)face.Key); + } + + value = RotationShort(face.Value.Rotation); + defaultValue = RotationShort(DefaultTexture.Rotation); + if (value != defaultValue) + { + if (Rotations.ContainsKey(value)) + Rotations[value] |= (uint)(1 << (int)face.Key); + else + Rotations[value] = (uint)(1 << (int)face.Key); + } + + if (face.Value.Flags1 != DefaultTexture.Flags1) + { + if (Flag1s.ContainsKey(face.Value.Flags1)) + Flag1s[face.Value.Flags1] |= (uint)(1 << (int)face.Key); + else + Flag1s[face.Value.Flags1] = (uint)(1 << (int)face.Key); + } + + if (face.Value.Flags2 != DefaultTexture.Flags2) + { + if (Flag2s.ContainsKey(face.Value.Flags2)) + Flag2s[face.Value.Flags2] |= (uint)(1 << (int)face.Key); + else + Flag2s[face.Value.Flags2] = (uint)(1 << (int)face.Key); + } + } + + if (DefaultTexture.TextureID != null) + binWriter.Write(DefaultTexture.TextureID.Data); + else + binWriter.Write(LLUUID.Zero.Data); + foreach (KeyValuePair kv in TextureIDs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key.Data); + } + + binWriter.Write((byte)0); + binWriter.Write(DefaultTexture.RGBA); + foreach (KeyValuePair kv in RGBAs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(RepeatShort(DefaultTexture.RepeatU)); + foreach (KeyValuePair kv in RepeatUs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(RepeatShort(DefaultTexture.RepeatV)); + foreach (KeyValuePair kv in RepeatVs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(OffsetShort(DefaultTexture.OffsetU)); + foreach (KeyValuePair kv in OffsetUs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(OffsetShort(DefaultTexture.OffsetV)); + foreach (KeyValuePair kv in OffsetVs) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(RotationShort(DefaultTexture.Rotation)); + foreach (KeyValuePair kv in Rotations) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(DefaultTexture.Flags1); + foreach (KeyValuePair kv in Flag1s) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + binWriter.Write((byte)0); + binWriter.Write(DefaultTexture.Flags2); + foreach (KeyValuePair kv in Flag2s) + { + binWriter.Write(GetFaceBitfieldBytes(kv.Value)); + binWriter.Write(kv.Key); + } + + return memStream.ToArray(); + } + + private byte[] GetFaceBitfieldBytes(uint bitfield) + { + int byteLength = 0; + uint tmpBitfield = bitfield; + while (tmpBitfield != 0) + { + tmpBitfield >>= 7; + byteLength++; + } + + if (byteLength == 0) + return new byte[1] { 0 }; + + byte[] bytes = new byte[byteLength]; + for (int i = 0; i < byteLength; i++) + { + bytes[i] = (byte)((bitfield >> (7 * (byteLength - i - 1))) & 0x7F); + if (i < byteLength - 1) + bytes[i] |= 0x80; + } + return bytes; + } + + private bool ReadFaceBitfield(byte[] data, ref int pos, ref uint faceBits, ref uint bitfieldSize) + { + faceBits = 0; + bitfieldSize = 0; + + if (pos >= data.Length) + return false; + + byte b = 0; + do + { + b = data[pos]; + faceBits = (faceBits << 7) | (uint)(b & 0x7F); + bitfieldSize += 7; + pos++; + } + while ((b & 0x80) != 0); + + return (faceBits != 0); + } + + private float DequantizeSigned(byte[] byteArray, int pos, float upper) + { + short value = (short)(byteArray[pos] | (byteArray[pos + 1] << 8)); + float QV = (float)value; + float QF = upper / 32767.0f; + return (float)(QV * QF); + } + + private short QuantizeSigned(float f, float upper) + { + float QF = 32767.0F / upper; + return (short)(f * QF); + } + + private short RepeatShort(float value) + { + return QuantizeSigned(value - 1.0f, 101.0f); + } + + private short OffsetShort(float value) + { + return QuantizeSigned(value, 1.0f); + } + + private short RotationShort(float value) + { + return QuantizeSigned(value, 359.995f); + } + + private float RepeatFloat(byte[] data, int pos) + { + return DequantizeSigned(data, pos, 101.0f) + 1.0f; + } + + private float OffsetFloat(byte[] data, int pos) + { + return DequantizeSigned(data, pos, 1.0f); + } + + private float RotationFloat(byte[] data, int pos) + { + return DequantizeSigned(data, pos, 359.995f); + } + + private void FromBytes(byte[] data, int pos, int length) + { + FaceTextures = new SerializableDictionary(); + DefaultTexture = new TextureEntryFace(null); + + if (length <= 0) + return; // No TextureEntry to process + + uint BitfieldSize = 0; + uint faceBits = 0; + int i = pos; + + //Read TextureID --------------------------------------- + DefaultTexture.TextureID = new LLUUID(data, i); + i += 16; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + LLUUID tmpUUID = new LLUUID(data, i); + i += 16; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).TextureID = tmpUUID; + } + //Read RGBA -------------------------------------------- + DefaultTexture.RGBA = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); + i += 4; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + uint tmpUint = (uint)(data[i] + (data[i + 1] << 8) + (data[i + 2] << 16) + (data[i + 3] << 24)); + i += 4; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).RGBA = tmpUint; + } + //Read RepeatU ----------------------------------------- + DefaultTexture.RepeatU = RepeatFloat(data, i); + i += 2; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + float tmpFloat = RepeatFloat(data, i); + i += 2; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).RepeatU = tmpFloat; + } + //Read RepeatV ----------------------------------------- + DefaultTexture.RepeatV = RepeatFloat(data, i); + i += 2; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + float tmpFloat = RepeatFloat(data, i); + i += 2; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).RepeatV = tmpFloat; + } + //Read OffsetU ----------------------------------------- + DefaultTexture.OffsetU = OffsetFloat(data, i); + i += 2; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + float tmpFloat = OffsetFloat(data, i); + i += 2; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).OffsetU = tmpFloat; + } + //Read OffsetV ----------------------------------------- + DefaultTexture.OffsetV = OffsetFloat(data, i); + i += 2; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + float tmpFloat = OffsetFloat(data, i); + i += 2; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).OffsetV = tmpFloat; + } + //Read Rotation ---------------------------------------- + DefaultTexture.Rotation = RotationFloat(data, i); + i += 2; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + float tmpFloat = RotationFloat(data, i); + i += 2; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).Rotation = tmpFloat; + } + //Read Flags1 ------------------------------------------ + DefaultTexture.Flags1 = data[i]; + i++; + + while (ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + byte tmpByte = data[i]; + i++; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).Flags1 = tmpByte; + } + //Read Flags2 ------------------------------------------ + DefaultTexture.Flags2 = data[i]; + i++; + + while (i - pos < length && ReadFaceBitfield(data, ref i, ref faceBits, ref BitfieldSize)) + { + byte tmpByte = data[i]; + i++; + + for (uint face = 0, bit = 1; face < BitfieldSize; face++, bit <<= 1) + if ((faceBits & bit) != 0) + CreateFace(face).Flags2 = tmpByte; + } + } + } + + /// + /// A single textured face. Don't instantiate this class yourself, use the + /// methods in TextureEntry + /// + [Serializable] + public class TextureEntryFace + { + [XmlAttribute("rgba")] + private uint rgba; + [XmlAttribute("repeatu")] + private float repeatU = 1.0f; + [XmlAttribute("repeatv")] + private float repeatV = 1.0f; + [XmlAttribute("offsetu")] + private float offsetU; + [XmlAttribute("offsetv")] + private float offsetV; + [XmlAttribute("rotation")] + private float rotation; + [XmlAttribute("flags1")] + private byte flags1; + [XmlAttribute("flags2")] + private byte flags2; + [XmlAttribute("textureattributes")] + private TextureAttributes hasAttribute; + [XmlText] + private LLUUID textureID; + [XmlElement("defaulttexture")] + private TextureEntryFace DefaultTexture = null; + + ////////////////////// + ///// Properties ///// + ////////////////////// + + /// + [XmlAttribute("rgba")] + public uint RGBA + { + get + { + if ((hasAttribute & TextureAttributes.RGBA) != 0) + return rgba; + else + return DefaultTexture.rgba; + } + set + { + rgba = value; + hasAttribute |= TextureAttributes.RGBA; + } + } + + /// + [XmlAttribute("repeatu")] + public float RepeatU + { + get + { + if ((hasAttribute & TextureAttributes.RepeatU) != 0) + return repeatU; + else + return DefaultTexture.repeatU; + } + set + { + repeatU = value; + hasAttribute |= TextureAttributes.RepeatU; + } + } + + /// + [XmlAttribute("repeatv")] + public float RepeatV + { + get + { + if ((hasAttribute & TextureAttributes.RepeatV) != 0) + return repeatV; + else + return DefaultTexture.repeatV; + } + set + { + repeatV = value; + hasAttribute |= TextureAttributes.RepeatV; + } + } + + /// + [XmlAttribute("offsetu")] + public float OffsetU + { + get + { + if ((hasAttribute & TextureAttributes.OffsetU) != 0) + return offsetU; + else + return DefaultTexture.offsetU; + } + set + { + offsetU = value; + hasAttribute |= TextureAttributes.OffsetU; + } + } + + /// + [XmlAttribute("offsetv")] + public float OffsetV + { + get + { + if ((hasAttribute & TextureAttributes.OffsetV) != 0) + return offsetV; + else + return DefaultTexture.offsetV; + } + set + { + offsetV = value; + hasAttribute |= TextureAttributes.OffsetV; + } + } + + /// + [XmlAttribute("rotation")] + public float Rotation + { + get + { + if ((hasAttribute & TextureAttributes.Rotation) != 0) + return rotation; + else + return DefaultTexture.rotation; + } + set + { + rotation = value; + hasAttribute |= TextureAttributes.Rotation; + } + } + + /// + [XmlAttribute("flags1")] + public byte Flags1 + { + get + { + if ((hasAttribute & TextureAttributes.Flags1) != 0) + return flags1; + else + return DefaultTexture.flags1; + } + set + { + flags1 = value; + hasAttribute |= TextureAttributes.Flags1; + } + } + + /// + [XmlAttribute("flags2")] + public byte Flags2 + { + get + { + if ((hasAttribute & TextureAttributes.Flags2) != 0) + return flags2; + else + return DefaultTexture.flags2; + } + set + { + flags2 = value; + hasAttribute |= TextureAttributes.Flags2; + } + } + + /// + [XmlElement("id")] + public LLUUID TextureID + { + get + { + if ((hasAttribute & TextureAttributes.TextureID) != 0) + return textureID; + else + return DefaultTexture.textureID; + } + set + { + textureID = value; + hasAttribute |= TextureAttributes.TextureID; + } + } + + ///////////////////////////// + ///// End of properties ///// + ///////////////////////////// + + /// + /// + /// + public TextureEntryFace() + { + } + + /// + /// Contains the definition for individual faces + /// + /// + public TextureEntryFace(TextureEntryFace defaultTexture) + { + DefaultTexture = defaultTexture; + if (DefaultTexture == null) + hasAttribute = TextureAttributes.All; + else + hasAttribute = TextureAttributes.None; + } + + public void ToXml(XmlWriter xmlWriter) + { + XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + XmlSerializer serializer = new XmlSerializer(typeof(TextureEntryFace)); + serializer.Serialize(xmlWriter, this, ns); + } + + } + + /// + /// Controls the texture animation of a particular prim + /// + [Serializable] + public class TextureAnimation + { + /// + [XmlAttribute("flags"), DefaultValue(0)] + public uint Flags; + /// + [XmlAttribute("face"), DefaultValue(0)] + public uint Face; + /// + [XmlAttribute("sizex"), DefaultValue(0)] + public uint SizeX; + /// + [XmlAttribute("sizey"), DefaultValue(0)] + public uint SizeY; + /// + [XmlAttribute("start"), DefaultValue(0)] + public float Start; + /// + [XmlAttribute("length"), DefaultValue(0)] + public float Length; + /// + [XmlAttribute("rate"), DefaultValue(0)] + public float Rate; + + /// + /// Default constructor + /// + public TextureAnimation() + { + } + + /// + /// + /// + /// + /// + public TextureAnimation(byte[] data, int pos) + { + FromBytes(data, pos); + } + + /// + /// + /// + /// + public byte[] GetBytes() + { + byte[] bytes = new byte[0]; + // FIXME: Finish TextureAnimation GetBytes() function + return bytes; + } + + private void FromBytes(byte[] data, int pos) + { + int i = pos; + + if (data.Length == 0) + return; + + Flags = (uint)data[i++]; + Face = (uint)data[i++]; + SizeX = (uint)data[i++]; + SizeY = (uint)data[i++]; + + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(data, i, 4); + Array.Reverse(data, i + 4, 4); + Array.Reverse(data, i + 8, 4); + } + + Start = BitConverter.ToSingle(data, i); + Length = BitConverter.ToSingle(data, i + 4); + Rate = BitConverter.ToSingle(data, i + 8); + } + } + } +} diff --git a/libsecondlife-cs/_VisualParam_.cs b/libsecondlife-cs/_VisualParam_.cs index d96ee9ca..b87e997c 100644 --- a/libsecondlife-cs/_VisualParam_.cs +++ b/libsecondlife-cs/_VisualParam_.cs @@ -1,287 +1,287 @@ -using System; -using System.Collections.Generic; - -namespace libsecondlife -{ - /// - /// A single visual characteristic of an avatar mesh, such as eyebrow height - /// - public struct VisualParam - { - /// Index of this visual param - public int ParamID; - /// Internal name - public string Name; - /// Group ID this parameter belongs to - public int Group; - /// Name of the wearable this parameter belongs to - public string Wearable; - /// Displayable label of this characteristic - public string Label; - /// Displayable label for the minimum value of this characteristic - public string LabelMin; - /// Displayable label for the maximum value of this characteristic - public string LabelMax; - /// Default value - public float DefaultValue; - /// Minimum value - public float MinValue; - /// Maximum value - public float MaxValue; - - /// - /// Set all the values through the constructor - /// - /// Index of this visual param - /// Internal name - /// - /// - /// Displayable label of this characteristic - /// Displayable label for the minimum value of this characteristic - /// Displayable label for the maximum value of this characteristic - /// Default value - /// Minimum value - /// Maximum value - public VisualParam(int paramID, string name, int group, string wearable, string label, string labelMin, string labelMax, float def, float min, float max) - { - ParamID = paramID; - Name = name; - Group = group; - Wearable = wearable; - Label = label; - LabelMin = labelMin; - LabelMax = labelMax; - DefaultValue = def; - MaxValue = max; - MinValue = min; - } - } - - /// - /// Holds the Params array of all the avatar appearance parameters - /// - public static class VisualParams - { - public static VisualParam[] Params = new VisualParam[] - { - new VisualParam(1, "Big_Brow", 0, "shape", "Brow Size", "Small", "Large", -0.3f, -0.3f, 2f), - new VisualParam(2, "Nose_Big_Out", 0, "shape", "Nose Size", "Small", "Large", -0.8f, -0.8f, 2.5f), - new VisualParam(4, "Broad_Nostrils", 0, "shape", "Nostril Width", "Narrow", "Broad", -0.5f, -0.5f, 1f), - new VisualParam(5, "Cleft_Chin", 0, "shape", "Chin Cleft", "Round", "Cleft", -0.1f, -0.1f, 1f), - new VisualParam(6, "Bulbous_Nose_Tip", 0, "shape", "Nose Tip Shape", "Pointy", "Bulbous", -0.3f, -0.3f, 1f), - new VisualParam(7, "Weak_Chin", 0, "shape", "Chin Angle", "Chin Out", "Chin In", -0.5f, -0.5f, 0.5f), - new VisualParam(8, "Double_Chin", 0, "shape", "Chin-Neck", "Tight Chin", "Double Chin", -0.5f, -0.5f, 1.5f), - new VisualParam(10, "Sunken_Cheeks", 0, "shape", "Lower Cheeks", "Well-Fed", "Sunken", -1.5f, -1.5f, 3f), - new VisualParam(11, "Noble_Nose_Bridge", 0, "shape", "Upper Bridge", "Low", "High", -0.5f, -0.5f, 1.5f), - new VisualParam(12, "Jowls", 0, "shape", "", "Less", "More", -0.5f, -0.5f, 2.5f), - new VisualParam(13, "Cleft_Chin_Upper", 0, "shape", "Upper Chin Cleft", "Round", "Cleft", 0f, 0f, 1.5f), - new VisualParam(14, "High_Cheek_Bones", 0, "shape", "Cheek Bones", "Low", "High", -0.5f, -0.5f, 1f), - new VisualParam(15, "Ears_Out", 0, "shape", "Ear Angle", "In", "Out", -0.5f, -0.5f, 1.5f), - new VisualParam(16, "Pointy_Eyebrows", 0, "hair", "Eyebrow Points", "Smooth", "Pointy", -0.5f, -0.5f, 3f), - new VisualParam(17, "Square_Jaw", 0, "shape", "Jaw Shape", "Pointy", "Square", -0.5f, -0.5f, 1f), - new VisualParam(18, "Puffy_Upper_Cheeks", 0, "shape", "Upper Cheeks", "Thin", "Puffy", -1.5f, -1.5f, 2.5f), - new VisualParam(19, "Upturned_Nose_Tip", 0, "shape", "Nose Tip Angle", "Downturned", "Upturned", -1.5f, -1.5f, 1f), - new VisualParam(20, "Bulbous_Nose", 0, "shape", "Nose Thickness", "Thin Nose", "Bulbous Nose", -0.5f, -0.5f, 1.5f), - new VisualParam(21, "Upper_Eyelid_Fold", 0, "shape", "Upper Eyelid Fold", "Uncreased", "Creased", -0.2f, -0.2f, 1.3f), - new VisualParam(22, "Attached_Earlobes", 0, "shape", "Attached Earlobes", "Unattached", "Attached", 0f, 0f, 1f), - new VisualParam(23, "Baggy_Eyes", 0, "shape", "Eye Bags", "Smooth", "Baggy", -0.5f, -0.5f, 1.5f), - new VisualParam(24, "Wide_Eyes", 0, "shape", "Eye Opening", "Narrow", "Wide", -1.5f, -1.5f, 2f), - new VisualParam(25, "Wide_Lip_Cleft", 0, "shape", "Lip Cleft", "Narrow", "Wide", -0.8f, -0.8f, 1.5f), - new VisualParam(27, "Wide_Nose_Bridge", 0, "shape", "Bridge Width", "Narrow", "Wide", -1.3f, -1.3f, 1.2f), - new VisualParam(31, "Arced_Eyebrows", 0, "hair", "Eyebrow Arc", "Flat", "Arced", 0.5f, 0f, 2f), - new VisualParam(33, "Height", 0, "shape", "Height", "Short", "Tall", -2.3f, -2.3f, 2f), - new VisualParam(34, "Thickness", 0, "shape", "Body Thickness", "Body Thin", "Body Thick", -0.7f, -0.7f, 1.5f), - new VisualParam(35, "Big_Ears", 0, "shape", "Ear Size", "Small", "Large", -1f, -1f, 2f), - new VisualParam(36, "Shoulders", 0, "shape", "Shoulders", "Narrow", "Broad", -0.5f, -1.8f, 1.4f), - new VisualParam(37, "Hip Width", 0, "shape", "Hip Width", "Narrow", "Wide", -3.2f, -3.2f, 2.8f), - new VisualParam(38, "Torso Length", 0, "shape", "", "Short Torso", "Long Torso", -1f, -1f, 1f), - new VisualParam(80, "male", 0, "shape", "", "", "", 0f, 0f, 1f), - new VisualParam(93, "Glove Length", 0, "gloves", "", "Short", "Long", 0.8f, 0.01f, 1f), - new VisualParam(98, "Eye Lightness", 0, "eyes", "", "Darker", "Lighter", 0f, 0f, 1f), - new VisualParam(99, "Eye Color", 0, "eyes", "", "Natural", "Unnatural", 0f, 0f, 1f), - new VisualParam(105, "Breast Size", 0, "shape", "", "Small", "Large", 0.5f, 0f, 1f), - new VisualParam(108, "Rainbow Color", 0, "skin", "", "None", "Wild", 0f, 0f, 1f), - new VisualParam(110, "Red Skin", 0, "skin", "Ruddiness", "Pale", "Ruddy", 0f, 0f, 0.1f), - new VisualParam(111, "Pigment", 0, "skin", "", "Light", "Dark", 0.5f, 0f, 1f), - new VisualParam(112, "Rainbow Color", 0, "hair", "", "None", "Wild", 0f, 0f, 1f), - new VisualParam(113, "Red Hair", 0, "hair", "", "No Red", "Very Red", 0f, 0f, 1f), - new VisualParam(114, "Blonde Hair", 0, "hair", "", "Black", "Blonde", 0.5f, 0f, 1f), - new VisualParam(115, "White Hair", 0, "hair", "", "No White", "All White", 0f, 0f, 1f), - new VisualParam(116, "Rosy Complexion", 0, "skin", "", "Less Rosy", "More Rosy", 0f, 0f, 1f), - new VisualParam(117, "Lip Pinkness", 0, "skin", "", "Darker", "Pinker", 0f, 0f, 1f), - new VisualParam(119, "Eyebrow Size", 0, "hair", "", "Thin Eyebrows", "Bushy Eyebrows", 0.5f, 0f, 1f), - new VisualParam(130, "Front Fringe", 0, "hair", "", "Short", "Long", 0.45f, 0f, 1f), - new VisualParam(131, "Side Fringe", 0, "hair", "", "Short", "Long", 0.5f, 0f, 1f), - new VisualParam(132, "Back Fringe", 0, "hair", "", "Short", "Long", 0.39f, 0f, 1f), - new VisualParam(133, "Hair Front", 0, "hair", "", "Short", "Long", 0.25f, 0f, 1f), - new VisualParam(134, "Hair Sides", 0, "hair", "", "Short", "Long", 0.5f, 0f, 1f), - new VisualParam(135, "Hair Back", 0, "hair", "", "Short", "Long", 0.55f, 0f, 1f), - new VisualParam(136, "Hair Sweep", 0, "hair", "", "Sweep Forward", "Sweep Back", 0.5f, 0f, 1f), - new VisualParam(137, "Hair Tilt", 0, "hair", "", "Left", "Right", 0.5f, 0f, 1f), - new VisualParam(140, "Hair_Part_Middle", 0, "hair", "Middle Part", "No Part", "Part", 0f, 0f, 2f), - new VisualParam(141, "Hair_Part_Right", 0, "hair", "Right Part", "No Part", "Part", 0f, 0f, 2f), - new VisualParam(142, "Hair_Part_Left", 0, "hair", "Left Part", "No Part", "Part", 0f, 0f, 2f), - new VisualParam(143, "Hair_Sides_Full", 0, "hair", "Full Hair Sides", "Mowhawk", "Full Sides", 0.125f, -4f, 1.5f), - new VisualParam(150, "Body Definition", 0, "skin", "", "Less", "More", 0f, 0f, 1f), - new VisualParam(155, "Lip Width", 0, "shape", "Lip Width", "Narrow Lips", "Wide Lips", 0f, -0.9f, 1.3f), - new VisualParam(157, "Belly Size", 0, "shape", "", "Small", "Big", 0f, 0f, 1f), - new VisualParam(162, "Facial Definition", 0, "skin", "", "Less", "More", 0f, 0f, 1f), - new VisualParam(163, "wrinkles", 0, "skin", "", "Less", "More", 0f, 0f, 1f), - new VisualParam(165, "Freckles", 0, "skin", "", "Less", "More", 0f, 0f, 1f), - new VisualParam(166, "Sideburns", 0, "hair", "", "Short Sideburns", "Mutton Chops", 0f, 0f, 1f), - new VisualParam(167, "Moustache", 0, "hair", "", "Chaplin", "Handlebars", 0f, 0f, 1f), - new VisualParam(168, "Soulpatch", 0, "hair", "", "Less soul", "More soul", 0f, 0f, 1f), - new VisualParam(169, "Chin Curtains", 0, "hair", "", "Less Curtains", "More Curtains", 0f, 0f, 1f), - new VisualParam(177, "Hair_Rumpled", 0, "hair", "Rumpled Hair", "Smooth Hair", "Rumpled Hair", 0f, 0f, 1f), - new VisualParam(181, "Hair_Big_Front", 0, "hair", "Big Hair Front", "Less", "More", 0.14f, -1f, 1f), - new VisualParam(182, "Hair_Big_Top", 0, "hair", "Big Hair Top", "Less", "More", 0.7f, -1f, 1f), - new VisualParam(183, "Hair_Big_Back", 0, "hair", "Big Hair Back", "Less", "More", 0.05f, -1f, 1f), - new VisualParam(184, "Hair_Spiked", 0, "hair", "Spiked Hair", "No Spikes", "Big Spikes", 0f, 0f, 1f), - new VisualParam(185, "Deep_Chin", 0, "shape", "Chin Depth", "Shallow", "Deep", -1f, -1f, 1f), - new VisualParam(192, "Bangs_Part_Middle", 0, "hair", "Part Bangs", "No Part", "Part Bangs", 0f, 0f, 1f), - new VisualParam(193, "Head Shape", 0, "shape", "Head Shape", "More Square", "More Round", 0.5f, 0f, 1f), - new VisualParam(196, "Eye Spacing", 0, "shape", "Eye Spacing", "Close Set Eyes", "Far Set Eyes", 0f, -2f, 1f), - new VisualParam(198, "Heel Height", 0, "shoes", "", "Low Heels", "High Heels", 0f, 0f, 1f), - new VisualParam(503, "Platform Height", 0, "shoes", "", "Low Platforms", "High Platforms", 0f, 0f, 1f), - new VisualParam(505, "Lip Thickness", 0, "shape", "", "Thin Lips", "Fat Lips", 0.5f, 0f, 1f), - new VisualParam(506, "Mouth_Height", 0, "shape", "Mouth Position", "High", "Low", -2f, -2f, 2f), - new VisualParam(507, "Breast_Gravity", 0, "shape", "Breast Buoyancy", "Less Gravity", "More Gravity", 0f, -1.5f, 2f), - new VisualParam(508, "Shoe_Platform_Width", 0, "shoes", "Platform Width", "Narrow", "Wide", -1f, -1f, 2f), - new VisualParam(513, "Heel Shape", 0, "shoes", "", "Pointy Heels", "Thick Heels", 0.5f, 0f, 1f), - new VisualParam(514, "Toe Shape", 0, "shoes", "", "Pointy", "Square", 0.5f, 0f, 1f), - new VisualParam(515, "Foot_Size", 0, "shape", "Foot Size", "Small", "Big", -1f, -1f, 3f), - new VisualParam(517, "Wide_Nose", 0, "shape", "Nose Width", "Narrow", "Wide", -0.5f, -0.5f, 1f), - new VisualParam(518, "Eyelashes_Long", 0, "shape", "Eyelash Length", "Short", "Long", -0.3f, -0.3f, 1.5f), - new VisualParam(603, "Sleeve Length", 0, "undershirt", "", "Short", "Long", 0.4f, 0.01f, 1f), - new VisualParam(604, "Bottom", 0, "undershirt", "", "Short", "Long", 0.85f, 0f, 1f), - new VisualParam(605, "Collar Front", 0, "undershirt", "", "Low", "High", 0.84f, 0f, 1f), - new VisualParam(606, "Sleeve Length", 0, "jacket", "", "Short", "Long", 0.8f, 0f, 1f), - new VisualParam(607, "Collar Front", 0, "jacket", "", "Low", "High", 0.8f, 0f, 1f), - new VisualParam(608, "bottom length lower", 0, "jacket", "Jacket Length", "Short", "Long", 0.8f, 0f, 1f), - new VisualParam(609, "open jacket", 0, "jacket", "Open Front", "Open", "Closed", 0.2f, 0f, 1f), - new VisualParam(616, "Shoe Height", 0, "shoes", "", "Short", "Tall", 0.1f, 0f, 1f), - new VisualParam(617, "Socks Length", 0, "socks", "", "Short", "Long", 0.35f, 0f, 1f), - new VisualParam(619, "Pants Length", 0, "underpants", "", "Short", "Long", 0.3f, 0f, 1f), - new VisualParam(624, "Pants Waist", 0, "underpants", "", "Low", "High", 0.8f, 0f, 1f), - new VisualParam(625, "Leg_Pantflair", 0, "pants", "Cuff Flare", "Tight Cuffs", "Flared Cuffs", 0f, 0f, 1.5f), - new VisualParam(629, "Forehead Angle", 0, "shape", "", "More Vertical", "More Sloped", 0.5f, 0f, 1f), - new VisualParam(637, "Body Fat", 0, "shape", "", "Less Body Fat", "More Body Fat", 0f, 0f, 1f), - new VisualParam(638, "Low_Crotch", 0, "pants", "Pants Crotch", "High and Tight", "Low and Loose", 0f, 0f, 1.3f), - new VisualParam(646, "Egg_Head", 0, "shape", "Egg Head", "Chin Heavy", "Forehead Heavy", 0f, -1.3f, 1f), - new VisualParam(647, "Squash_Stretch_Head", 0, "shape", "Head Stretch", "Squash Head", "Stretch Head", 0f, -0.5f, 1f), - new VisualParam(649, "Torso Muscles", 0, "shape", "Torso Muscles", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), - new VisualParam(650, "Eyelid_Corner_Up", 0, "shape", "Outer Eye Corner", "Corner Down", "Corner Up", -1.3f, -1.3f, 1.2f), - new VisualParam(652, "Leg Muscles", 0, "shape", "", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), - new VisualParam(653, "Tall_Lips", 0, "shape", "Lip Fullness", "Less Full", "More Full", -1f, -1f, 2f), - new VisualParam(654, "Shoe_Toe_Thick", 0, "shoes", "Toe Thickness", "Flat Toe", "Thick Toe", 0f, 0f, 2f), - new VisualParam(656, "Crooked_Nose", 0, "shape", "Crooked Nose", "Nose Left", "Nose Right", -2f, -2f, 2f), - new VisualParam(659, "Mouth Corner", 0, "shape", "", "Corner Down", "Corner Up", 0.5f, 0f, 1f), - new VisualParam(662, "Face Shear", 0, "shape", "", "Shear Right Up", "Shear Left Up", 0.5f, 0f, 1f), - new VisualParam(663, "Shift_Mouth", 0, "shape", "Shift Mouth", "Shift Left", "Shift Right", 0f, -2f, 2f), - new VisualParam(664, "Pop_Eye", 0, "shape", "Eye Pop", "Pop Right Eye", "Pop Left Eye", 0f, -1.3f, 1.3f), - new VisualParam(665, "Jaw_Jut", 0, "shape", "Jaw Jut", "Overbite", "Underbite", 0f, -2f, 2f), - new VisualParam(674, "Hair_Shear_Back", 0, "hair", "Shear Back", "Full Back", "Sheared Back", -0.3f, -1f, 2f), - new VisualParam(675, "Hand Size", 0, "shape", "", "Small Hands", "Large Hands", -0.3f, -0.3f, 0.3f), - new VisualParam(676, "Love_Handles", 0, "shape", "Love Handles", "Less Love", "More Love", 0f, -1f, 2f), - new VisualParam(678, "Torso Muscles", 0, "shape", "", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), - new VisualParam(682, "Head Size", 0, "shape", "Head Size", "Small Head", "Big Head", 0.5f, 0f, 1f), - new VisualParam(683, "Neck Thickness", 0, "shape", "", "Skinny Neck", "Thick Neck", -0.15f, -0.4f, 0.2f), - new VisualParam(684, "Breast_Female_Cleavage", 0, "shape", "Breast Cleavage", "Separate", "Join", 0f, -0.3f, 1.3f), - new VisualParam(685, "Chest_Male_No_Pecs", 0, "shape", "Pectorals", "Big Pectorals", "Sunken Chest", 0f, -0.5f, 1.1f), - new VisualParam(690, "Eye Size", 0, "shape", "Eye Size", "Beady Eyes", "Anime Eyes", 0.5f, 0f, 1f), - new VisualParam(692, "Leg Length", 0, "shape", "", "Short Legs", "Long Legs", -1f, -1f, 1f), - new VisualParam(693, "Arm Length", 0, "shape", "", "Short Arms", "Long arms", 0.6f, -1f, 1f), - new VisualParam(700, "Lipstick Color", 0, "skin", "", "Pink", "Black", 0.25f, 0f, 1f), - new VisualParam(701, "Lipstick", 0, "skin", "", "No Lipstick", "More Lipstick", 0f, 0f, 0.9f), - new VisualParam(702, "Lipgloss", 0, "skin", "", "No Lipgloss", "Glossy", 0f, 0f, 1f), - new VisualParam(703, "Eyeliner", 0, "skin", "", "No Eyeliner", "Full Eyeliner", 0f, 0f, 1f), - new VisualParam(704, "Blush", 0, "skin", "", "No Blush", "More Blush", 0f, 0f, 0.9f), - new VisualParam(705, "Blush Color", 0, "skin", "", "Pink", "Orange", 0.5f, 0f, 1f), - new VisualParam(706, "Out Shdw Opacity", 0, "skin", "", "Clear", "Opaque", 0.6f, 0.2f, 1f), - new VisualParam(707, "Outer Shadow", 0, "skin", "", "No Eyeshadow", "More Eyeshadow", 0f, 0f, 0.7f), - new VisualParam(708, "Out Shdw Color", 0, "skin", "", "Light", "Dark", 0f, 0f, 1f), - new VisualParam(709, "Inner Shadow", 0, "skin", "", "No Eyeshadow", "More Eyeshadow", 0f, 0f, 1f), - new VisualParam(710, "Nail Polish", 0, "skin", "", "No Polish", "Painted Nails", 0f, 0f, 1f), - new VisualParam(711, "Blush Opacity", 0, "skin", "", "Clear", "Opaque", 0.5f, 0f, 1f), - new VisualParam(712, "In Shdw Color", 0, "skin", "", "Light", "Dark", 0f, 0f, 1f), - new VisualParam(713, "In Shdw Opacity", 0, "skin", "", "Clear", "Opaque", 0.7f, 0.2f, 1f), - new VisualParam(714, "Eyeliner Color", 0, "skin", "", "Dark Green", "Black", 0f, 0f, 1f), - new VisualParam(715, "Nail Polish Color", 0, "skin", "", "Pink", "Black", 0f, 0f, 1f), - new VisualParam(750, "Eyebrow Density", 0, "hair", "", "Sparse", "Dense", 0.7f, 0f, 1f), - new VisualParam(752, "Hair Thickness", 0, "hair", "", "5 O'Clock Shadow", "Bushy Hair", 0.5f, 0f, 1f), - new VisualParam(753, "Saddlebags", 0, "shape", "Saddle Bags", "Less Saddle", "More Saddle", 0f, -0.5f, 3f), - new VisualParam(754, "Hair_Taper_Back", 0, "hair", "Taper Back", "Wide Back", "Narrow Back", 0f, -1f, 2f), - new VisualParam(755, "Hair_Taper_Front", 0, "hair", "Taper Front", "Wide Front", "Narrow Front", 0.05f, -1.5f, 1.5f), - new VisualParam(756, "Neck Length", 0, "shape", "", "Short Neck", "Long Neck", 0f, -1f, 1f), - new VisualParam(757, "Lower_Eyebrows", 0, "hair", "Eyebrow Height", "Higher", "Lower", -1f, -4f, 2f), - new VisualParam(758, "Lower_Bridge_Nose", 0, "shape", "Lower Bridge", "Low", "High", -1.5f, -1.5f, 1.5f), - new VisualParam(759, "Low_Septum_Nose", 0, "shape", "Nostril Division", "High", "Low", 0.5f, -1f, 1.5f), - new VisualParam(760, "Jaw_Angle", 0, "shape", "Jaw Angle", "Low Jaw", "High Jaw", 0f, -1.2f, 2f), - new VisualParam(762, "Hair_Shear_Front", 0, "hair", "Shear Front", "Full Front", "Sheared Front", 0f, 0f, 3f), - new VisualParam(763, "Hair Volume", 0, "hair", "", "Less Volume", "More Volume", 0.55f, 0f, 1f), - new VisualParam(764, "Lip_Cleft_Deep", 0, "shape", "Lip Cleft Depth", "Shallow", "Deep", -0.5f, -0.5f, 1.2f), - new VisualParam(765, "Puffy_Lower_Lids", 0, "shape", "Puffy Eyelids", "Flat", "Puffy", -0.3f, -0.3f, 2.5f), - new VisualParam(769, "Eye Depth", 0, "shape", "", "Sunken Eyes", "Bugged Eyes", 0.5f, 0f, 1f), - new VisualParam(773, "Head Length", 0, "shape", "", "Flat Head", "Long Head", 0.5f, 0f, 1f), - new VisualParam(775, "Body Freckles", 0, "skin", "", "Less Freckles", "More Freckles", 0f, 0f, 1f), - new VisualParam(779, "Collar Back", 0, "undershirt", "", "Low", "High", 0.84f, 0f, 1f), - new VisualParam(780, "Collar Back", 0, "jacket", "", "Low", "High", 0.8f, 0f, 1f), - new VisualParam(781, "Collar Back", 0, "shirt", "", "Low", "High", 0.78f, 0f, 1f), - new VisualParam(785, "Pigtails", 0, "hair", "", "Short Pigtails", "Long Pigtails", 0f, 0f, 1f), - new VisualParam(789, "Ponytail", 0, "hair", "", "Short Ponytail", "Long Ponytail", 0f, 0f, 1f), - new VisualParam(795, "Butt Size", 0, "shape", "Butt Size", "Flat Butt", "Big Butt", 0.25f, 0f, 1f), - new VisualParam(796, "Pointy_Ears", 0, "shape", "Ear Tips", "Flat", "Pointy", -0.4f, -0.4f, 3f), - new VisualParam(799, "Lip Ratio", 0, "shape", "Lip Ratio", "More Upper Lip", "More Lower Lip", 0.5f, 0f, 1f), - new VisualParam(800, "Sleeve Length", 0, "shirt", "", "Short", "Long", 0.89f, 0f, 1f), - new VisualParam(801, "Shirt Bottom", 0, "shirt", "", "Short", "Long", 1f, 0f, 1f), - new VisualParam(802, "Collar Front", 0, "shirt", "", "Low", "High", 0.78f, 0f, 1f), - new VisualParam(803, "shirt_red", 0, "shirt", "", "", "", 1f, 0f, 1f), - new VisualParam(804, "shirt_green", 0, "shirt", "", "", "", 1f, 0f, 1f), - new VisualParam(805, "shirt_blue", 0, "shirt", "", "", "", 1f, 0f, 1f), - new VisualParam(806, "pants_red", 0, "pants", "", "", "", 1f, 0f, 1f), - new VisualParam(807, "pants_green", 0, "pants", "", "", "", 1f, 0f, 1f), - new VisualParam(808, "pants_blue", 0, "pants", "", "", "", 1f, 0f, 1f), - new VisualParam(812, "shoes_red", 0, "shoes", "", "", "", 1f, 0f, 1f), - new VisualParam(813, "shoes_green", 0, "shoes", "", "", "", 1f, 0f, 1f), - new VisualParam(814, "Waist Height", 0, "pants", "", "Low", "High", 1f, 0f, 1f), - new VisualParam(815, "Pants Length", 0, "pants", "", "Short", "Long", 0.8f, 0f, 1f), - new VisualParam(816, "Loose Lower Clothing", 0, "pants", "Pants Fit", "Tight Pants", "Loose Pants", 0f, 0f, 1f), - new VisualParam(817, "shoes_blue", 0, "shoes", "", "", "", 1f, 0f, 1f), - new VisualParam(818, "socks_red", 0, "socks", "", "", "", 1f, 0f, 1f), - new VisualParam(819, "socks_green", 0, "socks", "", "", "", 1f, 0f, 1f), - new VisualParam(820, "socks_blue", 0, "socks", "", "", "", 1f, 0f, 1f), - new VisualParam(821, "undershirt_red", 0, "undershirt", "", "", "", 1f, 0f, 1f), - new VisualParam(822, "undershirt_green", 0, "undershirt", "", "", "", 1f, 0f, 1f), - new VisualParam(823, "undershirt_blue", 0, "undershirt", "", "", "", 1f, 0f, 1f), - new VisualParam(824, "underpants_red", 0, "underpants", "", "", "", 1f, 0f, 1f), - new VisualParam(825, "underpants_green", 0, "underpants", "", "", "", 1f, 0f, 1f), - new VisualParam(826, "underpants_blue", 0, "underpants", "", "", "", 1f, 0f, 1f), - new VisualParam(827, "gloves_red", 0, "gloves", "", "", "", 1f, 0f, 1f), - new VisualParam(828, "Loose Upper Clothing", 0, "shirt", "Shirt Fit", "Tight Shirt", "Loose Shirt", 0f, 0f, 1f), - new VisualParam(829, "gloves_green", 0, "gloves", "", "", "", 1f, 0f, 1f), - new VisualParam(830, "gloves_blue", 0, "gloves", "", "", "", 1f, 0f, 1f), - new VisualParam(834, "jacket_red", 0, "jacket", "", "", "", 1f, 0f, 1f), - new VisualParam(835, "jacket_green", 0, "jacket", "", "", "", 1f, 0f, 1f), - new VisualParam(836, "jacket_blue", 0, "jacket", "", "", "", 1f, 0f, 1f), - new VisualParam(840, "Shirtsleeve_flair", 0, "shirt", "Sleeve Looseness", "Tight Sleeves", "Loose Sleeves", 0f, 0f, 1.5f), - new VisualParam(841, "Bowed_Legs", 0, "shape", "Knee Angle", "Knock Kneed", "Bow Legged", 0f, -1f, 1f), - new VisualParam(842, "Hip Length", 0, "shape", "", "Short hips", "Long Hips", -1f, -1f, 1f), - new VisualParam(844, "Glove Fingers", 0, "gloves", "", "Fingerless", "Fingers", 1f, 0.01f, 1f), - new VisualParam(848, "skirt_bustle", 0, "skirt", "bustle skirt", "no bustle", "more bustle", 0.2f, 0f, 2f), - new VisualParam(858, "Skirt Length", 0, "skirt", "", "Short", "Long", 0.4f, 0.01f, 1f), - new VisualParam(859, "Slit Front", 0, "skirt", "", "Open Front", "Closed Front", 1f, 0f, 1f), - new VisualParam(860, "Slit Back", 0, "skirt", "", "Open Back", "Closed Back", 1f, 0f, 1f), - new VisualParam(861, "Slit Left", 0, "skirt", "", "Open Left", "Closed Left", 1f, 0f, 1f), - new VisualParam(862, "Slit Right", 0, "skirt", "", "Open Right", "Closed Right", 1f, 0f, 1f), - new VisualParam(863, "skirt_looseness", 0, "skirt", "Skirt Fit", "Tight Skirt", "Poofy Skirt", 0.333f, 0f, 1f), - new VisualParam(868, "Shirt Wrinkles", 0, "shirt", "", "", "", 0f, 0f, 1f), - new VisualParam(869, "Pants Wrinkles", 0, "pants", "", "", "", 0f, 0f, 1f), - new VisualParam(877, "Jacket Wrinkles", 0, "jacket", "Jacket Wrinkles", "No Wrinkles", "Wrinkles", 0f, 0f, 1f), - new VisualParam(879, "Male_Package", 0, "shape", "Package", "Coin Purse", "Duffle Bag", 0f, -0.5f, 2f), - new VisualParam(880, "Eyelid_Inner_Corner_Up", 0, "shape", "Inner Eye Corner", "Corner Down", "Corner Up", -1.3f, -1.3f, 1.2f), - new VisualParam(921, "skirt_red", 0, "skirt", "", "", "", 1f, 0f, 1f), - new VisualParam(922, "skirt_green", 0, "skirt", "", "", "", 1f, 0f, 1f), - new VisualParam(923, "skirt_blue", 0, "skirt", "", "", "", 1f, 0f, 1f), - }; - } -} +using System; +using System.Collections.Generic; + +namespace libsecondlife +{ + /// + /// A single visual characteristic of an avatar mesh, such as eyebrow height + /// + public struct VisualParam + { + /// Index of this visual param + public int ParamID; + /// Internal name + public string Name; + /// Group ID this parameter belongs to + public int Group; + /// Name of the wearable this parameter belongs to + public string Wearable; + /// Displayable label of this characteristic + public string Label; + /// Displayable label for the minimum value of this characteristic + public string LabelMin; + /// Displayable label for the maximum value of this characteristic + public string LabelMax; + /// Default value + public float DefaultValue; + /// Minimum value + public float MinValue; + /// Maximum value + public float MaxValue; + + /// + /// Set all the values through the constructor + /// + /// Index of this visual param + /// Internal name + /// + /// + /// Displayable label of this characteristic + /// Displayable label for the minimum value of this characteristic + /// Displayable label for the maximum value of this characteristic + /// Default value + /// Minimum value + /// Maximum value + public VisualParam(int paramID, string name, int group, string wearable, string label, string labelMin, string labelMax, float def, float min, float max) + { + ParamID = paramID; + Name = name; + Group = group; + Wearable = wearable; + Label = label; + LabelMin = labelMin; + LabelMax = labelMax; + DefaultValue = def; + MaxValue = max; + MinValue = min; + } + } + + /// + /// Holds the Params array of all the avatar appearance parameters + /// + public static class VisualParams + { + public static VisualParam[] Params = new VisualParam[] + { + new VisualParam(1, "Big_Brow", 0, "shape", "Brow Size", "Small", "Large", -0.3f, -0.3f, 2f), + new VisualParam(2, "Nose_Big_Out", 0, "shape", "Nose Size", "Small", "Large", -0.8f, -0.8f, 2.5f), + new VisualParam(4, "Broad_Nostrils", 0, "shape", "Nostril Width", "Narrow", "Broad", -0.5f, -0.5f, 1f), + new VisualParam(5, "Cleft_Chin", 0, "shape", "Chin Cleft", "Round", "Cleft", -0.1f, -0.1f, 1f), + new VisualParam(6, "Bulbous_Nose_Tip", 0, "shape", "Nose Tip Shape", "Pointy", "Bulbous", -0.3f, -0.3f, 1f), + new VisualParam(7, "Weak_Chin", 0, "shape", "Chin Angle", "Chin Out", "Chin In", -0.5f, -0.5f, 0.5f), + new VisualParam(8, "Double_Chin", 0, "shape", "Chin-Neck", "Tight Chin", "Double Chin", -0.5f, -0.5f, 1.5f), + new VisualParam(10, "Sunken_Cheeks", 0, "shape", "Lower Cheeks", "Well-Fed", "Sunken", -1.5f, -1.5f, 3f), + new VisualParam(11, "Noble_Nose_Bridge", 0, "shape", "Upper Bridge", "Low", "High", -0.5f, -0.5f, 1.5f), + new VisualParam(12, "Jowls", 0, "shape", "", "Less", "More", -0.5f, -0.5f, 2.5f), + new VisualParam(13, "Cleft_Chin_Upper", 0, "shape", "Upper Chin Cleft", "Round", "Cleft", 0f, 0f, 1.5f), + new VisualParam(14, "High_Cheek_Bones", 0, "shape", "Cheek Bones", "Low", "High", -0.5f, -0.5f, 1f), + new VisualParam(15, "Ears_Out", 0, "shape", "Ear Angle", "In", "Out", -0.5f, -0.5f, 1.5f), + new VisualParam(16, "Pointy_Eyebrows", 0, "hair", "Eyebrow Points", "Smooth", "Pointy", -0.5f, -0.5f, 3f), + new VisualParam(17, "Square_Jaw", 0, "shape", "Jaw Shape", "Pointy", "Square", -0.5f, -0.5f, 1f), + new VisualParam(18, "Puffy_Upper_Cheeks", 0, "shape", "Upper Cheeks", "Thin", "Puffy", -1.5f, -1.5f, 2.5f), + new VisualParam(19, "Upturned_Nose_Tip", 0, "shape", "Nose Tip Angle", "Downturned", "Upturned", -1.5f, -1.5f, 1f), + new VisualParam(20, "Bulbous_Nose", 0, "shape", "Nose Thickness", "Thin Nose", "Bulbous Nose", -0.5f, -0.5f, 1.5f), + new VisualParam(21, "Upper_Eyelid_Fold", 0, "shape", "Upper Eyelid Fold", "Uncreased", "Creased", -0.2f, -0.2f, 1.3f), + new VisualParam(22, "Attached_Earlobes", 0, "shape", "Attached Earlobes", "Unattached", "Attached", 0f, 0f, 1f), + new VisualParam(23, "Baggy_Eyes", 0, "shape", "Eye Bags", "Smooth", "Baggy", -0.5f, -0.5f, 1.5f), + new VisualParam(24, "Wide_Eyes", 0, "shape", "Eye Opening", "Narrow", "Wide", -1.5f, -1.5f, 2f), + new VisualParam(25, "Wide_Lip_Cleft", 0, "shape", "Lip Cleft", "Narrow", "Wide", -0.8f, -0.8f, 1.5f), + new VisualParam(27, "Wide_Nose_Bridge", 0, "shape", "Bridge Width", "Narrow", "Wide", -1.3f, -1.3f, 1.2f), + new VisualParam(31, "Arced_Eyebrows", 0, "hair", "Eyebrow Arc", "Flat", "Arced", 0.5f, 0f, 2f), + new VisualParam(33, "Height", 0, "shape", "Height", "Short", "Tall", -2.3f, -2.3f, 2f), + new VisualParam(34, "Thickness", 0, "shape", "Body Thickness", "Body Thin", "Body Thick", -0.7f, -0.7f, 1.5f), + new VisualParam(35, "Big_Ears", 0, "shape", "Ear Size", "Small", "Large", -1f, -1f, 2f), + new VisualParam(36, "Shoulders", 0, "shape", "Shoulders", "Narrow", "Broad", -0.5f, -1.8f, 1.4f), + new VisualParam(37, "Hip Width", 0, "shape", "Hip Width", "Narrow", "Wide", -3.2f, -3.2f, 2.8f), + new VisualParam(38, "Torso Length", 0, "shape", "", "Short Torso", "Long Torso", -1f, -1f, 1f), + new VisualParam(80, "male", 0, "shape", "", "", "", 0f, 0f, 1f), + new VisualParam(93, "Glove Length", 0, "gloves", "", "Short", "Long", 0.8f, 0.01f, 1f), + new VisualParam(98, "Eye Lightness", 0, "eyes", "", "Darker", "Lighter", 0f, 0f, 1f), + new VisualParam(99, "Eye Color", 0, "eyes", "", "Natural", "Unnatural", 0f, 0f, 1f), + new VisualParam(105, "Breast Size", 0, "shape", "", "Small", "Large", 0.5f, 0f, 1f), + new VisualParam(108, "Rainbow Color", 0, "skin", "", "None", "Wild", 0f, 0f, 1f), + new VisualParam(110, "Red Skin", 0, "skin", "Ruddiness", "Pale", "Ruddy", 0f, 0f, 0.1f), + new VisualParam(111, "Pigment", 0, "skin", "", "Light", "Dark", 0.5f, 0f, 1f), + new VisualParam(112, "Rainbow Color", 0, "hair", "", "None", "Wild", 0f, 0f, 1f), + new VisualParam(113, "Red Hair", 0, "hair", "", "No Red", "Very Red", 0f, 0f, 1f), + new VisualParam(114, "Blonde Hair", 0, "hair", "", "Black", "Blonde", 0.5f, 0f, 1f), + new VisualParam(115, "White Hair", 0, "hair", "", "No White", "All White", 0f, 0f, 1f), + new VisualParam(116, "Rosy Complexion", 0, "skin", "", "Less Rosy", "More Rosy", 0f, 0f, 1f), + new VisualParam(117, "Lip Pinkness", 0, "skin", "", "Darker", "Pinker", 0f, 0f, 1f), + new VisualParam(119, "Eyebrow Size", 0, "hair", "", "Thin Eyebrows", "Bushy Eyebrows", 0.5f, 0f, 1f), + new VisualParam(130, "Front Fringe", 0, "hair", "", "Short", "Long", 0.45f, 0f, 1f), + new VisualParam(131, "Side Fringe", 0, "hair", "", "Short", "Long", 0.5f, 0f, 1f), + new VisualParam(132, "Back Fringe", 0, "hair", "", "Short", "Long", 0.39f, 0f, 1f), + new VisualParam(133, "Hair Front", 0, "hair", "", "Short", "Long", 0.25f, 0f, 1f), + new VisualParam(134, "Hair Sides", 0, "hair", "", "Short", "Long", 0.5f, 0f, 1f), + new VisualParam(135, "Hair Back", 0, "hair", "", "Short", "Long", 0.55f, 0f, 1f), + new VisualParam(136, "Hair Sweep", 0, "hair", "", "Sweep Forward", "Sweep Back", 0.5f, 0f, 1f), + new VisualParam(137, "Hair Tilt", 0, "hair", "", "Left", "Right", 0.5f, 0f, 1f), + new VisualParam(140, "Hair_Part_Middle", 0, "hair", "Middle Part", "No Part", "Part", 0f, 0f, 2f), + new VisualParam(141, "Hair_Part_Right", 0, "hair", "Right Part", "No Part", "Part", 0f, 0f, 2f), + new VisualParam(142, "Hair_Part_Left", 0, "hair", "Left Part", "No Part", "Part", 0f, 0f, 2f), + new VisualParam(143, "Hair_Sides_Full", 0, "hair", "Full Hair Sides", "Mowhawk", "Full Sides", 0.125f, -4f, 1.5f), + new VisualParam(150, "Body Definition", 0, "skin", "", "Less", "More", 0f, 0f, 1f), + new VisualParam(155, "Lip Width", 0, "shape", "Lip Width", "Narrow Lips", "Wide Lips", 0f, -0.9f, 1.3f), + new VisualParam(157, "Belly Size", 0, "shape", "", "Small", "Big", 0f, 0f, 1f), + new VisualParam(162, "Facial Definition", 0, "skin", "", "Less", "More", 0f, 0f, 1f), + new VisualParam(163, "wrinkles", 0, "skin", "", "Less", "More", 0f, 0f, 1f), + new VisualParam(165, "Freckles", 0, "skin", "", "Less", "More", 0f, 0f, 1f), + new VisualParam(166, "Sideburns", 0, "hair", "", "Short Sideburns", "Mutton Chops", 0f, 0f, 1f), + new VisualParam(167, "Moustache", 0, "hair", "", "Chaplin", "Handlebars", 0f, 0f, 1f), + new VisualParam(168, "Soulpatch", 0, "hair", "", "Less soul", "More soul", 0f, 0f, 1f), + new VisualParam(169, "Chin Curtains", 0, "hair", "", "Less Curtains", "More Curtains", 0f, 0f, 1f), + new VisualParam(177, "Hair_Rumpled", 0, "hair", "Rumpled Hair", "Smooth Hair", "Rumpled Hair", 0f, 0f, 1f), + new VisualParam(181, "Hair_Big_Front", 0, "hair", "Big Hair Front", "Less", "More", 0.14f, -1f, 1f), + new VisualParam(182, "Hair_Big_Top", 0, "hair", "Big Hair Top", "Less", "More", 0.7f, -1f, 1f), + new VisualParam(183, "Hair_Big_Back", 0, "hair", "Big Hair Back", "Less", "More", 0.05f, -1f, 1f), + new VisualParam(184, "Hair_Spiked", 0, "hair", "Spiked Hair", "No Spikes", "Big Spikes", 0f, 0f, 1f), + new VisualParam(185, "Deep_Chin", 0, "shape", "Chin Depth", "Shallow", "Deep", -1f, -1f, 1f), + new VisualParam(192, "Bangs_Part_Middle", 0, "hair", "Part Bangs", "No Part", "Part Bangs", 0f, 0f, 1f), + new VisualParam(193, "Head Shape", 0, "shape", "Head Shape", "More Square", "More Round", 0.5f, 0f, 1f), + new VisualParam(196, "Eye Spacing", 0, "shape", "Eye Spacing", "Close Set Eyes", "Far Set Eyes", 0f, -2f, 1f), + new VisualParam(198, "Heel Height", 0, "shoes", "", "Low Heels", "High Heels", 0f, 0f, 1f), + new VisualParam(503, "Platform Height", 0, "shoes", "", "Low Platforms", "High Platforms", 0f, 0f, 1f), + new VisualParam(505, "Lip Thickness", 0, "shape", "", "Thin Lips", "Fat Lips", 0.5f, 0f, 1f), + new VisualParam(506, "Mouth_Height", 0, "shape", "Mouth Position", "High", "Low", -2f, -2f, 2f), + new VisualParam(507, "Breast_Gravity", 0, "shape", "Breast Buoyancy", "Less Gravity", "More Gravity", 0f, -1.5f, 2f), + new VisualParam(508, "Shoe_Platform_Width", 0, "shoes", "Platform Width", "Narrow", "Wide", -1f, -1f, 2f), + new VisualParam(513, "Heel Shape", 0, "shoes", "", "Pointy Heels", "Thick Heels", 0.5f, 0f, 1f), + new VisualParam(514, "Toe Shape", 0, "shoes", "", "Pointy", "Square", 0.5f, 0f, 1f), + new VisualParam(515, "Foot_Size", 0, "shape", "Foot Size", "Small", "Big", -1f, -1f, 3f), + new VisualParam(517, "Wide_Nose", 0, "shape", "Nose Width", "Narrow", "Wide", -0.5f, -0.5f, 1f), + new VisualParam(518, "Eyelashes_Long", 0, "shape", "Eyelash Length", "Short", "Long", -0.3f, -0.3f, 1.5f), + new VisualParam(603, "Sleeve Length", 0, "undershirt", "", "Short", "Long", 0.4f, 0.01f, 1f), + new VisualParam(604, "Bottom", 0, "undershirt", "", "Short", "Long", 0.85f, 0f, 1f), + new VisualParam(605, "Collar Front", 0, "undershirt", "", "Low", "High", 0.84f, 0f, 1f), + new VisualParam(606, "Sleeve Length", 0, "jacket", "", "Short", "Long", 0.8f, 0f, 1f), + new VisualParam(607, "Collar Front", 0, "jacket", "", "Low", "High", 0.8f, 0f, 1f), + new VisualParam(608, "bottom length lower", 0, "jacket", "Jacket Length", "Short", "Long", 0.8f, 0f, 1f), + new VisualParam(609, "open jacket", 0, "jacket", "Open Front", "Open", "Closed", 0.2f, 0f, 1f), + new VisualParam(616, "Shoe Height", 0, "shoes", "", "Short", "Tall", 0.1f, 0f, 1f), + new VisualParam(617, "Socks Length", 0, "socks", "", "Short", "Long", 0.35f, 0f, 1f), + new VisualParam(619, "Pants Length", 0, "underpants", "", "Short", "Long", 0.3f, 0f, 1f), + new VisualParam(624, "Pants Waist", 0, "underpants", "", "Low", "High", 0.8f, 0f, 1f), + new VisualParam(625, "Leg_Pantflair", 0, "pants", "Cuff Flare", "Tight Cuffs", "Flared Cuffs", 0f, 0f, 1.5f), + new VisualParam(629, "Forehead Angle", 0, "shape", "", "More Vertical", "More Sloped", 0.5f, 0f, 1f), + new VisualParam(637, "Body Fat", 0, "shape", "", "Less Body Fat", "More Body Fat", 0f, 0f, 1f), + new VisualParam(638, "Low_Crotch", 0, "pants", "Pants Crotch", "High and Tight", "Low and Loose", 0f, 0f, 1.3f), + new VisualParam(646, "Egg_Head", 0, "shape", "Egg Head", "Chin Heavy", "Forehead Heavy", 0f, -1.3f, 1f), + new VisualParam(647, "Squash_Stretch_Head", 0, "shape", "Head Stretch", "Squash Head", "Stretch Head", 0f, -0.5f, 1f), + new VisualParam(649, "Torso Muscles", 0, "shape", "Torso Muscles", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), + new VisualParam(650, "Eyelid_Corner_Up", 0, "shape", "Outer Eye Corner", "Corner Down", "Corner Up", -1.3f, -1.3f, 1.2f), + new VisualParam(652, "Leg Muscles", 0, "shape", "", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), + new VisualParam(653, "Tall_Lips", 0, "shape", "Lip Fullness", "Less Full", "More Full", -1f, -1f, 2f), + new VisualParam(654, "Shoe_Toe_Thick", 0, "shoes", "Toe Thickness", "Flat Toe", "Thick Toe", 0f, 0f, 2f), + new VisualParam(656, "Crooked_Nose", 0, "shape", "Crooked Nose", "Nose Left", "Nose Right", -2f, -2f, 2f), + new VisualParam(659, "Mouth Corner", 0, "shape", "", "Corner Down", "Corner Up", 0.5f, 0f, 1f), + new VisualParam(662, "Face Shear", 0, "shape", "", "Shear Right Up", "Shear Left Up", 0.5f, 0f, 1f), + new VisualParam(663, "Shift_Mouth", 0, "shape", "Shift Mouth", "Shift Left", "Shift Right", 0f, -2f, 2f), + new VisualParam(664, "Pop_Eye", 0, "shape", "Eye Pop", "Pop Right Eye", "Pop Left Eye", 0f, -1.3f, 1.3f), + new VisualParam(665, "Jaw_Jut", 0, "shape", "Jaw Jut", "Overbite", "Underbite", 0f, -2f, 2f), + new VisualParam(674, "Hair_Shear_Back", 0, "hair", "Shear Back", "Full Back", "Sheared Back", -0.3f, -1f, 2f), + new VisualParam(675, "Hand Size", 0, "shape", "", "Small Hands", "Large Hands", -0.3f, -0.3f, 0.3f), + new VisualParam(676, "Love_Handles", 0, "shape", "Love Handles", "Less Love", "More Love", 0f, -1f, 2f), + new VisualParam(678, "Torso Muscles", 0, "shape", "", "Less Muscular", "More Muscular", 0.5f, 0f, 1f), + new VisualParam(682, "Head Size", 0, "shape", "Head Size", "Small Head", "Big Head", 0.5f, 0f, 1f), + new VisualParam(683, "Neck Thickness", 0, "shape", "", "Skinny Neck", "Thick Neck", -0.15f, -0.4f, 0.2f), + new VisualParam(684, "Breast_Female_Cleavage", 0, "shape", "Breast Cleavage", "Separate", "Join", 0f, -0.3f, 1.3f), + new VisualParam(685, "Chest_Male_No_Pecs", 0, "shape", "Pectorals", "Big Pectorals", "Sunken Chest", 0f, -0.5f, 1.1f), + new VisualParam(690, "Eye Size", 0, "shape", "Eye Size", "Beady Eyes", "Anime Eyes", 0.5f, 0f, 1f), + new VisualParam(692, "Leg Length", 0, "shape", "", "Short Legs", "Long Legs", -1f, -1f, 1f), + new VisualParam(693, "Arm Length", 0, "shape", "", "Short Arms", "Long arms", 0.6f, -1f, 1f), + new VisualParam(700, "Lipstick Color", 0, "skin", "", "Pink", "Black", 0.25f, 0f, 1f), + new VisualParam(701, "Lipstick", 0, "skin", "", "No Lipstick", "More Lipstick", 0f, 0f, 0.9f), + new VisualParam(702, "Lipgloss", 0, "skin", "", "No Lipgloss", "Glossy", 0f, 0f, 1f), + new VisualParam(703, "Eyeliner", 0, "skin", "", "No Eyeliner", "Full Eyeliner", 0f, 0f, 1f), + new VisualParam(704, "Blush", 0, "skin", "", "No Blush", "More Blush", 0f, 0f, 0.9f), + new VisualParam(705, "Blush Color", 0, "skin", "", "Pink", "Orange", 0.5f, 0f, 1f), + new VisualParam(706, "Out Shdw Opacity", 0, "skin", "", "Clear", "Opaque", 0.6f, 0.2f, 1f), + new VisualParam(707, "Outer Shadow", 0, "skin", "", "No Eyeshadow", "More Eyeshadow", 0f, 0f, 0.7f), + new VisualParam(708, "Out Shdw Color", 0, "skin", "", "Light", "Dark", 0f, 0f, 1f), + new VisualParam(709, "Inner Shadow", 0, "skin", "", "No Eyeshadow", "More Eyeshadow", 0f, 0f, 1f), + new VisualParam(710, "Nail Polish", 0, "skin", "", "No Polish", "Painted Nails", 0f, 0f, 1f), + new VisualParam(711, "Blush Opacity", 0, "skin", "", "Clear", "Opaque", 0.5f, 0f, 1f), + new VisualParam(712, "In Shdw Color", 0, "skin", "", "Light", "Dark", 0f, 0f, 1f), + new VisualParam(713, "In Shdw Opacity", 0, "skin", "", "Clear", "Opaque", 0.7f, 0.2f, 1f), + new VisualParam(714, "Eyeliner Color", 0, "skin", "", "Dark Green", "Black", 0f, 0f, 1f), + new VisualParam(715, "Nail Polish Color", 0, "skin", "", "Pink", "Black", 0f, 0f, 1f), + new VisualParam(750, "Eyebrow Density", 0, "hair", "", "Sparse", "Dense", 0.7f, 0f, 1f), + new VisualParam(752, "Hair Thickness", 0, "hair", "", "5 O'Clock Shadow", "Bushy Hair", 0.5f, 0f, 1f), + new VisualParam(753, "Saddlebags", 0, "shape", "Saddle Bags", "Less Saddle", "More Saddle", 0f, -0.5f, 3f), + new VisualParam(754, "Hair_Taper_Back", 0, "hair", "Taper Back", "Wide Back", "Narrow Back", 0f, -1f, 2f), + new VisualParam(755, "Hair_Taper_Front", 0, "hair", "Taper Front", "Wide Front", "Narrow Front", 0.05f, -1.5f, 1.5f), + new VisualParam(756, "Neck Length", 0, "shape", "", "Short Neck", "Long Neck", 0f, -1f, 1f), + new VisualParam(757, "Lower_Eyebrows", 0, "hair", "Eyebrow Height", "Higher", "Lower", -1f, -4f, 2f), + new VisualParam(758, "Lower_Bridge_Nose", 0, "shape", "Lower Bridge", "Low", "High", -1.5f, -1.5f, 1.5f), + new VisualParam(759, "Low_Septum_Nose", 0, "shape", "Nostril Division", "High", "Low", 0.5f, -1f, 1.5f), + new VisualParam(760, "Jaw_Angle", 0, "shape", "Jaw Angle", "Low Jaw", "High Jaw", 0f, -1.2f, 2f), + new VisualParam(762, "Hair_Shear_Front", 0, "hair", "Shear Front", "Full Front", "Sheared Front", 0f, 0f, 3f), + new VisualParam(763, "Hair Volume", 0, "hair", "", "Less Volume", "More Volume", 0.55f, 0f, 1f), + new VisualParam(764, "Lip_Cleft_Deep", 0, "shape", "Lip Cleft Depth", "Shallow", "Deep", -0.5f, -0.5f, 1.2f), + new VisualParam(765, "Puffy_Lower_Lids", 0, "shape", "Puffy Eyelids", "Flat", "Puffy", -0.3f, -0.3f, 2.5f), + new VisualParam(769, "Eye Depth", 0, "shape", "", "Sunken Eyes", "Bugged Eyes", 0.5f, 0f, 1f), + new VisualParam(773, "Head Length", 0, "shape", "", "Flat Head", "Long Head", 0.5f, 0f, 1f), + new VisualParam(775, "Body Freckles", 0, "skin", "", "Less Freckles", "More Freckles", 0f, 0f, 1f), + new VisualParam(779, "Collar Back", 0, "undershirt", "", "Low", "High", 0.84f, 0f, 1f), + new VisualParam(780, "Collar Back", 0, "jacket", "", "Low", "High", 0.8f, 0f, 1f), + new VisualParam(781, "Collar Back", 0, "shirt", "", "Low", "High", 0.78f, 0f, 1f), + new VisualParam(785, "Pigtails", 0, "hair", "", "Short Pigtails", "Long Pigtails", 0f, 0f, 1f), + new VisualParam(789, "Ponytail", 0, "hair", "", "Short Ponytail", "Long Ponytail", 0f, 0f, 1f), + new VisualParam(795, "Butt Size", 0, "shape", "Butt Size", "Flat Butt", "Big Butt", 0.25f, 0f, 1f), + new VisualParam(796, "Pointy_Ears", 0, "shape", "Ear Tips", "Flat", "Pointy", -0.4f, -0.4f, 3f), + new VisualParam(799, "Lip Ratio", 0, "shape", "Lip Ratio", "More Upper Lip", "More Lower Lip", 0.5f, 0f, 1f), + new VisualParam(800, "Sleeve Length", 0, "shirt", "", "Short", "Long", 0.89f, 0f, 1f), + new VisualParam(801, "Shirt Bottom", 0, "shirt", "", "Short", "Long", 1f, 0f, 1f), + new VisualParam(802, "Collar Front", 0, "shirt", "", "Low", "High", 0.78f, 0f, 1f), + new VisualParam(803, "shirt_red", 0, "shirt", "", "", "", 1f, 0f, 1f), + new VisualParam(804, "shirt_green", 0, "shirt", "", "", "", 1f, 0f, 1f), + new VisualParam(805, "shirt_blue", 0, "shirt", "", "", "", 1f, 0f, 1f), + new VisualParam(806, "pants_red", 0, "pants", "", "", "", 1f, 0f, 1f), + new VisualParam(807, "pants_green", 0, "pants", "", "", "", 1f, 0f, 1f), + new VisualParam(808, "pants_blue", 0, "pants", "", "", "", 1f, 0f, 1f), + new VisualParam(812, "shoes_red", 0, "shoes", "", "", "", 1f, 0f, 1f), + new VisualParam(813, "shoes_green", 0, "shoes", "", "", "", 1f, 0f, 1f), + new VisualParam(814, "Waist Height", 0, "pants", "", "Low", "High", 1f, 0f, 1f), + new VisualParam(815, "Pants Length", 0, "pants", "", "Short", "Long", 0.8f, 0f, 1f), + new VisualParam(816, "Loose Lower Clothing", 0, "pants", "Pants Fit", "Tight Pants", "Loose Pants", 0f, 0f, 1f), + new VisualParam(817, "shoes_blue", 0, "shoes", "", "", "", 1f, 0f, 1f), + new VisualParam(818, "socks_red", 0, "socks", "", "", "", 1f, 0f, 1f), + new VisualParam(819, "socks_green", 0, "socks", "", "", "", 1f, 0f, 1f), + new VisualParam(820, "socks_blue", 0, "socks", "", "", "", 1f, 0f, 1f), + new VisualParam(821, "undershirt_red", 0, "undershirt", "", "", "", 1f, 0f, 1f), + new VisualParam(822, "undershirt_green", 0, "undershirt", "", "", "", 1f, 0f, 1f), + new VisualParam(823, "undershirt_blue", 0, "undershirt", "", "", "", 1f, 0f, 1f), + new VisualParam(824, "underpants_red", 0, "underpants", "", "", "", 1f, 0f, 1f), + new VisualParam(825, "underpants_green", 0, "underpants", "", "", "", 1f, 0f, 1f), + new VisualParam(826, "underpants_blue", 0, "underpants", "", "", "", 1f, 0f, 1f), + new VisualParam(827, "gloves_red", 0, "gloves", "", "", "", 1f, 0f, 1f), + new VisualParam(828, "Loose Upper Clothing", 0, "shirt", "Shirt Fit", "Tight Shirt", "Loose Shirt", 0f, 0f, 1f), + new VisualParam(829, "gloves_green", 0, "gloves", "", "", "", 1f, 0f, 1f), + new VisualParam(830, "gloves_blue", 0, "gloves", "", "", "", 1f, 0f, 1f), + new VisualParam(834, "jacket_red", 0, "jacket", "", "", "", 1f, 0f, 1f), + new VisualParam(835, "jacket_green", 0, "jacket", "", "", "", 1f, 0f, 1f), + new VisualParam(836, "jacket_blue", 0, "jacket", "", "", "", 1f, 0f, 1f), + new VisualParam(840, "Shirtsleeve_flair", 0, "shirt", "Sleeve Looseness", "Tight Sleeves", "Loose Sleeves", 0f, 0f, 1.5f), + new VisualParam(841, "Bowed_Legs", 0, "shape", "Knee Angle", "Knock Kneed", "Bow Legged", 0f, -1f, 1f), + new VisualParam(842, "Hip Length", 0, "shape", "", "Short hips", "Long Hips", -1f, -1f, 1f), + new VisualParam(844, "Glove Fingers", 0, "gloves", "", "Fingerless", "Fingers", 1f, 0.01f, 1f), + new VisualParam(848, "skirt_bustle", 0, "skirt", "bustle skirt", "no bustle", "more bustle", 0.2f, 0f, 2f), + new VisualParam(858, "Skirt Length", 0, "skirt", "", "Short", "Long", 0.4f, 0.01f, 1f), + new VisualParam(859, "Slit Front", 0, "skirt", "", "Open Front", "Closed Front", 1f, 0f, 1f), + new VisualParam(860, "Slit Back", 0, "skirt", "", "Open Back", "Closed Back", 1f, 0f, 1f), + new VisualParam(861, "Slit Left", 0, "skirt", "", "Open Left", "Closed Left", 1f, 0f, 1f), + new VisualParam(862, "Slit Right", 0, "skirt", "", "Open Right", "Closed Right", 1f, 0f, 1f), + new VisualParam(863, "skirt_looseness", 0, "skirt", "Skirt Fit", "Tight Skirt", "Poofy Skirt", 0.333f, 0f, 1f), + new VisualParam(868, "Shirt Wrinkles", 0, "shirt", "", "", "", 0f, 0f, 1f), + new VisualParam(869, "Pants Wrinkles", 0, "pants", "", "", "", 0f, 0f, 1f), + new VisualParam(877, "Jacket Wrinkles", 0, "jacket", "Jacket Wrinkles", "No Wrinkles", "Wrinkles", 0f, 0f, 1f), + new VisualParam(879, "Male_Package", 0, "shape", "Package", "Coin Purse", "Duffle Bag", 0f, -0.5f, 2f), + new VisualParam(880, "Eyelid_Inner_Corner_Up", 0, "shape", "Inner Eye Corner", "Corner Down", "Corner Up", -1.3f, -1.3f, 1.2f), + new VisualParam(921, "skirt_red", 0, "skirt", "", "", "", 1f, 0f, 1f), + new VisualParam(922, "skirt_green", 0, "skirt", "", "", "", 1f, 0f, 1f), + new VisualParam(923, "skirt_blue", 0, "skirt", "", "", "", 1f, 0f, 1f), + }; + } +} diff --git a/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.mdp b/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.mdp index 1de08c62..ad5de62f 100644 --- a/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.mdp +++ b/libsecondlife-cs/libsecondlife.Tests/libsecondlife.Tests.mdp @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsecondlife-cs/libsecondlife.Utilities/libsecondlife.Utilities.mdp b/libsecondlife-cs/libsecondlife.Utilities/libsecondlife.Utilities.mdp index b4a8f5bf..30d6db59 100644 --- a/libsecondlife-cs/libsecondlife.Utilities/libsecondlife.Utilities.mdp +++ b/libsecondlife-cs/libsecondlife.Utilities/libsecondlife.Utilities.mdp @@ -1,27 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file