Files
libremetaverse/Programs/Simian/Extensions/SceneManager.cs
John Hurliman c20afbbf80 * Added InventoryItemFlags, which is actually only the upper half of the Flags field for inventory items. Stores slam bits, permission override flags, and other things we don't use at all right now
[Simian]
* Initial task inventory support. Move, remove, and RezScript are not supported yet
* SimulationObject Frozen and RotationAxis properties now point to the root prim in the linkset

git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@2503 52acb1d6-8a22-11de-b505-999d5b087335
2009-03-19 00:25:03 +00:00

1762 lines
73 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using ExtensionLoader;
using HttpServer;
using OpenMetaverse;
using OpenMetaverse.Imaging;
using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Http;
namespace Simian
{
class EventQueueServerCap
{
public EventQueueServer Server;
public Uri Capability;
public EventQueueServerCap(EventQueueServer server, Uri capability)
{
Server = server;
Capability = capability;
}
}
public class SceneManager : ISceneProvider
{
const int TARGET_FRAMES_PER_SECOND = 45;
// Interfaces. Although no other classes will access these interfaces directly
// (getters are used instead), they must be marked public so ExtensionLoader
// can automatically assign them
public IAvatarProvider avatars;
public IParcelProvider parcels;
public IPhysicsProvider physics;
public IScriptEngine scriptEngine;
public ITaskInventoryProvider taskInventory;
public IUDPProvider udp;
public event ObjectAddOrUpdateCallback OnObjectAddOrUpdate;
public event ObjectRemoveCallback OnObjectRemove;
public event ObjectSetRotationAxisCallback OnObjectSetRotationAxis;
public event ObjectApplyImpulseCallback OnObjectApplyImpulse;
public event ObjectApplyRotationalImpulseCallback OnObjectApplyRotationalImpulse;
public event ObjectSetTorqueCallback OnObjectSetTorque;
public event ObjectAnimateCallback OnObjectAnimate;
public event ObjectChatCallback OnObjectChat;
public event ObjectUndoCallback OnObjectUndo;
public event ObjectRedoCallback OnObjectRedo;
public event AgentAddCallback OnAgentAdd;
public event AgentRemoveCallback OnAgentRemove;
public event AgentAppearanceCallback OnAgentAppearance;
public event TriggerSoundCallback OnTriggerSound;
public event TriggerEffectsCallback OnTriggerEffects;
public event TerrainUpdateCallback OnTerrainUpdate;
public event WindUpdateCallback OnWindUpdate;
public Simian Server { get { return server; } }
public IAvatarProvider Avatars { get { return avatars; } }
public IParcelProvider Parcels { get { return parcels; } }
public IPhysicsProvider Physics { get { return physics; } }
public IScriptEngine ScriptEngine { get { return scriptEngine; } }
public ITaskInventoryProvider TaskInventory { get { return taskInventory; } }
public IUDPProvider UDP { get { return udp; } }
public X509Certificate2 RegionCertificate { get { return regionCert; } }
public uint RegionX
{
get { return regionX; }
set
{
regionX = value;
regionHandle = Utils.UIntsToLong(regionX * 256, regionY * 256);
}
}
public uint RegionY
{
get { return regionY; }
set
{
regionY = value;
regionHandle = Utils.UIntsToLong(regionX * 256, regionY * 256);
}
}
public ulong RegionHandle { get { return regionHandle; } }
public UUID RegionID { get { return regionID; } }
public string RegionName { get { return regionName; } set { regionName = value; } }
public RegionFlags RegionFlags { get { return regionFlags; } }
public IPEndPoint IPAndPort { get { return endpoint; } set { endpoint = value; } }
public Vector3 DefaultPosition { get { return defaultPosition; } }
public Vector3 DefaultLookAt { get { return defaultLookAt; } }
public UUID MapTextureID { get { return mapTextureID; } }
public float WaterHeight { get { return waterHeight; } }
public uint TerrainPatchWidth { get { return 16; } }
public uint TerrainPatchHeight { get { return 16; } }
public uint TerrainPatchCountWidth { get { return 16; } }
public uint TerrainPatchCountHeight { get { return 16; } }
public float TimeDilation { get { return TimeDilation; } }
Simian server;
// Contains all scene objects, including prims and avatars
DoubleDictionary<uint, UUID, SimulationObject> sceneObjects = new DoubleDictionary<uint, UUID, SimulationObject>();
// A duplicate of the avatar information stored in sceneObjects, improves operations such as iterating over all agents
Dictionary<UUID, Agent> sceneAgents = new Dictionary<UUID, Agent>();
// Event queues for each avatar in the scene
Dictionary<UUID, EventQueueServerCap> eventQueues = new Dictionary<UUID, EventQueueServerCap>();
int currentLocalID = 1;
X509Certificate2 regionCert;
ulong regionHandle;
RegionFlags regionFlags;
UUID regionID = UUID.Random();
TerrainPatch[,] heightmap = new TerrainPatch[16, 16];
Vector2[,] windSpeeds = new Vector2[16, 16];
ExtensionLoader<ISceneProvider> extensions = new ExtensionLoader<ISceneProvider>();
IPEndPoint endpoint;
uint regionX;
uint regionY;
string regionName;
float waterHeight;
UUID mapTextureID;
Vector3 defaultPosition = new Vector3(128f, 128f, 30f);
Vector3 defaultLookAt = Vector3.UnitZ;
/// <summary>Track the eight neighboring tiles around us</summary>
RegionInfo[] neighbors = new RegionInfo[8];
/// <summary>List of callback URIs for pending client connections. When a new client connection
/// is established for a client in this dictionary, an enable_client_complete message will be
/// sent to the associated URI</summary>
Dictionary<UUID, Uri> enableClientCompleteCallbacks = new Dictionary<UUID, Uri>();
float timeDilation;
bool running;
public SceneManager()
{
}
public bool Start(Simian server, RegionInfo regionInfo, X509Certificate2 regionCert, string defaultTerrainFile, int staticObjects, int physicalObjects)
{
running = true;
this.server = server;
this.regionName = regionInfo.Name;
this.endpoint = regionInfo.IPAndPort;
this.regionID = regionInfo.ID;
this.regionCert = regionCert;
this.regionFlags = regionInfo.Flags;
this.mapTextureID = regionInfo.MapTextureID;
this.waterHeight = regionInfo.WaterHeight;
// Set the properties because this will automatically update the regionHandle
RegionX = regionInfo.X;
RegionY = regionInfo.Y;
#region ISceneProvider Extension Loading
try
{
// Create a list of references for .cs extensions that are compiled at runtime
List<string> references = new List<string>();
references.Add("OpenMetaverseTypes.dll");
references.Add("OpenMetaverse.dll");
references.Add("Simian.exe");
// Load extensions from the current executing assembly, Simian.*.dll assemblies on disk, and
// Simian.*.cs source files on disk.
extensions.LoadAllExtensions(Assembly.GetExecutingAssembly(),
AppDomain.CurrentDomain.BaseDirectory, server.ExtensionList, references,
"Simian.*.dll", "Simian.*.cs");
// Automatically assign extensions that implement interfaces to the list of interface
// variables in "assignables"
extensions.AssignExtensions(this, extensions.GetInterfaces(this));
// Start all of the extensions
foreach (IExtension<ISceneProvider> extension in extensions.Extensions)
{
// Only print the extension names if this is the first loaded scene
if (server.Scenes.Count == 0)
Logger.Log("Starting Scene extension " + extension.GetType().Name, Helpers.LogLevel.Info);
extension.Start(this);
}
}
catch (ExtensionException ex)
{
Logger.Log("SceneManager extension loading failed, shutting down: " + ex.Message, Helpers.LogLevel.Error);
Stop();
return false;
}
#endregion ISceneProvider Extension Loading
// Callback registration
server.Grid.OnRegionUpdate += Grid_OnRegionUpdate;
udp.OnAgentConnection += udp_OnAgentConnection;
udp.RegisterPacketCallback(PacketType.CompleteAgentMovement, CompleteAgentMovementHandler);
// Load the default terrain for this sim
if (!String.IsNullOrEmpty(defaultTerrainFile))
LoadTerrain(Simian.DATA_DIR + defaultTerrainFile);
// Start the physics thread
Thread physicsThread = new Thread(new ThreadStart(PhysicsThread));
physicsThread.Name = "Physics";
physicsThread.Start();
Logger.Log(String.Format("Region {0} online at ({1},{2}) listening on {3}", regionName, regionX, regionY, endpoint),
Helpers.LogLevel.Info);
// Tell the grid that this region is online
regionInfo.Online = true;
server.Grid.RegionUpdate(regionInfo, regionCert);
return true;
}
public void Stop()
{
Logger.Log("Stopping region " + regionName, Helpers.LogLevel.Info);
running = false;
// Remove all of the agents from the scene. This will shutdown UDP connections and event queues to
// each of the agents as well
lock (sceneAgents)
{
List<Agent> agents = new List<Agent>(sceneAgents.Values);
for (int i = 0; i < agents.Count; i++)
ObjectRemove(this, agents[i].ID);
}
// Stop ISceneProvider extensions
foreach (IExtension<ISceneProvider> extension in extensions.Extensions)
{
Logger.Log("Stopping Scene extension " + extension.GetType().Name, Helpers.LogLevel.Info);
extension.Stop();
}
Logger.Log("Region " + regionName + " is stopped", Helpers.LogLevel.Info);
}
#region Object Interfaces
public void ObjectAddOrUpdate(object sender, SimulationObject obj, UUID ownerID, int scriptStartParam, PrimFlags creatorFlags, UpdateFlags updateFlags)
{
if (OnObjectAddOrUpdate != null)
{
OnObjectAddOrUpdate(sender, obj, ownerID, scriptStartParam, creatorFlags, updateFlags);
}
#region Initialize new objects
// Check if the object already exists in the scene
if (!sceneObjects.ContainsKey(obj.Prim.ID))
{
// Enable some default flags that all objects will have
obj.Prim.Flags |= server.Permissions.GetDefaultObjectFlags();
// Object did not exist before, so there's no way it could contain inventory
obj.Prim.Flags |= PrimFlags.InventoryEmpty;
// Fun Fact: Prim.OwnerID is only used by the LL viewer to mute sounds
obj.Prim.OwnerID = ownerID;
// Other than storing tree species, I have no idea what this does
obj.Prim.ScratchPad = Utils.EmptyBytes;
// Assign a unique LocalID to this object if no LocalID is set
if (obj.Prim.LocalID == 0)
obj.Prim.LocalID = (uint)Interlocked.Increment(ref currentLocalID);
// Assign a random ID to this object if no ID is set
if (obj.Prim.ID == UUID.Zero)
obj.Prim.ID = UUID.Random();
// Set the RegionHandle if no RegionHandle is set
if (obj.Prim.RegionHandle == 0)
obj.Prim.RegionHandle = regionHandle;
// Make sure this object has properties
if (obj.Prim.Properties == null)
{
obj.Prim.Properties = new Primitive.ObjectProperties();
obj.Prim.Properties.CreationDate = DateTime.Now;
obj.Prim.Properties.CreatorID = ownerID;
obj.Prim.Properties.Name = "New Object";
obj.Prim.Properties.ObjectID = obj.Prim.ID;
obj.Prim.Properties.OwnerID = ownerID;
obj.Prim.Properties.Permissions = server.Permissions.GetDefaultPermissions();
obj.Prim.Properties.SalePrice = 10;
}
// Set the default scale
if (obj.Prim.Scale == Vector3.Zero)
obj.Prim.Scale = new Vector3(0.5f, 0.5f, 0.5f);
// Set the collision plane
if (obj.Prim.CollisionPlane == Vector4.Zero)
obj.Prim.CollisionPlane = Vector4.UnitW;
// Set default textures if none are set
if (obj.Prim.Textures == null)
obj.Prim.Textures = new Primitive.TextureEntry(new UUID("89556747-24cb-43ed-920b-47caed15465f")); // Plywood
// Add the object to the scene dictionary
sceneObjects.Add(obj.Prim.LocalID, obj.Prim.ID, obj);
}
#endregion Initialize new objects
// Reset the prim CRC
obj.CRC = 0;
#region UpdateFlags to packet type conversion
bool canUseCompressed = true;
bool canUseImproved = true;
if ((updateFlags & UpdateFlags.FullUpdate) == UpdateFlags.FullUpdate || creatorFlags != PrimFlags.None)
{
canUseCompressed = false;
canUseImproved = false;
}
else
{
if ((updateFlags & UpdateFlags.Velocity) != 0 ||
(updateFlags & UpdateFlags.Acceleration) != 0 ||
(updateFlags & UpdateFlags.CollisionPlane) != 0 ||
(updateFlags & UpdateFlags.Joint) != 0)
{
canUseCompressed = false;
}
if ((updateFlags & UpdateFlags.PrimFlags) != 0 ||
(updateFlags & UpdateFlags.ParentID) != 0 ||
(updateFlags & UpdateFlags.Scale) != 0 ||
(updateFlags & UpdateFlags.PrimData) != 0 ||
(updateFlags & UpdateFlags.Text) != 0 ||
(updateFlags & UpdateFlags.NameValue) != 0 ||
(updateFlags & UpdateFlags.ExtraData) != 0 ||
(updateFlags & UpdateFlags.TextureAnim) != 0 ||
(updateFlags & UpdateFlags.Sound) != 0 ||
(updateFlags & UpdateFlags.Particles) != 0 ||
(updateFlags & UpdateFlags.Material) != 0 ||
(updateFlags & UpdateFlags.ClickAction) != 0 ||
(updateFlags & UpdateFlags.MediaURL) != 0 ||
(updateFlags & UpdateFlags.Joint) != 0)
{
canUseImproved = false;
}
}
#endregion UpdateFlags to packet type conversion
SendObjectPacket(obj, canUseCompressed, canUseImproved, creatorFlags, updateFlags);
}
public bool ObjectRemove(object sender, uint localID)
{
SimulationObject obj;
if (sceneObjects.TryGetValue(localID, out obj))
{
RemoveObject(sender, obj);
return true;
}
return false;
}
public bool ObjectRemove(object sender, UUID id)
{
SimulationObject obj;
if (sceneObjects.TryGetValue(id, out obj))
{
RemoveObject(sender, obj);
return true;
}
return false;
}
public void ObjectSetRotationAxis(object sender, SimulationObject obj, Vector3 rotationAxis)
{
if (OnObjectSetRotationAxis != null)
{
OnObjectSetRotationAxis(sender, obj, rotationAxis);
}
// Update the object
obj.RotationAxis = rotationAxis;
}
public void ObjectApplyImpulse(object sender, SimulationObject obj, Vector3 impulse)
{
if (OnObjectApplyImpulse != null)
{
OnObjectApplyImpulse(sender, obj, impulse);
}
// FIXME:
}
public void ObjectApplyRotationalImpulse(object sender, SimulationObject obj, Vector3 impulse)
{
if (OnObjectApplyRotationalImpulse != null)
{
OnObjectApplyRotationalImpulse(sender, obj, impulse);
}
// FIXME:
}
public void ObjectSetTorque(object sender, SimulationObject obj, Vector3 torque)
{
if (OnObjectSetTorque != null)
{
OnObjectSetTorque(sender, obj, torque);
}
obj.Torque = torque;
}
public void ObjectAnimate(object sender, UUID senderID, UUID objectID, AnimationTrigger[] animations)
{
if (OnObjectAnimate != null)
{
OnObjectAnimate(sender, senderID, objectID, animations);
}
AvatarAnimationPacket sendAnim = new AvatarAnimationPacket();
sendAnim.Sender.ID = senderID;
sendAnim.AnimationSourceList = new AvatarAnimationPacket.AnimationSourceListBlock[1];
sendAnim.AnimationSourceList[0] = new AvatarAnimationPacket.AnimationSourceListBlock();
sendAnim.AnimationSourceList[0].ObjectID = objectID;
sendAnim.AnimationList = new AvatarAnimationPacket.AnimationListBlock[animations.Length];
for (int i = 0; i < animations.Length; i++)
{
sendAnim.AnimationList[i] = new AvatarAnimationPacket.AnimationListBlock();
sendAnim.AnimationList[i].AnimID = animations[i].AnimationID;
sendAnim.AnimationList[i].AnimSequenceID = animations[i].SequenceID;
}
udp.BroadcastPacket(sendAnim, PacketCategory.State);
}
public void ObjectChat(object sender, UUID ownerID, UUID sourceID, ChatAudibleLevel audible, ChatType type, ChatSourceType sourceType,
string fromName, Vector3 position, int channel, string message)
{
if (OnObjectChat != null)
{
OnObjectChat(sender, ownerID, sourceID, audible, type, sourceType, fromName, position, channel, message);
}
if (channel == 0)
{
// TODO: Reduction provider will impose the chat radius
ChatFromSimulatorPacket chat = new ChatFromSimulatorPacket();
chat.ChatData.Audible = (byte)audible;
chat.ChatData.ChatType = (byte)type;
chat.ChatData.OwnerID = ownerID;
chat.ChatData.SourceID = sourceID;
chat.ChatData.SourceType = (byte)sourceType;
chat.ChatData.Position = position;
chat.ChatData.FromName = Utils.StringToBytes(fromName);
chat.ChatData.Message = Utils.StringToBytes(message);
udp.BroadcastPacket(chat, PacketCategory.Messaging);
}
}
public void ObjectUndo(object sender, SimulationObject obj)
{
if (OnObjectUndo != null)
{
OnObjectUndo(sender, obj);
}
Primitive prim = obj.UndoSteps.DequeueLast();
if (prim != null)
{
Logger.Log(String.Format("Performing undo on object {0}", obj.Prim.ID), Helpers.LogLevel.Debug);
obj.RedoSteps.Enqueue(prim);
obj.Prim = prim;
// Inform clients
ObjectAddOrUpdate(this, obj, obj.Prim.OwnerID, 0, PrimFlags.None, UpdateFlags.FullUpdate);
}
else
{
Logger.Log(String.Format("Undo requested on object {0} with no remaining undo steps", obj.Prim.ID),
Helpers.LogLevel.Debug);
}
}
public void ObjectRedo(object sender, SimulationObject obj)
{
if (OnObjectRedo != null)
{
OnObjectRedo(sender, obj);
}
Primitive prim = obj.RedoSteps.DequeueLast();
if (prim != null)
{
Logger.Log(String.Format("Performing redo on object {0}", obj.Prim.ID), Helpers.LogLevel.Debug);
obj.UndoSteps.Enqueue(prim);
obj.Prim = prim;
// Inform clients
ObjectAddOrUpdate(this, obj, obj.Prim.OwnerID, 0, PrimFlags.None, UpdateFlags.FullUpdate);
}
else
{
Logger.Log(String.Format("Redo requested on object {0} with no remaining redo steps", obj.Prim.ID),
Helpers.LogLevel.Debug);
}
}
public bool ContainsObject(uint localID)
{
return sceneObjects.ContainsKey(localID);
}
public bool ContainsObject(UUID id)
{
return sceneObjects.ContainsKey(id);
}
public int ObjectCount()
{
return sceneObjects.Count;
}
public bool TryGetObject(uint localID, out SimulationObject obj)
{
return sceneObjects.TryGetValue(localID, out obj);
}
public bool TryGetObject(UUID id, out SimulationObject obj)
{
return sceneObjects.TryGetValue(id, out obj);
}
public void ForEachObject(Action<SimulationObject> action)
{
sceneObjects.ForEach(action);
}
public SimulationObject FindObject(Predicate<SimulationObject> predicate)
{
return sceneObjects.FindValue(predicate);
}
public int RemoveAllObjects(Predicate<SimulationObject> predicate)
{
return sceneObjects.RemoveAll(predicate);
}
public void TriggerSound(object sender, UUID objectID, UUID parentID, UUID ownerID, UUID soundID, Vector3 position, float gain)
{
if (OnTriggerSound != null)
{
OnTriggerSound(sender, objectID, parentID, ownerID, soundID, position, gain);
}
SoundTriggerPacket sound = new SoundTriggerPacket();
sound.SoundData.Handle = regionHandle;
sound.SoundData.ObjectID = objectID;
sound.SoundData.ParentID = parentID;
sound.SoundData.OwnerID = ownerID;
sound.SoundData.Position = position;
sound.SoundData.SoundID = soundID;
sound.SoundData.Gain = gain;
udp.BroadcastPacket(sound, PacketCategory.State);
}
public void TriggerEffects(object sender, ViewerEffect[] effects)
{
if (OnTriggerEffects != null)
{
OnTriggerEffects(sender, effects);
}
ViewerEffectPacket effect = new ViewerEffectPacket();
effect.AgentData.AgentID = UUID.Zero;
effect.AgentData.SessionID = UUID.Zero;
effect.Effect = new ViewerEffectPacket.EffectBlock[effects.Length];
for (int i = 0; i < effects.Length; i++)
{
ViewerEffect currentEffect = effects[i];
ViewerEffectPacket.EffectBlock block = new ViewerEffectPacket.EffectBlock();
block.AgentID = currentEffect.AgentID;
block.Color = currentEffect.Color.GetBytes(true);
block.Duration = currentEffect.Duration;
block.ID = currentEffect.EffectID;
block.Type = (byte)currentEffect.Type;
block.TypeData = currentEffect.TypeData;
effect.Effect[i] = block;
}
udp.BroadcastPacket(effect, PacketCategory.State);
}
#endregion Object Interfaces
#region Agent Interfaces
public bool AgentAdd(object sender, Agent agent, PrimFlags creatorFlags)
{
// Sanity check, since this should have already been done
agent.Avatar.Prim.ID = agent.Info.ID;
// Check if the agent already exists in the scene
lock (sceneAgents)
{
if (sceneAgents.ContainsKey(agent.ID))
sceneAgents.Remove(agent.ID);
}
// Update the current region handle
agent.Avatar.Prim.RegionHandle = regionHandle;
// Avatars always have physics
agent.Avatar.Prim.Flags |= PrimFlags.Physics;
// Default avatar values
agent.Avatar.Prim.Position = new Vector3(128f, 128f, 25f);
agent.Avatar.Prim.Rotation = Quaternion.Identity;
agent.Avatar.Prim.Scale = new Vector3(0.45f, 0.6f, 1.9f);
agent.Avatar.Prim.PrimData.Material = Material.Flesh;
agent.Avatar.Prim.PrimData.PCode = PCode.Avatar;
agent.Avatar.Prim.Textures = new Primitive.TextureEntry(new UUID("c228d1cf-4b5d-4ba8-84f4-899a0796aa97"));
// Set the avatar name
NameValue[] name = new NameValue[2];
name[0] = new NameValue("FirstName", NameValue.ValueType.String, NameValue.ClassType.ReadWrite,
NameValue.SendtoType.SimViewer, agent.Info.FirstName);
name[1] = new NameValue("LastName", NameValue.ValueType.String, NameValue.ClassType.ReadWrite,
NameValue.SendtoType.SimViewer, agent.Info.LastName);
agent.Avatar.Prim.NameValues = name;
// Give testers a provisionary balance of 1000L
agent.Info.Balance = 1000;
// Some default avatar prim properties
agent.Avatar.Prim.Properties = new Primitive.ObjectProperties();
agent.Avatar.Prim.Properties.CreationDate = Utils.UnixTimeToDateTime(agent.Info.CreationTime);
agent.Avatar.Prim.Properties.Name = agent.FullName;
agent.Avatar.Prim.Properties.ObjectID = agent.ID;
if (agent.Avatar.Prim.LocalID == 0)
{
// Assign a unique LocalID to this agent
agent.Avatar.Prim.LocalID = (uint)Interlocked.Increment(ref currentLocalID);
}
if (OnAgentAdd != null)
OnAgentAdd(sender, agent, creatorFlags);
// Add the agent to the scene dictionary
lock (sceneAgents) sceneAgents[agent.ID] = agent;
Logger.Log("Added agent " + agent.FullName + " to the scene", Helpers.LogLevel.Info);
return true;
}
void AgentRemove(object sender, Agent agent)
{
if (OnAgentRemove != null)
OnAgentRemove(sender, agent);
Logger.Log("Removing agent " + agent.FullName + " from the scene", Helpers.LogLevel.Info);
lock (sceneAgents) sceneAgents.Remove(agent.ID);
// Kill the EventQueue
RemoveEventQueue(agent.ID);
// Remove the UDP client
udp.RemoveClient(agent);
// Notify everyone in the scene that this agent has gone offline
OfflineNotificationPacket offline = new OfflineNotificationPacket();
offline.AgentBlock = new OfflineNotificationPacket.AgentBlockBlock[1];
offline.AgentBlock[0] = new OfflineNotificationPacket.AgentBlockBlock();
offline.AgentBlock[0].AgentID = agent.ID;
udp.BroadcastPacket(offline, PacketCategory.State);
}
public void AgentAppearance(object sender, Agent agent, Primitive.TextureEntry textures, byte[] visualParams)
{
if (OnAgentAppearance != null)
{
OnAgentAppearance(sender, agent, textures, visualParams);
}
// Broadcast an object update for this avatar
// TODO: Is this necessary here?
//ObjectUpdatePacket update = SimulationObject.BuildFullUpdate(agent.Avatar,
// regionHandle, agent.Flags);
//scene.UDP.BroadcastPacket(update, PacketCategory.State);
// Update the avatar
agent.Avatar.Prim.Textures = textures;
if (visualParams != null && visualParams.Length > 1)
agent.Info.VisualParams = visualParams;
if (agent.Info.VisualParams != null)
{
// Send the appearance packet to all other clients
AvatarAppearancePacket appearance = agent.BuildAppearancePacket();
ForEachAgent(
delegate(Agent recipient)
{
if (recipient != agent)
udp.SendPacket(recipient.ID, appearance, PacketCategory.State);
}
);
}
}
public bool TryGetAgent(UUID id, out Agent agent)
{
return sceneAgents.TryGetValue(id, out agent);
}
public int AgentCount()
{
return sceneAgents.Count;
}
public void ForEachAgent(Action<Agent> action)
{
lock (sceneAgents)
{
foreach (Agent agent in sceneAgents.Values)
action(agent);
}
}
public Agent FindAgent(Predicate<Agent> predicate)
{
lock (sceneAgents)
{
foreach (Agent agent in sceneAgents.Values)
{
if (predicate(agent))
return agent;
}
}
return null;
}
public int RemoveAllAgents(Predicate<Agent> predicate)
{
List<UUID> list = new List<UUID>();
lock (sceneAgents)
{
foreach (KeyValuePair<UUID, Agent> kvp in sceneAgents)
{
if (predicate(kvp.Value))
list.Add(kvp.Key);
}
for (int i = 0; i < list.Count; i++)
sceneAgents.Remove(list[i]);
}
return list.Count;
}
#endregion Agent Interfaces
#region Terrain and Wind
public float GetTerrainHeightAt(float fx, float fy)
{
int x = (int)fx;
int y = (int)fy;
if (x > 255) x = 255;
else if (x < 0) x = 0;
if (y > 255) y = 255;
else if (y < 0) y = 0;
int patchX = x / 16;
int patchY = y / 16;
if (heightmap[patchY, patchX] != null)
{
float center = heightmap[patchY, patchX].Height[y - (patchY * 16), x - (patchX * 16)];
float distX = fx - (int)fx;
float distY = fy - (int)fy;
float nearestX;
float nearestY;
if (distX > 0f)
{
int i = x < 255 ? 1 : 0;
int newPatchX = (x + i) / 16;
nearestX = heightmap[patchY, newPatchX].Height[y - (patchY * 16), (x + i) - (newPatchX * 16)];
}
else
{
int i = x > 0 ? 1 : 0;
int newPatchX = (x - i) / 16;
nearestX = heightmap[patchY, newPatchX].Height[y - (patchY * 16), (x - i) - (newPatchX * 16)];
}
if (distY > 0f)
{
int i = y < 255 ? 1 : 0;
int newPatchY = (y + i) / 16;
nearestY = heightmap[newPatchY, patchX].Height[(y + i) - (newPatchY * 16), x - (patchX * 16)];
}
else
{
int i = y > 0 ? 1 : 0;
int newPatchY = (y - i) / 16;
nearestY = heightmap[newPatchY, patchX].Height[(y - i) - (newPatchY * 16), x - (patchX * 16)];
}
float lerpX = Utils.Lerp(center, nearestX, Math.Abs(distX));
float lerpY = Utils.Lerp(center, nearestY, Math.Abs(distY));
return ((lerpX + lerpY) / 2);
}
else
{
return 0f;
}
}
public float[,] GetTerrainPatch(uint x, uint y)
{
float[,] copy = new float[16, 16];
Buffer.BlockCopy(heightmap[y, x].Height, 0, copy, 0, 16 * 16 * sizeof(float));
return copy;
}
public void SetTerrainPatch(object sender, uint x, uint y, float[,] patchData)
{
if (OnTerrainUpdate != null)
OnTerrainUpdate(sender, x, y, patchData);
float[,] copy = new float[16, 16];
Buffer.BlockCopy(patchData, 0, copy, 0, 16 * 16 * sizeof(float));
heightmap[y, x].Height = copy;
LayerDataPacket layer = TerrainCompressor.CreateLandPacket(heightmap[y, x].Height, (int)x, (int)y);
udp.BroadcastPacket(layer, PacketCategory.Terrain);
}
public Vector2 GetWindSpeedAt(float fx, float fy)
{
int x = (int)fx;
int y = (int)fy;
if (x > 255) x = 255;
else if (x < 0) x = 0;
if (y > 255) y = 255;
else if (y < 0) y = 0;
int patchX = x / 16;
int patchY = y / 16;
return windSpeeds[patchY, patchX];
}
public Vector2 GetWindSpeed(uint x, uint y)
{
return windSpeeds[y, x];
}
public void SetWindSpeed(object sender, uint x, uint y, Vector2 windSpeed)
{
if (OnWindUpdate != null)
{
OnWindUpdate(sender, x, y, windSpeed);
}
windSpeeds[y, x] = windSpeed;
}
#endregion Terrain and Wind
#region Capabilities Interfaces
public Uri CreateEventQueue(UUID agentID)
{
EventQueueServer eqServer = new EventQueueServer(server.HttpServer);
EventQueueServerCap eqServerCap = new EventQueueServerCap(eqServer,
server.Capabilities.CreateCapability(EventQueueHandler, false, eqServer));
lock (eventQueues)
eventQueues.Add(agentID, eqServerCap);
return eqServerCap.Capability;
}
public bool RemoveEventQueue(UUID agentID)
{
lock (eventQueues)
{
EventQueueServerCap queue;
if (eventQueues.TryGetValue(agentID, out queue))
{
queue.Server.Stop();
return eventQueues.Remove(agentID);
}
else
{
return false;
}
}
}
public bool HasRunningEventQueue(Agent agent)
{
return eventQueues.ContainsKey(agent.ID);
}
public void SendEvent(Agent agent, string name, OSDMap body)
{
EventQueueServerCap eventQueue;
if (eventQueues.TryGetValue(agent.ID, out eventQueue))
{
eventQueue.Server.SendEvent(name, body);
}
else
{
Logger.Log(String.Format("Cannot send the event {0} to agent {1}, no event queue for that avatar",
name, agent.FullName), Helpers.LogLevel.Warning);
}
}
#endregion Capabilities Interfaces
public void InformClientOfNeighbors(Agent agent)
{
for (int i = 0; i < 8; i++)
{
if (!agent.NeighborConnections[i] && neighbors[i].Online)
{
Logger.Log("Sending enable_client for " + agent.FullName + " to neighbor " + neighbors[i].Name, Helpers.LogLevel.Info);
// Create a callback for enable_client_complete
Uri callbackUri = server.Capabilities.CreateCapability(EnableClientCompleteCapHandler, false, null);
OSDMap enableClient = CapsMessages.EnableClient(agent.ID, agent.SessionID, agent.SecureSessionID,
(int)agent.CircuitCode, agent.Info.FirstName, agent.Info.LastName, callbackUri);
AutoResetEvent waitEvent = new AutoResetEvent(false);
CapsClient request = new CapsClient(neighbors[i].EnableClientCap);
request.OnComplete +=
delegate(CapsClient client, OSD result, Exception error)
{
OSDMap response = result as OSDMap;
if (response != null)
{
bool success = response["success"].AsBoolean();
Logger.Log("enable_client response: " + success, Helpers.LogLevel.Info);
if (success)
{
// Send the EnableSimulator capability to clients
RegionInfo neighbor = neighbors[i];
OSDMap enableSimulator = CapsMessages.EnableSimulator(neighbor.Handle, neighbor.IPAndPort.Address, neighbor.IPAndPort.Port);
SendEvent(agent, "EnableSimulator", enableSimulator);
}
}
waitEvent.Set();
};
request.StartRequest(enableClient);
if (!waitEvent.WaitOne(30 * 1000, false))
Logger.Log("enable_client request timed out", Helpers.LogLevel.Warning);
}
}
}
#region Callback Handlers
public bool SeedCapabilityHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state)
{
UUID agentID = (UUID)state;
OSDArray array = OSDParser.DeserializeLLSDXml(request.Body) as OSDArray;
if (array != null)
{
OSDMap osdResponse = new OSDMap();
for (int i = 0; i < array.Count; i++)
{
string capName = array[i].AsString();
switch (capName)
{
case "EventQueueGet":
Uri eqCap = null;
// Check if this agent already has an event queue
EventQueueServerCap eqServer;
if (eventQueues.TryGetValue(agentID, out eqServer))
eqCap = eqServer.Capability;
// If not, create one
if (eqCap == null)
eqCap = CreateEventQueue(agentID);
osdResponse.Add("EventQueueGet", OSD.FromUri(eqCap));
break;
}
}
byte[] responseData = OSDParser.SerializeLLSDXmlBytes(osdResponse);
response.ContentLength = responseData.Length;
response.Body.Write(responseData, 0, responseData.Length);
response.Body.Flush();
}
else
{
response.Status = HttpStatusCode.BadRequest;
}
return true;
}
public bool EnableClientCapHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state)
{
OSDMap map = OSDParser.DeserializeLLSDXml(request.Body) as OSDMap;
OSDMap osdResponse = new OSDMap();
if (map != null)
{
UUID agentID = map["agent_id"].AsUUID();
UUID sessionID = map["session_id"].AsUUID();
UUID secureSessionID = map["secure_session_id"].AsUUID();
uint circuitCode = (uint)map["circuit_code"].AsInteger();
// TODO: Send an identity url and token instead so we can pull down all of the information
string firstName = map["first_name"].AsString();
string lastName = map["last_name"].AsString();
Uri callbackUri = map["callback_uri"].AsUri();
Logger.Log(String.Format(
"enable_client request. agent_id: {0}, session_id: {1}, secureSessionID: {2}, " +
"first_name: {3}, last_name: {4}, callback_uri: {5}", agentID, sessionID, secureSessionID,
firstName, lastName, callbackUri), Helpers.LogLevel.Info);
if (agentID != UUID.Zero && sessionID != UUID.Zero && secureSessionID != UUID.Zero &&
!String.IsNullOrEmpty(firstName) && !String.IsNullOrEmpty(lastName))
{
AgentInfo info = new AgentInfo();
info.AccessLevel = "M";
info.FirstName = firstName;
info.Height = 1.9f;
info.HomeLookAt = Vector3.UnitZ;
info.HomePosition = new Vector3(128f, 128f, 25f);
info.HomeRegionHandle = regionHandle;
info.ID = agentID;
info.LastName = lastName;
info.PasswordHash = String.Empty;
Agent agent = new Agent(new SimulationObject(new Avatar(), this), info);
// Set the avatar ID
agent.Avatar.Prim.ID = agentID;
// Random session IDs
agent.SessionID = sessionID;
agent.SecureSessionID = secureSessionID;
// Create a seed capability for this agent
agent.SeedCapability = server.Capabilities.CreateCapability(SeedCapabilityHandler, false, agentID);
agent.TickLastPacketReceived = Environment.TickCount;
agent.Info.LastLoginTime = Utils.DateTimeToUnixTime(DateTime.Now);
// Add the callback URI to the list of pending enable_client_complete callbacks
lock (enableClientCompleteCallbacks)
enableClientCompleteCallbacks[agentID] = callbackUri;
// Assign a circuit code and track the agent as an unassociated agent (no UDP connection yet)
udp.CreateCircuit(agent, circuitCode);
agent.CircuitCode = circuitCode;
osdResponse["success"] = OSD.FromBoolean(true);
}
else
{
osdResponse["success"] = OSD.FromBoolean(false);
osdResponse["message"] = OSD.FromString("missing required fields for enable_client");
}
}
else
{
osdResponse["success"] = OSD.FromBoolean(false);
osdResponse["message"] = OSD.FromString("failed to parse enable_client message");
}
byte[] responseData = OSDParser.SerializeLLSDXmlBytes(osdResponse);
response.ContentLength = responseData.Length;
response.Body.Write(responseData, 0, responseData.Length);
response.Body.Flush();
return true;
}
public bool EnableClientCompleteCapHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state)
{
OSDMap map = OSDParser.DeserializeLLSDXml(request.Body) as OSDMap;
OSDMap osdResponse = new OSDMap();
if (map != null)
{
UUID agentID = map["agent_id"].AsUUID();
Uri seedCapability = map["seed_capability"].AsUri();
Logger.Log(String.Format("enable_client_complete response. agent_id: {0}, seed_capability: {1}",
agentID, seedCapability), Helpers.LogLevel.Info);
if (enableClientCompleteCallbacks.Remove(agentID))
{
// FIXME: Finish this
}
}
return true;
}
bool EventQueueHandler(IHttpClientContext context, IHttpRequest request, IHttpResponse response, object state)
{
EventQueueServer eqServer = (EventQueueServer)state;
return eqServer.EventQueueHandler(context, request, response);
}
void Grid_OnRegionUpdate(RegionInfo regionInfo)
{
// TODO: Detect regions coming online so we can call
// InformClientOfNeighbors(agent);
// for every agent
// Check if the sim was a neighbor
if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX - 1), 256 * (regionY + 1)))
neighbors[0] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * regionX, 256 * (regionY + 1)))
neighbors[1] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX + 1), 256 * (regionY + 1)))
neighbors[2] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX - 1), 256 * regionY))
neighbors[3] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX + 1), 256 * regionY))
neighbors[4] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX - 1), 256 * (regionY - 1)))
neighbors[5] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * regionX, 256 * (regionY - 1)))
neighbors[6] = regionInfo;
else if (regionInfo.Handle == Utils.UIntsToLong(256 * (regionX + 1), 256 * (regionY - 1)))
neighbors[7] = regionInfo;
}
void udp_OnAgentConnection(Agent agent, uint circuitCode)
{
Uri callbackUri;
if (enableClientCompleteCallbacks.TryGetValue(agent.ID, out callbackUri))
{
lock (enableClientCompleteCallbacks)
enableClientCompleteCallbacks.Remove(agent.ID);
Logger.Log("Sending enable_client_complete callback to " + callbackUri.ToString(), Helpers.LogLevel.Info);
OSDMap enableClientComplete = CapsMessages.EnableClientComplete(agent.ID);
AutoResetEvent waitEvent = new AutoResetEvent(false);
CapsClient request = new CapsClient(callbackUri);
request.OnComplete +=
delegate(CapsClient client, OSD result, Exception error)
{
OSDMap response = result as OSDMap;
if (response != null)
{
bool success = response["success"].AsBoolean();
Logger.Log("enable_client_complete response: " + success, Helpers.LogLevel.Info);
if (success)
{
Uri seedCapability = response["seed_capability"].AsUri();
}
}
waitEvent.Set();
};
request.StartRequest(enableClientComplete);
if (!waitEvent.WaitOne(30 * 1000, false))
Logger.Log("enable_client_complete request timed out", Helpers.LogLevel.Warning);
}
}
void CompleteAgentMovementHandler(Packet packet, Agent agent)
{
// Add this avatar as an object in the scene
ObjectAddOrUpdate(this, agent.Avatar, agent.Avatar.Prim.OwnerID, 0, PrimFlags.None, UpdateFlags.FullUpdate);
// Send a response back to the client
AgentMovementCompletePacket complete = new AgentMovementCompletePacket();
complete.AgentData.AgentID = agent.ID;
complete.AgentData.SessionID = agent.SessionID;
complete.Data.LookAt = Vector3.UnitZ; // TODO: Properly implement LookAt someday
complete.Data.Position = agent.Avatar.Prim.Position;
complete.Data.RegionHandle = regionHandle;
complete.Data.Timestamp = Utils.DateTimeToUnixTime(DateTime.Now);
complete.SimData.ChannelVersion = Utils.StringToBytes("Simian");
udp.SendPacket(agent.ID, complete, PacketCategory.Transaction);
//HACK: Notify everyone when someone logs on to the simulator
OnlineNotificationPacket online = new OnlineNotificationPacket();
online.AgentBlock = new OnlineNotificationPacket.AgentBlockBlock[1];
online.AgentBlock[0] = new OnlineNotificationPacket.AgentBlockBlock();
online.AgentBlock[0].AgentID = agent.ID;
udp.BroadcastPacket(online, PacketCategory.State);
}
#endregion Callback Handlers
void PhysicsThread()
{
const float TARGET_FRAME_TIME = 1f / (float)TARGET_FRAMES_PER_SECOND;
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
float elapsedTime = 0f;
int sleepMS;
while (running)
{
stopwatch.Start();
physics.Update(elapsedTime);
// Measure the duration of this frame
stopwatch.Stop();
elapsedTime = (float)stopwatch.Elapsed.TotalSeconds;
stopwatch.Reset();
// Calculate time dilation and decide if we need to sleep to limit FPS
if (elapsedTime < TARGET_FRAME_TIME)
{
timeDilation = (1f / elapsedTime) / (float)TARGET_FRAMES_PER_SECOND;
sleepMS = (int)((TARGET_FRAME_TIME - elapsedTime) * 1000f);
Thread.Sleep(sleepMS);
elapsedTime = TARGET_FRAME_TIME;
}
else
{
timeDilation = 1f;
}
}
}
void LoadTerrain(string mapFile)
{
byte[] rgbValues = new byte[256 * 256 * 3];
if (File.Exists(mapFile))
{
lock (heightmap)
{
Bitmap bmp = LoadTGAClass.LoadTGA(mapFile);
if (bmp.Width == 256 && bmp.Height == 256)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
Marshal.Copy(bmpData.Scan0, rgbValues, 0, rgbValues.Length);
bmp.UnlockBits(bmpData);
}
else
{
Logger.Log("Map file " + mapFile + " has the wrong dimensions or wrong pixel format (must be 256x256 RGB). Defaulting to 25m",
Helpers.LogLevel.Warning);
for (int i = 0; i < rgbValues.Length; i++)
rgbValues[i] = 25;
}
}
}
else
{
Logger.Log("Map file " + mapFile + " not found, defaulting to 25m", Helpers.LogLevel.Info);
for (int i = 0; i < rgbValues.Length; i++)
rgbValues[i] = 25;
}
uint patchX = 0, patchY = 0, x = 0, y = 0;
for (int i = 0; i < rgbValues.Length; i += 3)
{
if (heightmap[patchY, patchX] == null)
heightmap[patchY, patchX] = new TerrainPatch(16, 16);
heightmap[patchY, patchX].Height[y, x] = (float)rgbValues[i];
++x;
if (x > 15)
{
if (y == 15)
{
if (OnTerrainUpdate != null)
OnTerrainUpdate(this, patchX, patchY, heightmap[patchY, patchX].Height);
}
x = 0;
++patchX;
}
if (patchX > 15)
{
patchX = 0;
++y;
}
if (y > 15)
{
y = 0;
++patchY;
}
}
}
void RemoveObject(object sender, SimulationObject obj)
{
// If this object has an agent associated with it, remove the agent from the scene as well
Agent agent;
if (sceneAgents.TryGetValue(obj.Prim.ID, out agent))
AgentRemove(sender, agent);
// Remove the agent from the scene
sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID);
// Fire the callback
if (OnObjectRemove != null)
OnObjectRemove(sender, obj);
// If this object has a cached task inventory asset, delete it now
if (obj.Inventory.InventorySerial > 0)
taskInventory.RemoveTaskFile(obj.Inventory.GetInventoryFilename());
// Broadcast the kill message
KillObjectPacket kill = new KillObjectPacket();
kill.ObjectData = new KillObjectPacket.ObjectDataBlock[1];
kill.ObjectData[0] = new KillObjectPacket.ObjectDataBlock();
kill.ObjectData[0].ID = obj.Prim.LocalID;
udp.BroadcastPacket(kill, PacketCategory.State);
}
void SendObjectPacket(SimulationObject obj, bool canUseCompressed, bool canUseImproved, PrimFlags creatorFlags, UpdateFlags updateFlags)
{
if (!canUseImproved && !canUseCompressed)
{
#region ObjectUpdate
Logger.DebugLog("Sending ObjectUpdate");
if (sceneAgents.ContainsKey(obj.Prim.OwnerID))
{
// Send an update out to the creator
ObjectUpdatePacket updateToOwner = new ObjectUpdatePacket();
updateToOwner.RegionData.RegionHandle = regionHandle;
updateToOwner.RegionData.TimeDilation = (ushort)(timeDilation * (float)UInt16.MaxValue);
updateToOwner.ObjectData = new ObjectUpdatePacket.ObjectDataBlock[1];
updateToOwner.ObjectData[0] = SimulationObject.BuildUpdateBlock(obj.Prim,
obj.Prim.Flags | creatorFlags | PrimFlags.ObjectYouOwner, obj.CRC);
udp.SendPacket(obj.Prim.OwnerID, updateToOwner, PacketCategory.State);
}
// Send an update out to everyone else
ObjectUpdatePacket updateToOthers = new ObjectUpdatePacket();
updateToOthers.RegionData.RegionHandle = regionHandle;
updateToOthers.RegionData.TimeDilation = UInt16.MaxValue;
updateToOthers.ObjectData = new ObjectUpdatePacket.ObjectDataBlock[1];
updateToOthers.ObjectData[0] = SimulationObject.BuildUpdateBlock(obj.Prim,
obj.Prim.Flags, obj.CRC);
ForEachAgent(
delegate(Agent recipient)
{
if (recipient.ID != obj.Prim.OwnerID)
udp.SendPacket(recipient.ID, updateToOthers, PacketCategory.State);
}
);
#endregion ObjectUpdate
}
else if (!canUseImproved)
{
#region ObjectUpdateCompressed
#region Size calculation and field serialization
CompressedFlags flags = 0;
int size = 84;
byte[] textBytes = null;
byte[] mediaURLBytes = null;
byte[] particleBytes = null;
byte[] extraParamBytes = null;
byte[] nameValueBytes = null;
byte[] textureBytes = null;
byte[] textureAnimBytes = null;
if ((updateFlags & UpdateFlags.AngularVelocity) != 0)
{
flags |= CompressedFlags.HasAngularVelocity;
size += 12;
}
if ((updateFlags & UpdateFlags.ParentID) != 0)
{
flags |= CompressedFlags.HasParent;
size += 4;
}
if ((updateFlags & UpdateFlags.ScratchPad) != 0)
{
switch (obj.Prim.PrimData.PCode)
{
case PCode.Grass:
case PCode.Tree:
case PCode.NewTree:
flags |= CompressedFlags.Tree;
size += 2; // Size byte plus one byte
break;
default:
flags |= CompressedFlags.ScratchPad;
size += 1 + obj.Prim.ScratchPad.Length; // Size byte plus bytes
break;
}
}
if ((updateFlags & UpdateFlags.Text) != 0)
{
flags |= CompressedFlags.HasText;
textBytes = Utils.StringToBytes(obj.Prim.Text);
size += textBytes.Length; // Null-terminated, no size byte
size += 4; // Text color
}
if ((updateFlags & UpdateFlags.MediaURL) != 0)
{
flags |= CompressedFlags.MediaURL;
mediaURLBytes = Utils.StringToBytes(obj.Prim.MediaURL);
size += mediaURLBytes.Length; // Null-terminated, no size byte
}
if ((updateFlags & UpdateFlags.Particles) != 0)
{
flags |= CompressedFlags.HasParticles;
particleBytes = obj.Prim.ParticleSys.GetBytes();
size += particleBytes.Length; // Should be exactly 86 bytes
}
// Extra Params
extraParamBytes = obj.Prim.GetExtraParamsBytes();
size += extraParamBytes.Length;
if ((updateFlags & UpdateFlags.Sound) != 0)
{
flags |= CompressedFlags.HasSound;
size += 25; // SoundID, SoundGain, SoundFlags, SoundRadius
}
if ((updateFlags & UpdateFlags.NameValue) != 0)
{
flags |= CompressedFlags.HasNameValues;
nameValueBytes = Utils.StringToBytes(NameValue.NameValuesToString(obj.Prim.NameValues));
size += nameValueBytes.Length; // Null-terminated, no size byte
}
size += 23; // PrimData
size += 4; // Texture Length
textureBytes = obj.Prim.Textures.GetBytes();
size += textureBytes.Length; // Texture Entry
if ((updateFlags & UpdateFlags.TextureAnim) != 0)
{
flags |= CompressedFlags.TextureAnimation;
size += 4; // TextureAnim Length
textureAnimBytes = obj.Prim.TextureAnim.GetBytes();
size += textureAnimBytes.Length; // TextureAnim
}
#endregion Size calculation and field serialization
#region Packet serialization
int pos = 0;
byte[] data = new byte[size];
// UUID
obj.Prim.ID.ToBytes(data, 0);
pos += 16;
// LocalID
Utils.UIntToBytes(obj.Prim.LocalID, data, pos);
pos += 4;
// PCode
data[pos++] = (byte)obj.Prim.PrimData.PCode;
// State
data[pos++] = obj.Prim.PrimData.State;
// CRC
Utils.UIntToBytes(obj.CRC, data, pos);
pos += 4;
// Material
data[pos++] = (byte)obj.Prim.PrimData.Material;
// ClickAction
data[pos++] = (byte)obj.Prim.ClickAction;
// Scale
obj.Prim.Scale.ToBytes(data, pos);
pos += 12;
// Position
obj.Prim.Position.ToBytes(data, pos);
pos += 12;
// Rotation
obj.Prim.Rotation.ToBytes(data, pos);
pos += 12;
// Compressed Flags
Utils.UIntToBytes((uint)flags, data, pos);
pos += 4;
// OwnerID
obj.Prim.OwnerID.ToBytes(data, pos);
pos += 16;
if ((flags & CompressedFlags.HasAngularVelocity) != 0)
{
obj.Prim.AngularVelocity.ToBytes(data, pos);
pos += 12;
}
if ((flags & CompressedFlags.HasParent) != 0)
{
Utils.UIntToBytes(obj.Prim.ParentID, data, pos);
pos += 4;
}
if ((flags & CompressedFlags.ScratchPad) != 0)
{
data[pos++] = (byte)obj.Prim.ScratchPad.Length;
Buffer.BlockCopy(obj.Prim.ScratchPad, 0, data, pos, obj.Prim.ScratchPad.Length);
pos += obj.Prim.ScratchPad.Length;
}
else if ((flags & CompressedFlags.Tree) != 0)
{
data[pos++] = 1;
data[pos++] = (byte)obj.Prim.TreeSpecies;
}
if ((flags & CompressedFlags.HasText) != 0)
{
Buffer.BlockCopy(textBytes, 0, data, pos, textBytes.Length);
pos += textBytes.Length;
obj.Prim.TextColor.ToBytes(data, pos, false);
pos += 4;
}
if ((flags & CompressedFlags.MediaURL) != 0)
{
Buffer.BlockCopy(mediaURLBytes, 0, data, pos, mediaURLBytes.Length);
pos += mediaURLBytes.Length;
}
if ((flags & CompressedFlags.HasParticles) != 0)
{
Buffer.BlockCopy(particleBytes, 0, data, pos, particleBytes.Length);
pos += particleBytes.Length;
}
// Extra Params
Buffer.BlockCopy(extraParamBytes, 0, data, pos, extraParamBytes.Length);
pos += extraParamBytes.Length;
if ((flags & CompressedFlags.HasSound) != 0)
{
obj.Prim.Sound.ToBytes(data, pos);
pos += 16;
Utils.FloatToBytes(obj.Prim.SoundGain, data, pos);
pos += 4;
data[pos++] = (byte)obj.Prim.SoundFlags;
Utils.FloatToBytes(obj.Prim.SoundRadius, data, pos);
pos += 4;
}
if ((flags & CompressedFlags.HasNameValues) != 0)
{
Buffer.BlockCopy(nameValueBytes, 0, data, pos, nameValueBytes.Length);
pos += nameValueBytes.Length;
}
// Path PrimData
data[pos++] = (byte)obj.Prim.PrimData.PathCurve;
Utils.UInt16ToBytes(Primitive.PackBeginCut(obj.Prim.PrimData.PathBegin), data, pos); pos += 2;
Utils.UInt16ToBytes(Primitive.PackEndCut(obj.Prim.PrimData.PathEnd), data, pos); pos += 2;
data[pos++] = Primitive.PackPathScale(obj.Prim.PrimData.PathScaleX);
data[pos++] = Primitive.PackPathScale(obj.Prim.PrimData.PathScaleY);
data[pos++] = (byte)Primitive.PackPathShear(obj.Prim.PrimData.PathShearX);
data[pos++] = (byte)Primitive.PackPathShear(obj.Prim.PrimData.PathShearY);
data[pos++] = (byte)Primitive.PackPathTwist(obj.Prim.PrimData.PathTwist);
data[pos++] = (byte)Primitive.PackPathTwist(obj.Prim.PrimData.PathTwistBegin);
data[pos++] = (byte)Primitive.PackPathTwist(obj.Prim.PrimData.PathRadiusOffset);
data[pos++] = (byte)Primitive.PackPathTaper(obj.Prim.PrimData.PathTaperX);
data[pos++] = (byte)Primitive.PackPathTaper(obj.Prim.PrimData.PathTaperY);
data[pos++] = Primitive.PackPathRevolutions(obj.Prim.PrimData.PathRevolutions);
data[pos++] = (byte)Primitive.PackPathTwist(obj.Prim.PrimData.PathSkew);
// Profile PrimData
data[pos++] = obj.Prim.PrimData.profileCurve;
Utils.UInt16ToBytes(Primitive.PackBeginCut(obj.Prim.PrimData.ProfileBegin), data, pos); pos += 2;
Utils.UInt16ToBytes(Primitive.PackEndCut(obj.Prim.PrimData.ProfileEnd), data, pos); pos += 2;
Utils.UInt16ToBytes(Primitive.PackProfileHollow(obj.Prim.PrimData.ProfileHollow), data, pos); pos += 2;
// Texture Length
Utils.UIntToBytes((uint)textureBytes.Length, data, pos);
pos += 4;
// Texture Entry
Buffer.BlockCopy(textureBytes, 0, data, pos, textureBytes.Length);
pos += textureBytes.Length;
if ((flags & CompressedFlags.TextureAnimation) != 0)
{
Utils.UIntToBytes((uint)textureAnimBytes.Length, data, pos);
pos += 4;
Buffer.BlockCopy(textureAnimBytes, 0, data, pos, textureAnimBytes.Length);
pos += textureAnimBytes.Length;
}
#endregion Packet serialization
#region Packet sending
//Logger.DebugLog("Sending ObjectUpdateCompressed with " + flags.ToString());
if (sceneAgents.ContainsKey(obj.Prim.OwnerID))
{
// Send an update out to the creator
ObjectUpdateCompressedPacket updateToOwner = new ObjectUpdateCompressedPacket();
updateToOwner.RegionData.RegionHandle = regionHandle;
updateToOwner.RegionData.TimeDilation = (ushort)(timeDilation * (float)UInt16.MaxValue);
updateToOwner.ObjectData = new ObjectUpdateCompressedPacket.ObjectDataBlock[1];
updateToOwner.ObjectData[0] = new ObjectUpdateCompressedPacket.ObjectDataBlock();
updateToOwner.ObjectData[0].UpdateFlags = (uint)(obj.Prim.Flags | creatorFlags | PrimFlags.ObjectYouOwner);
updateToOwner.ObjectData[0].Data = data;
udp.SendPacket(obj.Prim.OwnerID, updateToOwner, PacketCategory.State);
}
// Send an update out to everyone else
ObjectUpdateCompressedPacket updateToOthers = new ObjectUpdateCompressedPacket();
updateToOthers.RegionData.RegionHandle = regionHandle;
updateToOthers.RegionData.TimeDilation = UInt16.MaxValue;
updateToOthers.ObjectData = new ObjectUpdateCompressedPacket.ObjectDataBlock[1];
updateToOthers.ObjectData[0] = new ObjectUpdateCompressedPacket.ObjectDataBlock();
updateToOthers.ObjectData[0].UpdateFlags = (uint)obj.Prim.Flags;
updateToOthers.ObjectData[0].Data = data;
ForEachAgent(
delegate(Agent recipient)
{
if (recipient.ID != obj.Prim.OwnerID)
udp.SendPacket(recipient.ID, updateToOthers, PacketCategory.State);
}
);
#endregion Packet sending
#endregion ObjectUpdateCompressed
}
else
{
#region ImprovedTerseObjectUpdate
//Logger.DebugLog("Sending ImprovedTerseObjectUpdate");
int pos = 0;
byte[] data = new byte[(obj.Prim is Avatar ? 60 : 44)];
// LocalID
Utils.UIntToBytes(obj.Prim.LocalID, data, pos);
pos += 4;
// Avatar/CollisionPlane
data[pos++] = obj.Prim.PrimData.State;
if (obj.Prim is Avatar)
{
data[pos++] = 1;
obj.Prim.CollisionPlane.ToBytes(data, pos);
pos += 16;
}
else
{
++pos;
}
// Position
obj.Prim.Position.ToBytes(data, pos);
pos += 12;
// Velocity
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Velocity.X, -128.0f, 128.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Velocity.Y, -128.0f, 128.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Velocity.Z, -128.0f, 128.0f), data, pos); pos += 2;
// Acceleration
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Acceleration.X, -64.0f, 64.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Acceleration.Y, -64.0f, 64.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Acceleration.Z, -64.0f, 64.0f), data, pos); pos += 2;
// Rotation
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Rotation.X, -1.0f, 1.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Rotation.Y, -1.0f, 1.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Rotation.Z, -1.0f, 1.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.Rotation.W, -1.0f, 1.0f), data, pos); pos += 2;
// Angular Velocity
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.AngularVelocity.X, -64.0f, 64.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.AngularVelocity.Y, -64.0f, 64.0f), data, pos); pos += 2;
Utils.UInt16ToBytes(Utils.FloatToUInt16(obj.Prim.AngularVelocity.Z, -64.0f, 64.0f), data, pos); pos += 2;
ImprovedTerseObjectUpdatePacket update = new ImprovedTerseObjectUpdatePacket();
update.RegionData.RegionHandle = RegionHandle;
update.RegionData.TimeDilation = (ushort)(timeDilation * (float)UInt16.MaxValue);
update.ObjectData = new ImprovedTerseObjectUpdatePacket.ObjectDataBlock[1];
update.ObjectData[0] = new ImprovedTerseObjectUpdatePacket.ObjectDataBlock();
update.ObjectData[0].Data = data;
if ((updateFlags & UpdateFlags.Textures) != 0)
{
byte[] textureBytes = obj.Prim.Textures.GetBytes();
byte[] textureEntry = new byte[textureBytes.Length + 4];
// Texture Length
Utils.IntToBytes(textureBytes.Length, textureEntry, 0);
// Texture
Buffer.BlockCopy(textureBytes, 0, textureEntry, 4, textureBytes.Length);
update.ObjectData[0].TextureEntry = textureEntry;
}
else
{
update.ObjectData[0].TextureEntry = Utils.EmptyBytes;
}
udp.BroadcastPacket(update, PacketCategory.State);
#endregion ImprovedTerseObjectUpdate
}
}
}
}