/* * Copyright (c) 2006-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 { #region Structs /// /// Avatar group management /// public struct GroupMember { /// Key of Group Member public LLUUID ID; /// Total land contribution public int Contribution; /// Online status information public string OnlineStatus; /// Abilities that the Group Member has public GroupPowers Powers; /// Current group title public string Title; /// Is a group owner public bool IsOwner; } /// /// Role manager for a group /// public struct GroupRole { /// Key of Role public LLUUID ID; /// Name of Role public string Name; /// Group Title associated with Role public string Title; /// Description of Role public string Description; /// Abilities Associated with Role public GroupPowers Powers; /// /// Returns the role's title /// /// The role's title public override string ToString() { return Name; } } /// /// Class to represent Group Title /// public struct GroupTitle { /// Group Title public string Title; /// Whether title is Active public bool Selected; } /// /// Represents a group in Second Life /// public struct Group { /// Key of Group public LLUUID ID; /// Key of Group Insignia public LLUUID InsigniaID; /// Key of Group Founder public LLUUID FounderID; /// Key of Group Role for Owners public LLUUID OwnerRole; /// Name of Group public string Name; /// Text of Group Charter public string Charter; /// Title of "everyone" role public string MemberTitle; /// Is the group open for enrolement to everyone public bool OpenEnrollment; /// Will group show up in search public bool ShowInList; /// public GroupPowers Powers; /// public bool AcceptNotices; /// public bool AllowPublish; /// Is the group Mature public bool MaturePublish; /// Cost of group membership public int MembershipFee; /// public int Money; /// public int Contribution; /// public int GroupMembershipCount; /// public int GroupRolesCount; /// /// Returns the name of the group /// /// public override string ToString() { return Name; } } /// /// Profile of a group /// public struct GroupProfile { /// public LLUUID ID; /// Key of Group Insignia public LLUUID InsigniaID; /// Key of Group Founder public LLUUID FounderID; /// Key of Group Role for Owners public LLUUID OwnerRole; /// Name of Group public string Name; /// Text of Group Charter public string Charter; /// public string MemberTitle; /// public bool OpenEnrollment; /// public bool ShowInList; /// public GroupPowers Powers; /// public bool AcceptNotices; /// public bool AllowPublish; /// public bool MaturePublish; /// public int MembershipFee; /// public int Money; /// public int Contribution; /// public int GroupMembershipCount; /// public int GroupRolesCount; } /// /// A group Vote /// public struct Vote { /// Key of Avatar who created Vote public LLUUID Candidate; /// Text of the Vote proposal public string VoteString; /// Total number of votes public int NumVotes; } public struct GroupProposal { public string VoteText; public int Quorum; public float Majority; public int Duration; } /// /// /// public struct GroupAccountSummary { /// public int IntervalDays; /// public int CurrentInterval; /// public string StartDate; /// public int Balance; /// public int TotalCredits; /// public int TotalDebits; /// public int ObjectTaxCurrent; /// public int LightTaxCurrent; /// public int LandTaxCurrent; /// public int GroupTaxCurrent; /// public int ParcelDirFeeCurrent; /// public int ObjectTaxEstimate; /// public int LightTaxEstimate; /// public int LandTaxEstimate; /// public int GroupTaxEstimate; /// public int ParcelDirFeeEstimate; /// public int NonExemptMembers; /// public string LastTaxDate; /// public string TaxDate; } /// /// /// public struct GroupAccountDetails { /// public int IntervalDays; /// public int CurrentInterval; /// public string StartDate; /// A list of description/amount pairs making up the account /// history public List> HistoryItems; } /// /// /// public struct GroupAccountTransactions { /// public int IntervalDays; /// public int CurrentInterval; /// public string StartDate; /// List of all the transactions for this group public List Transactions; } /// /// A single transaction made by a group /// public struct Transaction { /// public string Time; /// public string User; /// public int Type; /// public string Item; /// public int Amount; } /// /// Struct representing a group notice /// public struct GroupNotice { /// public string Subject; /// public string Message; /// public LLUUID AttachmentID; /// public LLUUID OwnerID; /// /// /// /// public byte[] SerializeAttachment() { if (OwnerID == LLUUID.Zero || AttachmentID == LLUUID.Zero) return new byte[0]; //I guess this is how this works, no gaurentees string lsd = "" + AttachmentID.ToString() + "" + OwnerID.ToString() + ""; return Helpers.StringToField(lsd); } } #endregion Structs #region Enums /// /// Role update flags /// public enum GroupRoleUpdate : uint { NoUpdate, UpdateData, UpdatePowers, UpdateAll, Create, Delete } /// /// Group role powers flags /// [Flags] public enum GroupRolePowers : long { None = 0, Invite = 1 << 1, Eject = 1 << 2, ChangeOptions = 1 << 3, CreateRole = 1 << 4, DeleteRole = 1 << 5, RoleProperties = 1 << 6, AssignMemberLimited = 1 << 7, AssignMember = 1 << 8, RemoveMember = 1 << 9, ChangeActions = 1 << 10, ChangeIdentity = 1 << 11, LandDeed = 1 << 12, LandRelease = 1 << 13, LandSetSale = 1 << 14, LandDevideJoin = 1 << 15, FindPlaces = 1 << 17, LandChangeIdentity = 1 << 18, SetLandingPoint = 1 << 19, ChangeMedia = 1 << 20, LandEdit = 1 << 21, LandOptions = 1 << 22, AllowEditLand = 1 << 23, AllowFly = 1 << 24, AllowRez = 1 << 25, AllowLandmark = 1 << 26, AllowSetHome = 1 << 28, LandManageAllowed = 1 << 29, LandManageBanned = 1 << 30, LandManagePasses = 1 << 31, LandEjectAndFreeze = 1 << 32, ReturnGroupOwned = 1 << 48, ReturnGroupSet = 1 << 33, ReturnNonGroup = 1 << 34, LandGardening = 1 << 35, DeedObject = 1 << 36, ObjectManipulate = 1 << 38, ObjectSetForSale = 1 << 39, Accountable = 1 << 40, SendNotices = 1 << 42, ReceiveNotices = 1 << 43, StartProposal = 1 << 44, VoteOnProposal = 1 << 45 } [Flags] public enum GroupPowers : ulong { MemberInvite = 2, MemberEject = 4, MemberOptions = 8, MemberVisibleInDir = 140737488355328, RoleCreate = 16, RoleDelete = 32, RoleProperties = 64, RoleAssignMemberLimited = 128, RoleAssignMember = 256, RoleRemoveMember = 512, RoleChangeActions = 1024, GroupChangeIdentity = 2048, LandDeed = 4096, LandRelease = 8192, LandSetSaleInfo = 16384, LandDivideJoin = 32768, LandFindPlaces = 131072, LandChangeIdentity = 262144, LandSetLandingPoint = 524288, LandChangeMedia = 1048576, LandEdit = 2097152, LandOptions = 4194304, LandAllowEditLand = 8388608, LandAllowFly = 16777216, LandAllowCreate = 33554432, LandAllowLandmark = 67108864, LandAllowSetHome = 268435456, LandManageAllowed = 536870912, LandManageBanned = 1073741824, LandManagePasses = 2147483648, LandAdmin = 4294967296, LandReturnGroupOwned = 281474976710656, LandReturnGroupSet = 8589934592, LandReturnNonGroup = 17179869184, LandReturn = LandReturnGroupOwned | LandReturnGroupSet | LandReturnNonGroup, LandGardening = 34359738368, ObjectDeed = 68719476736, ObjectManipulate = 274877906944, ObjectSetSale = 549755813888, AccountingAccountable = 1099511627776, NoticesSend = 4398046511104, NoticesReceive = 8796093022208, ProposalStart = 17592186044416, ProposalVote = 35184372088832 } #endregion Enums /// /// Handles all network traffic related to reading and writing group /// information /// public class GroupManager { #region Delegates /// /// Callback for the list of groups the avatar is currently a member of /// /// public delegate void CurrentGroupsCallback(Dictionary groups); /// /// Callback for a list of group names /// /// public delegate void GroupNamesCallback(Dictionary groupNames); /// /// Callback for the profile of a group /// /// public delegate void GroupProfileCallback(GroupProfile group); /// /// Callback for the member list of a group /// /// public delegate void GroupMembersCallback(Dictionary members); /// /// Callback for the role list of a group /// /// public delegate void GroupRolesCallback(Dictionary roles); /// /// Callback for a pairing of roles to members /// /// public delegate void GroupRolesMembersCallback(List> rolesMembers); /// /// Callback for the title list of a group /// /// public delegate void GroupTitlesCallback(Dictionary titles); /// /// /// /// public delegate void GroupAccountSummaryCallback(GroupAccountSummary summary); /// /// /// /// public delegate void GroupAccountDetailsCallback(GroupAccountDetails details); /// /// /// /// public delegate void GroupAccountTransactionsCallback(GroupAccountTransactions transactions); /// /// /// /// /// /// public delegate void GroupCreatedCallback(LLUUID groupID, bool success, string message); /// /// /// /// /// public delegate void GroupJoinedCallback(LLUUID groupID, bool success); /// /// /// /// /// public delegate void GroupLeftCallback(LLUUID groupID, bool success); /// /// /// /// public delegate void GroupDroppedCallback(LLUUID groupID); #endregion Delegates #region Events /// public event CurrentGroupsCallback OnCurrentGroups; /// public event GroupNamesCallback OnGroupNames; /// public event GroupProfileCallback OnGroupProfile; /// public event GroupMembersCallback OnGroupMembers; /// public event GroupRolesCallback OnGroupRoles; /// public event GroupRolesMembersCallback OnGroupRolesMembers; /// public event GroupTitlesCallback OnGroupTitles; /// public event GroupAccountSummaryCallback OnGroupAccountSummary; /// public event GroupAccountDetailsCallback OnGroupAccountDetails; /// public event GroupCreatedCallback OnGroupCreated; /// public event GroupJoinedCallback OnGroupJoined; /// public event GroupLeftCallback OnGroupLeft; /// public event GroupDroppedCallback OnGroupDropped; #endregion Events private SecondLife Client; /// A list of all the lists of group members, indexed by the request ID private Dictionary> GroupMembersCaches; /// A list of all the lists of group roles, indexed by the request ID private Dictionary> GroupRolesCaches; /// A list of all the role to member mappings private Dictionary>> GroupRolesMembersCaches; /// Caches group name lookups public InternalDictionary GroupName2KeyCache; /// /// /// /// public GroupManager(SecondLife client) { Client = client; GroupMembersCaches = new Dictionary>(); GroupRolesCaches = new Dictionary>(); GroupRolesMembersCaches = new Dictionary>>(); GroupName2KeyCache = new InternalDictionary(); Client.Network.RegisterCallback(PacketType.AgentGroupDataUpdate, new NetworkManager.PacketCallback(GroupDataHandler)); Client.Network.RegisterCallback(PacketType.AgentDropGroup, new NetworkManager.PacketCallback(AgentDropGroupHandler)); Client.Network.RegisterCallback(PacketType.GroupTitlesReply, new NetworkManager.PacketCallback(GroupTitlesHandler)); Client.Network.RegisterCallback(PacketType.GroupProfileReply, new NetworkManager.PacketCallback(GroupProfileHandler)); Client.Network.RegisterCallback(PacketType.GroupMembersReply, new NetworkManager.PacketCallback(GroupMembersHandler)); Client.Network.RegisterCallback(PacketType.GroupRoleDataReply, new NetworkManager.PacketCallback(GroupRoleDataHandler)); Client.Network.RegisterCallback(PacketType.GroupRoleMembersReply, new NetworkManager.PacketCallback(GroupRoleMembersHandler)); Client.Network.RegisterCallback(PacketType.GroupActiveProposalItemReply, new NetworkManager.PacketCallback(GroupActiveProposalItemHandler)); Client.Network.RegisterCallback(PacketType.GroupVoteHistoryItemReply, new NetworkManager.PacketCallback(GroupVoteHistoryItemHandler)); Client.Network.RegisterCallback(PacketType.GroupAccountSummaryReply, new NetworkManager.PacketCallback(GroupAccountSummaryHandler)); Client.Network.RegisterCallback(PacketType.GroupAccountDetailsReply, new NetworkManager.PacketCallback(GroupAccountDetailsHandler)); Client.Network.RegisterCallback(PacketType.GroupAccountTransactionsReply, new NetworkManager.PacketCallback(GroupAccountTransactionsHandler)); Client.Network.RegisterCallback(PacketType.CreateGroupReply, new NetworkManager.PacketCallback(CreateGroupReplyHandler)); Client.Network.RegisterCallback(PacketType.JoinGroupReply, new NetworkManager.PacketCallback(JoinGroupReplyHandler)); Client.Network.RegisterCallback(PacketType.LeaveGroupReply, new NetworkManager.PacketCallback(LeaveGroupReplyHandler)); Client.Network.RegisterCallback(PacketType.UUIDGroupNameReply, new NetworkManager.PacketCallback(UUIDGroupNameReplyHandler)); } /// /// Request a current list of groups the avatar is a member of. /// /// CAPS Event Queue must be running for this to work since the results /// come across CAPS. public void RequestCurrentGroups() { AgentDataUpdateRequestPacket request = new AgentDataUpdateRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; Client.Network.SendPacket(request); } /// /// Lookup name of group based on groupID /// /// groupID of group to lookup name for. public void RequestGroupName(LLUUID groupID) { // if we already have this in the cache, return from cache instead of making a request if (GroupName2KeyCache.ContainsKey(groupID)) { Dictionary groupNames = new Dictionary(); lock(GroupName2KeyCache.Dictionary) groupNames.Add(groupID, GroupName2KeyCache.Dictionary[groupID]); if (OnGroupNames != null) { try { OnGroupNames(groupNames); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } else { UUIDGroupNameRequestPacket req = new UUIDGroupNameRequestPacket(); UUIDGroupNameRequestPacket.UUIDNameBlockBlock[] block = new UUIDGroupNameRequestPacket.UUIDNameBlockBlock[1]; block[0] = new UUIDGroupNameRequestPacket.UUIDNameBlockBlock(); block[0].ID = groupID; req.UUIDNameBlock = block; Client.Network.SendPacket(req); } } /// /// Request lookup of multiple group names /// /// List of group IDs to request. public void RequestGroupNames(List groupIDs) { Dictionary groupNames = new Dictionary(); foreach (LLUUID groupID in groupIDs) if (GroupName2KeyCache.ContainsKey(groupID)) { groupIDs.Remove(groupID); lock (GroupName2KeyCache.Dictionary) groupNames.Add(groupID, GroupName2KeyCache.Dictionary[groupID]); } if (groupIDs.Count > 0) { UUIDGroupNameRequestPacket req = new UUIDGroupNameRequestPacket(); UUIDGroupNameRequestPacket.UUIDNameBlockBlock[] block = new UUIDGroupNameRequestPacket.UUIDNameBlockBlock[groupIDs.Count]; for (int i = 0; i < groupIDs.Count; i++) { block[i] = new UUIDGroupNameRequestPacket.UUIDNameBlockBlock(); block[i].ID = groupIDs[i]; } req.UUIDNameBlock = block; Client.Network.SendPacket(req); } // fire handler from cache if(groupNames.Count > 0 && OnGroupNames != null) try { OnGroupNames(groupNames); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } /// Lookup group profile data such as name, enrollment, founder, logo, etc /// Subscribe to OnGroupProfile event to receive the results. /// group ID (UUID) public void RequestGroupProfile(LLUUID group) { GroupProfileRequestPacket request = new GroupProfileRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.GroupData.GroupID = group; Client.Network.SendPacket(request); } /// Request a list of group members. /// Subscribe to OnGroupMembers event to receive the results. /// group ID (UUID) public void RequestGroupMembers(LLUUID group) { LLUUID requestID = LLUUID.Random(); lock (GroupMembersCaches) GroupMembersCaches[requestID] = new Dictionary(); GroupMembersRequestPacket request = new GroupMembersRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.GroupData.GroupID = group; request.GroupData.RequestID = requestID; Client.Network.SendPacket(request); } /// Request group roles /// Subscribe to OnGroupRoles event to receive the results. /// group ID (UUID) public void RequestGroupRoles(LLUUID group) { LLUUID requestID = LLUUID.Random(); lock (GroupRolesCaches) GroupRolesCaches[requestID] = new Dictionary(); GroupRoleDataRequestPacket request = new GroupRoleDataRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.GroupData.GroupID = group; request.GroupData.RequestID = requestID; Client.Network.SendPacket(request); } /// Request members (members,role) role mapping for a group. /// Subscribe to OnGroupRolesMembers event to receive the results. /// group ID (UUID) public void RequestGroupRoleMembers(LLUUID group) { LLUUID requestID = LLUUID.Random(); lock (GroupRolesMembersCaches) { GroupRolesMembersCaches[requestID] = new List>(); } GroupRoleMembersRequestPacket request = new GroupRoleMembersRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.GroupData.GroupID = group; request.GroupData.RequestID = requestID; Client.Network.SendPacket(request); } /// Request a groups Titles /// Subscribe to OnGroupTitles event to receive the results. /// group ID (UUID) public void RequestGroupTitles(LLUUID group) { LLUUID requestID = LLUUID.Random(); GroupTitlesRequestPacket request = new GroupTitlesRequestPacket(); request.AgentData.AgentID = Client.Self.AgentID; request.AgentData.SessionID = Client.Self.SessionID; request.AgentData.GroupID = group; request.AgentData.RequestID = requestID; Client.Network.SendPacket(request); } /// Begin to get the group account summary /// Subscribe to the OnGroupAccountSummary event to receive the results. /// group ID (UUID) /// How long of an interval /// Which interval (0 for current, 1 for last) public void RequestGroupAccountSummary(LLUUID group, int intervalDays, int currentInterval) { GroupAccountSummaryRequestPacket p = new GroupAccountSummaryRequestPacket(); p.AgentData.AgentID = Client.Self.AgentID; p.AgentData.SessionID = Client.Self.SessionID; p.AgentData.GroupID = group; p.MoneyData.RequestID = LLUUID.Random(); p.MoneyData.CurrentInterval = currentInterval; p.MoneyData.IntervalDays = intervalDays; Client.Network.SendPacket(p); } /// Invites a user to a group /// The group to invite to /// A list of roles to invite a person to /// Key of person to invite public void Invite(LLUUID group, List roles, LLUUID personkey) { InviteGroupRequestPacket igp = new InviteGroupRequestPacket(); igp.AgentData = new InviteGroupRequestPacket.AgentDataBlock(); igp.AgentData.AgentID = Client.Self.AgentID; igp.AgentData.SessionID = Client.Self.SessionID; igp.GroupData = new InviteGroupRequestPacket.GroupDataBlock(); igp.GroupData.GroupID = group; igp.InviteData = new InviteGroupRequestPacket.InviteDataBlock[roles.Count]; for (int i = 0; i < roles.Count; i++) { igp.InviteData[i] = new InviteGroupRequestPacket.InviteDataBlock(); igp.InviteData[i].InviteeID = personkey; igp.InviteData[i].RoleID = roles[i]; } Client.Network.SendPacket(igp); } /// Set a group as the current active group /// group ID (UUID) public void ActivateGroup(LLUUID id) { ActivateGroupPacket activate = new ActivateGroupPacket(); activate.AgentData.AgentID = Client.Self.AgentID; activate.AgentData.SessionID = Client.Self.SessionID; activate.AgentData.GroupID = id; Client.Network.SendPacket(activate); } /// Change the role that determines your active title /// Group ID to use /// Role ID to change to public void ActivateTitle(LLUUID group, LLUUID role) { GroupTitleUpdatePacket gtu = new GroupTitleUpdatePacket(); gtu.AgentData.AgentID = Client.Self.AgentID; gtu.AgentData.SessionID = Client.Self.SessionID; gtu.AgentData.TitleRoleID = role; gtu.AgentData.GroupID = group; Client.Network.SendPacket(gtu); } /// Set this avatar's tier contribution /// Group ID to change tier in /// amount of tier to donate public void SetGroupContribution(LLUUID group, int contribution) { SetGroupContributionPacket sgp = new SetGroupContributionPacket(); sgp.AgentData.AgentID = Client.Self.AgentID; sgp.AgentData.SessionID = Client.Self.SessionID; sgp.Data.GroupID = group; sgp.Data.Contribution = contribution; Client.Network.SendPacket(sgp); } /// Request to join a group /// Subscribe to OnGroupJoined event for confirmation. /// group ID (UUID) to join. public void RequestJoinGroup(LLUUID id) { JoinGroupRequestPacket join = new JoinGroupRequestPacket(); join.AgentData.AgentID = Client.Self.AgentID; join.AgentData.SessionID = Client.Self.SessionID; join.GroupData.GroupID = id; Client.Network.SendPacket(join); } /// /// Request to create a new group. If the group is successfully /// created, L$100 will automatically be deducted /// /// Subscribe to OnGroupCreated event to receive confirmation. /// Group struct containing the new group info public void RequestCreateGroup(Group group) { libsecondlife.Packets.CreateGroupRequestPacket cgrp = new CreateGroupRequestPacket(); //Fill in agent data cgrp.AgentData = new CreateGroupRequestPacket.AgentDataBlock(); cgrp.AgentData.AgentID = Client.Self.AgentID; cgrp.AgentData.SessionID = Client.Self.SessionID; //Fill in group data cgrp.GroupData = new CreateGroupRequestPacket.GroupDataBlock(); cgrp.GroupData.AllowPublish = group.AllowPublish; cgrp.GroupData.Charter = Helpers.StringToField(group.Charter); cgrp.GroupData.InsigniaID = group.InsigniaID; cgrp.GroupData.MaturePublish = group.MaturePublish; cgrp.GroupData.MembershipFee = group.MembershipFee; cgrp.GroupData.Name = Helpers.StringToField(group.Name); cgrp.GroupData.OpenEnrollment = group.OpenEnrollment; cgrp.GroupData.ShowInList = group.ShowInList; //Send it Client.Network.SendPacket(cgrp); } /// Update a group's profile and other information /// Groups ID (UUID) to update. /// Group struct to update. public void UpdateGroup(LLUUID id, Group group) { libsecondlife.Packets.UpdateGroupInfoPacket cgrp = new UpdateGroupInfoPacket(); //Fill in agent data cgrp.AgentData = new UpdateGroupInfoPacket.AgentDataBlock(); cgrp.AgentData.AgentID = Client.Self.AgentID; cgrp.AgentData.SessionID = Client.Self.SessionID; //Fill in group data cgrp.GroupData = new UpdateGroupInfoPacket.GroupDataBlock(); cgrp.GroupData.GroupID = id; cgrp.GroupData.AllowPublish = group.AllowPublish; cgrp.GroupData.Charter = Helpers.StringToField(group.Charter); cgrp.GroupData.InsigniaID = group.InsigniaID; cgrp.GroupData.MaturePublish = group.MaturePublish; cgrp.GroupData.MembershipFee = group.MembershipFee; cgrp.GroupData.OpenEnrollment = group.OpenEnrollment; cgrp.GroupData.ShowInList = group.ShowInList; //Send it Client.Network.SendPacket(cgrp); } /// Eject a user from a group /// Group ID to eject the user from /// Avatar's key to eject public void EjectUser(LLUUID group, LLUUID member) { libsecondlife.Packets.EjectGroupMemberRequestPacket eject = new EjectGroupMemberRequestPacket(); eject.AgentData = new EjectGroupMemberRequestPacket.AgentDataBlock(); eject.AgentData.AgentID = Client.Self.AgentID; eject.AgentData.SessionID = Client.Self.SessionID; //Group eject.GroupData = new EjectGroupMemberRequestPacket.GroupDataBlock(); eject.GroupData.GroupID = group; //People to eject eject.EjectData = new EjectGroupMemberRequestPacket.EjectDataBlock[1]; eject.EjectData[0] = new EjectGroupMemberRequestPacket.EjectDataBlock(); eject.EjectData[0].EjecteeID = member; //send it Client.Network.SendPacket(eject); } /// Update role information /// Group to update /// Role to update public void UpdateRole(LLUUID group, GroupRole role) { libsecondlife.Packets.GroupRoleUpdatePacket gru = new GroupRoleUpdatePacket(); gru.AgentData.AgentID = Client.Self.AgentID; gru.AgentData.SessionID = Client.Self.SessionID; gru.AgentData.GroupID = group; gru.RoleData = new GroupRoleUpdatePacket.RoleDataBlock[1]; gru.RoleData[0].Name = Helpers.StringToField(role.Name); gru.RoleData[0].Description = Helpers.StringToField(role.Description); gru.RoleData[0].Powers = (ulong)role.Powers; gru.RoleData[0].Title = Helpers.StringToField(role.Title); gru.RoleData[0].UpdateType = (byte)GroupRoleUpdate.UpdateAll; Client.Network.SendPacket(gru); } /// Create a new group role /// Group ID to update /// Role to create public void CreateRole(LLUUID group, GroupRole role) { libsecondlife.Packets.GroupRoleUpdatePacket gru = new GroupRoleUpdatePacket(); gru.AgentData.AgentID = Client.Self.AgentID; gru.AgentData.SessionID = Client.Self.SessionID; gru.AgentData.GroupID = group; gru.RoleData = new GroupRoleUpdatePacket.RoleDataBlock[1]; gru.RoleData[0].Name = Helpers.StringToField(role.Name); gru.RoleData[0].Description = Helpers.StringToField(role.Description); gru.RoleData[0].Powers = (ulong)role.Powers; gru.RoleData[0].Title = Helpers.StringToField(role.Title); gru.RoleData[0].UpdateType = (byte)GroupRoleUpdate.Create; Client.Network.SendPacket(gru); } /// Remove an avatar from a role /// Group ID to update /// Role ID to be removed from /// Avatar's Key to remove public void RemoveFromRole(LLUUID group, LLUUID role, LLUUID member) { libsecondlife.Packets.GroupRoleChangesPacket grc = new GroupRoleChangesPacket(); grc.AgentData.AgentID = Client.Self.AgentID; grc.AgentData.SessionID = Client.Self.SessionID; grc.AgentData.GroupID = group; grc.RoleChange = new GroupRoleChangesPacket.RoleChangeBlock[1]; grc.RoleChange[0] = new GroupRoleChangesPacket.RoleChangeBlock(); //Add to members and role grc.RoleChange[0].MemberID = member; grc.RoleChange[0].RoleID = role; //1 = Remove From Role grc.RoleChange[0].Change = 1; Client.Network.SendPacket(grc); } /// Assign an avatar to a role /// Group ID to update /// Role ID to assign to /// Avatar's ID to assign to role public void AddToRole(LLUUID group, LLUUID role, LLUUID member) { libsecondlife.Packets.GroupRoleChangesPacket grc = new GroupRoleChangesPacket(); grc.AgentData.AgentID = Client.Self.AgentID; grc.AgentData.SessionID = Client.Self.SessionID; grc.AgentData.GroupID = group; grc.RoleChange = new GroupRoleChangesPacket.RoleChangeBlock[1]; grc.RoleChange[0] = new GroupRoleChangesPacket.RoleChangeBlock(); //Add to members and role grc.RoleChange[0].MemberID = member; grc.RoleChange[0].RoleID = role; //0 = Add to Role grc.RoleChange[0].Change = 0; Client.Network.SendPacket(grc); } /// Send out a group notice /// Group ID to update /// GroupNotice structure containing notice data public void SendGroupNotice(LLUUID group, GroupNotice notice) { Client.Self.InstantMessage(Client.Self.Name, group, notice.Subject + "|" + notice.Message, LLUUID.Zero, InstantMessageDialog.GroupNotice, InstantMessageOnline.Online, LLVector3.Zero, LLUUID.Zero, notice.SerializeAttachment()); } /// Start a group proposal (vote) /// The Group ID to send proposal to /// GroupProposal structure containing the proposal public void StartProposal(LLUUID group, GroupProposal prop) { StartGroupProposalPacket p = new StartGroupProposalPacket(); p.AgentData.AgentID = Client.Self.AgentID; p.AgentData.SessionID = Client.Self.SessionID; p.ProposalData.GroupID = group; p.ProposalData.ProposalText = Helpers.StringToField(prop.VoteText); p.ProposalData.Quorum = prop.Quorum; p.ProposalData.Majority = prop.Majority; p.ProposalData.Duration = prop.Duration; Client.Network.SendPacket(p); } /// Request to leave a group /// Subscribe to OnGroupLeft event to receive confirmation /// The group to leave public void LeaveGroup(LLUUID groupID) { LeaveGroupRequestPacket p = new LeaveGroupRequestPacket(); p.AgentData.AgentID = Client.Self.AgentID; p.AgentData.SessionID = Client.Self.SessionID; p.GroupData.GroupID = groupID; Client.Network.SendPacket(p); } #region Packet Handlers private void GroupDataHandler(Packet packet, Simulator simulator) { if (OnCurrentGroups != null) { AgentGroupDataUpdatePacket update = (AgentGroupDataUpdatePacket)packet; Dictionary currentGroups = new Dictionary(); foreach (AgentGroupDataUpdatePacket.GroupDataBlock block in update.GroupData) { Group group = new Group(); group.ID = block.GroupID; group.InsigniaID = block.GroupInsigniaID; group.Name = Helpers.FieldToUTF8String(block.GroupName); group.Powers = (GroupPowers)block.GroupPowers; group.Contribution = block.Contribution; group.AcceptNotices = block.AcceptNotices; currentGroups[block.GroupID] = group; } try { OnCurrentGroups(currentGroups); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void AgentDropGroupHandler(Packet packet, Simulator simulator) { if (OnGroupDropped != null) { try { OnGroupDropped(((AgentDropGroupPacket)packet).AgentData.GroupID); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupProfileHandler(Packet packet, Simulator simulator) { if (OnGroupProfile != null) { GroupProfileReplyPacket profile = (GroupProfileReplyPacket)packet; GroupProfile group = new GroupProfile(); group.ID = profile.GroupData.GroupID; group.AllowPublish = profile.GroupData.AllowPublish; group.Charter = Helpers.FieldToUTF8String(profile.GroupData.Charter); group.FounderID = profile.GroupData.FounderID; group.GroupMembershipCount = profile.GroupData.GroupMembershipCount; group.GroupRolesCount = profile.GroupData.GroupRolesCount; group.InsigniaID = profile.GroupData.InsigniaID; group.MaturePublish = profile.GroupData.MaturePublish; group.MembershipFee = profile.GroupData.MembershipFee; group.MemberTitle = Helpers.FieldToUTF8String(profile.GroupData.MemberTitle); group.Money = profile.GroupData.Money; group.Name = Helpers.FieldToUTF8String(profile.GroupData.Name); group.OpenEnrollment = profile.GroupData.OpenEnrollment; group.OwnerRole = profile.GroupData.OwnerRole; group.Powers = (GroupPowers)profile.GroupData.PowersMask; group.ShowInList = profile.GroupData.ShowInList; try { OnGroupProfile(group); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupTitlesHandler(Packet packet, Simulator simulator) { if (OnGroupTitles != null) { GroupTitlesReplyPacket titles = (GroupTitlesReplyPacket)packet; Dictionary groupTitleCache = new Dictionary(); foreach (GroupTitlesReplyPacket.GroupDataBlock block in titles.GroupData) { GroupTitle groupTitle = new GroupTitle(); groupTitle.Title = Helpers.FieldToUTF8String(block.Title); groupTitle.Selected = block.Selected; groupTitleCache[block.RoleID] = groupTitle; } try { OnGroupTitles(groupTitleCache); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupMembersHandler(Packet packet, Simulator simulator) { GroupMembersReplyPacket members = (GroupMembersReplyPacket)packet; Dictionary groupMemberCache = null; lock (GroupMembersCaches) { // If nothing is registered to receive this RequestID drop the data if (GroupMembersCaches.ContainsKey(members.GroupData.RequestID)) { groupMemberCache = GroupMembersCaches[members.GroupData.RequestID]; foreach (GroupMembersReplyPacket.MemberDataBlock block in members.MemberData) { GroupMember groupMember = new GroupMember(); groupMember.ID = block.AgentID; groupMember.Contribution = block.Contribution; groupMember.IsOwner = block.IsOwner; groupMember.OnlineStatus = Helpers.FieldToUTF8String(block.OnlineStatus); groupMember.Powers = (GroupPowers)block.AgentPowers; groupMember.Title = Helpers.FieldToUTF8String(block.Title); groupMemberCache[block.AgentID] = groupMember; } } } // Check if we've received all the group members that are showing up if (OnGroupMembers != null && groupMemberCache != null && groupMemberCache.Count >= members.GroupData.MemberCount) { try { OnGroupMembers(groupMemberCache); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupRoleDataHandler(Packet packet, Simulator simulator) { GroupRoleDataReplyPacket roles = (GroupRoleDataReplyPacket)packet; Dictionary groupRoleCache = null; lock (GroupRolesCaches) { // If nothing is registered to receive this RequestID drop the data if (GroupRolesCaches.ContainsKey(roles.GroupData.RequestID)) { groupRoleCache = GroupRolesCaches[roles.GroupData.RequestID]; foreach (GroupRoleDataReplyPacket.RoleDataBlock block in roles.RoleData) { GroupRole groupRole = new GroupRole(); groupRole.ID = block.RoleID; groupRole.Description = Helpers.FieldToUTF8String(block.Description); groupRole.Name = Helpers.FieldToUTF8String(block.Name); groupRole.Powers = (GroupPowers)block.Powers; groupRole.Title = Helpers.FieldToUTF8String(block.Title); groupRoleCache[block.RoleID] = groupRole; } } } // Check if we've received all the group members that are showing up if (OnGroupRoles != null && groupRoleCache != null && groupRoleCache.Count >= roles.GroupData.RoleCount) { try { OnGroupRoles(groupRoleCache); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupRoleMembersHandler(Packet packet, Simulator simulator) { GroupRoleMembersReplyPacket members = (GroupRoleMembersReplyPacket)packet; List> groupRoleMemberCache = null; try { lock (GroupRolesMembersCaches) { // If nothing is registered to receive this RequestID drop the data if (GroupRolesMembersCaches.ContainsKey(members.AgentData.RequestID)) { groupRoleMemberCache = GroupRolesMembersCaches[members.AgentData.RequestID]; foreach (GroupRoleMembersReplyPacket.MemberDataBlock block in members.MemberData) { KeyValuePair rolemember = new KeyValuePair(block.RoleID, block.MemberID); groupRoleMemberCache.Add(rolemember); } } } } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } //Client.DebugLog("Pairs Ratio: " + groupRoleMemberCache.Count + "/" + members.AgentData.TotalPairs); // Check if we've received all the pairs that are showing up if (OnGroupRolesMembers != null && groupRoleMemberCache != null && groupRoleMemberCache.Count >= members.AgentData.TotalPairs) { try { OnGroupRolesMembers(groupRoleMemberCache); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupActiveProposalItemHandler(Packet packet, Simulator simulator) { //GroupActiveProposalItemReplyPacket proposal = (GroupActiveProposalItemReplyPacket)packet; // TODO: Create a proposal struct to represent the fields in a proposal item } private void GroupVoteHistoryItemHandler(Packet packet, Simulator simulator) { //GroupVoteHistoryItemReplyPacket history = (GroupVoteHistoryItemReplyPacket)packet; // TODO: This was broken in the official viewer when I was last trying to work on it } private void GroupAccountSummaryHandler(Packet packet, Simulator simulator) { if (OnGroupAccountSummary != null) { GroupAccountSummaryReplyPacket summary = (GroupAccountSummaryReplyPacket)packet; GroupAccountSummary account = new GroupAccountSummary(); account.Balance = summary.MoneyData.Balance; account.CurrentInterval = summary.MoneyData.CurrentInterval; account.GroupTaxCurrent = summary.MoneyData.GroupTaxCurrent; account.GroupTaxEstimate = summary.MoneyData.GroupTaxEstimate; account.IntervalDays = summary.MoneyData.IntervalDays; account.LandTaxCurrent = summary.MoneyData.LandTaxCurrent; account.LandTaxEstimate = summary.MoneyData.LandTaxEstimate; account.LastTaxDate = Helpers.FieldToUTF8String(summary.MoneyData.LastTaxDate); account.LightTaxCurrent = summary.MoneyData.LightTaxCurrent; account.LightTaxEstimate = summary.MoneyData.LightTaxEstimate; account.NonExemptMembers = summary.MoneyData.NonExemptMembers; account.ObjectTaxCurrent = summary.MoneyData.ObjectTaxCurrent; account.ObjectTaxEstimate = summary.MoneyData.ObjectTaxEstimate; account.ParcelDirFeeCurrent = summary.MoneyData.ParcelDirFeeCurrent; account.ParcelDirFeeEstimate = summary.MoneyData.ParcelDirFeeEstimate; account.StartDate = Helpers.FieldToUTF8String(summary.MoneyData.StartDate); account.TaxDate = Helpers.FieldToUTF8String(summary.MoneyData.TaxDate); account.TotalCredits = summary.MoneyData.TotalCredits; account.TotalDebits = summary.MoneyData.TotalDebits; try { OnGroupAccountSummary(account); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupAccountDetailsHandler(Packet packet, Simulator simulator) { if (OnGroupAccountDetails != null) { GroupAccountDetailsReplyPacket details = (GroupAccountDetailsReplyPacket)packet; GroupAccountDetails account = new GroupAccountDetails(); account.CurrentInterval = details.MoneyData.CurrentInterval; account.IntervalDays = details.MoneyData.IntervalDays; account.StartDate = Helpers.FieldToUTF8String(details.MoneyData.StartDate); account.HistoryItems = new List>(); foreach (GroupAccountDetailsReplyPacket.HistoryDataBlock block in details.HistoryData) { KeyValuePair item = new KeyValuePair(Helpers.FieldToUTF8String(block.Description), block.Amount); account.HistoryItems.Add(item); } try { OnGroupAccountDetails(account); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void GroupAccountTransactionsHandler(Packet packet, Simulator simulator) { GroupAccountTransactionsReplyPacket reply = (GroupAccountTransactionsReplyPacket)packet; Client.Log("Got a GroupAccountTransactionsReply packet, implement this callback!\n" + reply.ToString(), Helpers.LogLevel.Error); } private void CreateGroupReplyHandler(Packet packet, Simulator simulator) { if (OnGroupCreated != null) { CreateGroupReplyPacket reply = (CreateGroupReplyPacket)packet; string message = Helpers.FieldToUTF8String(reply.ReplyData.Message); try { OnGroupCreated(reply.ReplyData.GroupID, reply.ReplyData.Success, message); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void JoinGroupReplyHandler(Packet packet, Simulator simulator) { if (OnGroupJoined != null) { JoinGroupReplyPacket reply = (JoinGroupReplyPacket)packet; try { OnGroupJoined(reply.GroupData.GroupID, reply.GroupData.Success); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void LeaveGroupReplyHandler(Packet packet, Simulator simulator) { if (OnGroupLeft != null) { LeaveGroupReplyPacket reply = (LeaveGroupReplyPacket)packet; try { OnGroupLeft(reply.GroupData.GroupID, reply.GroupData.Success); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void UUIDGroupNameReplyHandler(Packet packet, Simulator simulator) { UUIDGroupNameReplyPacket reply = (UUIDGroupNameReplyPacket)packet; UUIDGroupNameReplyPacket.UUIDNameBlockBlock[] blocks = reply.UUIDNameBlock; Dictionary groupNames = new Dictionary(); foreach (UUIDGroupNameReplyPacket.UUIDNameBlockBlock block in blocks) { groupNames.Add(block.ID, Helpers.FieldToUTF8String(block.GroupName)); if (!GroupName2KeyCache.ContainsKey(block.ID)) GroupName2KeyCache.SafeAdd(block.ID, Helpers.FieldToUTF8String(block.GroupName)); } if (OnGroupNames != null) { try { OnGroupNames(groupNames); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } #endregion Packet Handlers } }