* Created an LLUDP folder to hold extensions that are purely LLUDP packet handlers. This is not a complete abstraction away from transport protocols, but it's a start * Moved physics code from Movement.cs into PhysicsSimple.cs, and moved the physics loop into a thread in SceneManager * Simian.ini cleanup git-svn-id: http://libopenmetaverse.googlecode.com/svn/libopenmetaverse/trunk@2490 52acb1d6-8a22-11de-b505-999d5b087335
1763 lines
73 KiB
C#
1763 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;
|
|
Agent agent;
|
|
|
|
if (sceneObjects.TryGetValue(localID, out obj))
|
|
{
|
|
if (sceneAgents.TryGetValue(obj.Prim.ID, out agent))
|
|
AgentRemove(sender, agent);
|
|
|
|
if (OnObjectRemove != null)
|
|
OnObjectRemove(sender, obj);
|
|
|
|
sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID);
|
|
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool ObjectRemove(object sender, UUID id)
|
|
{
|
|
SimulationObject obj;
|
|
Agent agent;
|
|
|
|
if (sceneObjects.TryGetValue(id, out obj))
|
|
{
|
|
if (sceneAgents.TryGetValue(id, out agent))
|
|
AgentRemove(sender, agent);
|
|
|
|
if (OnObjectRemove != null)
|
|
OnObjectRemove(sender, obj);
|
|
|
|
sceneObjects.Remove(obj.Prim.LocalID, obj.Prim.ID);
|
|
|
|
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);
|
|
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 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
|
|
}
|
|
}
|
|
}
|
|
}
|