OSDN Git Service

RAD-154: Added //status command
[radegast/radegast.git] / Radegast / Core / StateManager.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above copyright
12 //       notice, this list of conditions and the following disclaimer in the
13 //       documentation and/or other materials provided with the distribution.
14 //     * Neither the name of the application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.Timers;
34 using System.Threading;
35 using OpenMetaverse;
36 using Radegast.Netcom;
37
38 namespace Radegast
39 {
40     public class KnownHeading
41     {
42         public string ID { get; set; }
43         public string Name { get; set; }
44         public Quaternion Heading { get; set; }
45         public KnownHeading(string id, string name, Quaternion heading)
46         {
47             this.ID = id;
48             this.Name = name;
49             this.Heading = heading;
50         }
51     }
52
53     public class StateManager : IDisposable
54     {
55         public Parcel Parcel { get; set; }
56
57         private RadegastInstance instance;
58         private GridClient client { get { return instance.Client; } }
59         private RadegastNetcom netcom { get { return instance.Netcom; } }
60
61         private bool typing = false;
62         private bool away = false;
63         private bool busy = false;
64         private bool flying = false;
65         private bool alwaysrun = false;
66         private bool sitting = false;
67
68         private bool following = false;
69         private string followName = string.Empty;
70         private float followDistance = 3.0f;
71         private UUID followID;
72
73         private UUID awayAnimationID = new UUID("fd037134-85d4-f241-72c6-4f42164fedee");
74         private UUID busyAnimationID = new UUID("efcf670c2d188128973a034ebc806b67");
75         private UUID typingAnimationID = new UUID("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9");
76         internal static Random rnd = new Random();
77         private System.Threading.Timer lookAtTimer;
78
79         /// <summary>
80         /// Passes walk state
81         /// </summary>
82         /// <param name="walking">True if we are walking towards a targer</param>
83         public delegate void WalkStateCanged(bool walking);
84
85         /// <summary>
86         /// Fires when we start or stop walking towards a target
87         /// </summary>
88         public event WalkStateCanged OnWalkStateCanged;
89
90         static List<KnownHeading> m_Headings;
91         public static List<KnownHeading> KnownHeadings
92         {
93             get
94             {
95                 if (m_Headings == null)
96                 {
97                     m_Headings = new List<KnownHeading>(16);
98                     m_Headings.Add(new KnownHeading("E", "East", new Quaternion(0.00000f, 0.00000f, 0.00000f, 1.00000f)));
99                     m_Headings.Add(new KnownHeading("ENE", "East by Northeast", new Quaternion(0.00000f, 0.00000f, 0.19509f, 0.98079f)));
100                     m_Headings.Add(new KnownHeading("NE", "Northeast", new Quaternion(0.00000f, 0.00000f, 0.38268f, 0.92388f)));
101                     m_Headings.Add(new KnownHeading("NNE", "North by Northeast", new Quaternion(0.00000f, 0.00000f, 0.55557f, 0.83147f)));
102                     m_Headings.Add(new KnownHeading("N", "North", new Quaternion(0.00000f, 0.00000f, 0.70711f, 0.70711f)));
103                     m_Headings.Add(new KnownHeading("NNW", "North by Northwest", new Quaternion(0.00000f, 0.00000f, 0.83147f, 0.55557f)));
104                     m_Headings.Add(new KnownHeading("NW", "Nortwest", new Quaternion(0.00000f, 0.00000f, 0.92388f, 0.38268f)));
105                     m_Headings.Add(new KnownHeading("WNW", "West by Northwest", new Quaternion(0.00000f, 0.00000f, 0.98079f, 0.19509f)));
106                     m_Headings.Add(new KnownHeading("W", "West", new Quaternion(0.00000f, 0.00000f, 1.00000f, -0.00000f)));
107                     m_Headings.Add(new KnownHeading("WSW", "West by Southwest", new Quaternion(0.00000f, 0.00000f, 0.98078f, -0.19509f)));
108                     m_Headings.Add(new KnownHeading("SW", "Southwest", new Quaternion(0.00000f, 0.00000f, 0.92388f, -0.38268f)));
109                     m_Headings.Add(new KnownHeading("SSW", "South by Southwest", new Quaternion(0.00000f, 0.00000f, 0.83147f, -0.55557f)));
110                     m_Headings.Add(new KnownHeading("S", "South", new Quaternion(0.00000f, 0.00000f, 0.70711f, -0.70711f)));
111                     m_Headings.Add(new KnownHeading("SSE", "South by Southeast", new Quaternion(0.00000f, 0.00000f, 0.55557f, -0.83147f)));
112                     m_Headings.Add(new KnownHeading("SE", "Southeast", new Quaternion(0.00000f, 0.00000f, 0.38268f, -0.92388f)));
113                     m_Headings.Add(new KnownHeading("ESE", "East by Southeast", new Quaternion(0.00000f, 0.00000f, 0.19509f, -0.98078f)));
114                 }
115                 return m_Headings;
116             }
117         }
118
119         public static Vector3 RotToEuler(Quaternion r)
120         {
121             Quaternion t = new Quaternion(r.X * r.X, r.Y * r.Y, r.Z * r.Z, r.W * r.W);
122
123             float m = (t.X + t.Y + t.Z + t.W);
124             if (Math.Abs(m) < 0.001) return Vector3.Zero;
125             float n = 2 * (r.Y * r.W + r.X * r.Z);
126             float p = m * m - n * n;
127
128             if (p > 0)
129                 return new Vector3(
130                     (float)Math.Atan2(2.0 * (r.X * r.W - r.Y * r.Z), (-t.X - t.Y + t.Z + t.W)),
131                     (float)Math.Atan2(n, Math.Sqrt(p)),
132                     (float)Math.Atan2(2.0 * (r.Z * r.W - r.X * r.Y), t.X - t.Y - t.Z + t.W)
133                     );
134             else if (n > 0)
135                 return new Vector3(
136                     0f,
137                     (float)(Math.PI / 2d),
138                     (float)Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Y)
139                     );
140             else
141                 return new Vector3(
142                     0f,
143                     -(float)(Math.PI / 2d),
144                     (float)Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Z)
145                     );
146         }
147
148         public static KnownHeading ClosestKnownHeading(int degrees)
149         {
150             KnownHeading ret = KnownHeadings[0];
151             int facing = (int)(57.2957795d * RotToEuler(KnownHeadings[0].Heading).Z);
152             if (facing < 0) facing += 360;
153             int minDistance = Math.Abs(degrees - facing);
154
155             for (int i = 1; i < KnownHeadings.Count; i++)
156             {
157                 facing = (int)(57.2957795d * RotToEuler(KnownHeadings[i].Heading).Z);
158                 if (facing < 0) facing += 360;
159
160                 int distance = Math.Abs(degrees - facing);
161                 if (distance < minDistance)
162                 {
163                     ret = KnownHeadings[i];
164                     minDistance = distance;
165                 }
166             }
167
168             return ret;
169         }
170
171         public StateManager(RadegastInstance instance)
172         {
173             this.instance = instance;
174             this.instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
175
176             beamTimer = new System.Timers.Timer();
177             beamTimer.Enabled = false;
178             beamTimer.Elapsed += new ElapsedEventHandler(beamTimer_Elapsed);
179
180             // Callbacks
181             netcom.ClientConnected += new EventHandler<EventArgs>(netcom_ClientConnected);
182             netcom.ClientDisconnected += new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
183             netcom.ChatReceived += new EventHandler<ChatEventArgs>(netcom_ChatReceived);
184             RegisterClientEvents(client);
185         }
186
187
188         private void RegisterClientEvents(GridClient client)
189         {
190             client.Objects.TerseObjectUpdate += new EventHandler<TerseObjectUpdateEventArgs>(Objects_TerseObjectUpdate);
191             client.Self.AlertMessage += new EventHandler<AlertMessageEventArgs>(Self_AlertMessage);
192             client.Self.TeleportProgress += new EventHandler<TeleportEventArgs>(Self_TeleportProgress);
193             client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
194         }
195
196         private void UnregisterClientEvents(GridClient client)
197         {
198             client.Objects.TerseObjectUpdate -= new EventHandler<TerseObjectUpdateEventArgs>(Objects_TerseObjectUpdate);
199             client.Self.AlertMessage -= new EventHandler<AlertMessageEventArgs>(Self_AlertMessage);
200             client.Self.TeleportProgress -= new EventHandler<TeleportEventArgs>(Self_TeleportProgress);
201             client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
202         }
203
204         void instance_ClientChanged(object sender, ClientChangedEventArgs e)
205         {
206             UnregisterClientEvents(e.OldClient);
207             RegisterClientEvents(client);
208         }
209
210         public void Dispose()
211         {
212             netcom.ClientConnected -= new EventHandler<EventArgs>(netcom_ClientConnected);
213             netcom.ClientDisconnected -= new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
214             netcom.ChatReceived -= new EventHandler<ChatEventArgs>(netcom_ChatReceived);
215             UnregisterClientEvents(client);
216             beamTimer.Dispose();
217             beamTimer = null;
218
219             if (lookAtTimer != null)
220             {
221                 lookAtTimer.Dispose();
222                 lookAtTimer = null;
223             }
224
225             if (walkTimer != null)
226             {
227                 walkTimer.Dispose();
228                 walkTimer = null;
229             }
230         }
231
232         public void SetRandomHeading()
233         {
234             client.Self.Movement.UpdateFromHeading(Utils.TWO_PI * rnd.NextDouble(), true);
235             LookInFront();
236         }
237
238         void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
239         {
240             if (e.Simulator == client.Network.CurrentSim)
241             {
242                 SetRandomHeading();
243             }
244         }
245
246         private UUID teleportEffect = UUID.Random();
247
248         void Self_TeleportProgress(object sender, TeleportEventArgs e)
249         {
250             if (!client.Network.Connected) return;
251
252             if (e.Status == TeleportStatus.Progress)
253             {
254                 client.Self.SphereEffect(client.Self.GlobalPosition, Color4.White, 4f, teleportEffect);
255             }
256
257             if (e.Status == TeleportStatus.Finished)
258             {
259                 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0f, teleportEffect);
260                 SetRandomHeading();
261             }
262
263             if (e.Status == TeleportStatus.Failed)
264             {
265                 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0f, teleportEffect);
266             }
267         }
268
269         void netcom_ClientDisconnected(object sender, DisconnectedEventArgs e)
270         {
271             typing = away = busy = walking = false;
272
273             if (lookAtTimer != null)
274             {
275                 lookAtTimer.Dispose();
276                 lookAtTimer = null;
277             }
278
279         }
280
281         void netcom_ClientConnected(object sender, EventArgs e)
282         {
283             client.Self.Movement.Camera.Far = 256f;
284             effectSource = client.Self.AgentID;
285
286             if (lookAtTimer == null)
287                 lookAtTimer = new System.Threading.Timer(new TimerCallback(lookAtTimerTick), null, Timeout.Infinite, Timeout.Infinite);
288         }
289
290         void Objects_TerseObjectUpdate(object sender, TerseObjectUpdateEventArgs e)
291         {
292             if (!e.Update.Avatar) return;
293             if (!following) return;
294
295             Avatar av;
296             client.Network.CurrentSim.ObjectsAvatars.TryGetValue(e.Update.LocalID, out av);
297             if (av == null) return;
298
299             if (av.Name == followName)
300             {
301                 Vector3 pos = AvatarPosition(client.Network.CurrentSim, av);
302
303                 FollowUpdate(pos);
304             }
305         }
306
307         void FollowUpdate(Vector3 pos)
308         {
309             if (Vector3.Distance(pos, client.Self.SimPosition) > followDistance)
310             {
311                 Vector3 target = pos + Vector3.Normalize(client.Self.SimPosition - pos) * (followDistance - 1f);
312                 client.Self.AutoPilotCancel();
313                 Vector3d glb = GlobalPosition(client.Network.CurrentSim, target);
314                 client.Self.AutoPilot(glb.X, glb.Y, glb.Z);
315             }
316             else
317             {
318                 client.Self.AutoPilotCancel();
319                 client.Self.Movement.TurnToward(pos);
320             }
321         }
322
323         public Quaternion AvatarRotation(Simulator sim, UUID avID)
324         {
325             Quaternion rot = Quaternion.Identity;
326             Avatar av = sim.ObjectsAvatars.Find((Avatar a) => { return a.ID == avID; });
327
328             if (av == null)
329                 return rot;
330
331             if (av.ParentID == 0)
332             {
333                 rot = av.Rotation;
334             }
335             else
336             {
337                 Primitive prim;
338                 if (sim.ObjectsPrimitives.TryGetValue(av.ParentID, out prim))
339                 {
340                     rot = prim.Rotation + av.Rotation;
341                 }
342             }
343
344             return rot;
345         }
346
347
348         public Vector3 AvatarPosition(Simulator sim, UUID avID)
349         {
350             Vector3 pos = Vector3.Zero;
351             Avatar av = sim.ObjectsAvatars.Find((Avatar a) => { return a.ID == avID; });
352             if (av != null)
353             {
354                 return AvatarPosition(sim, av);
355             }
356             else
357             {
358                 Vector3 coarse;
359                 if (sim.AvatarPositions.TryGetValue(avID, out coarse))
360                 {
361                     if (coarse.Z > 0.01)
362                         return coarse;
363                 }
364             }
365             return pos;
366         }
367
368         public Vector3 AvatarPosition(Simulator sim, Avatar av)
369         {
370             Vector3 pos = Vector3.Zero;
371
372             if (av.ParentID == 0)
373             {
374                 pos = av.Position;
375             }
376             else
377             {
378                 Primitive prim;
379                 if (sim.ObjectsPrimitives.TryGetValue(av.ParentID, out prim))
380                 {
381                     pos = prim.Position + av.Position;
382                 }
383             }
384
385             return pos;
386         }
387
388         public void Follow(string name, UUID id)
389         {
390             followName = name;
391             followID = id;
392             following = followID != UUID.Zero;
393
394             if (following)
395             {
396                 walking = false;
397
398                 Vector3 target = AvatarPosition(client.Network.CurrentSim, id);
399                 if (Vector3.Zero != target)
400                 {
401                     client.Self.Movement.TurnToward(target);
402                     FollowUpdate(target);
403                 }
404
405             }
406         }
407
408         #region Look at effect
409         private int lastLookAtEffect = 0;
410         private UUID lookAtEffect = UUID.Random();
411
412         /// <summary>
413         /// Set eye focus 3m in front of us
414         /// </summary>
415         public void LookInFront()
416         {
417             if (!client.Network.Connected) return;
418
419             client.Self.LookAtEffect(client.Self.AgentID, client.Self.AgentID,
420                 new Vector3d(new Vector3(3, 0, 0) * Quaternion.Identity),
421                 LookAtType.Idle, lookAtEffect);
422         }
423
424         void lookAtTimerTick(object state)
425         {
426             LookInFront();
427         }
428
429         void netcom_ChatReceived(object sender, ChatEventArgs e)
430         {
431             if (e.SourceID != client.Self.AgentID && (e.SourceType == ChatSourceType.Agent || e.Type == ChatType.StartTyping))
432             {
433                 // change focus max every 4 seconds
434                 if (Environment.TickCount - lastLookAtEffect > 4000)
435                 {
436                     lastLookAtEffect = Environment.TickCount;
437                     client.Self.LookAtEffect(client.Self.AgentID, e.SourceID, Vector3d.Zero, LookAtType.Respond, lookAtEffect);
438                     // keep looking at the speaker for 10 seconds
439                     if (lookAtTimer != null)
440                     {
441                         lookAtTimer.Change(10000, Timeout.Infinite);
442                     }
443                 }
444             }
445         }
446         #endregion Look at effect
447
448         #region Walking (move to)
449         private bool walking = false;
450         private System.Threading.Timer walkTimer;
451         private int walkChekInterval = 500;
452         private Vector3d walkToTarget;
453         int lastDistance = 0;
454         int lastDistanceChanged = 0;
455
456         public void WalkTo(Primitive prim)
457         {
458             WalkTo(GlobalPosition(prim));
459         }
460
461         public void WalkTo(Vector3d globalPos)
462         {
463             walkToTarget = globalPos;
464
465             if (following)
466             {
467                 following = false;
468                 followName = string.Empty;
469             }
470
471             if (walkTimer == null)
472             {
473                 walkTimer = new System.Threading.Timer(new TimerCallback(walkTimerElapsed), null, walkChekInterval, Timeout.Infinite);
474             }
475
476             lastDistanceChanged = System.Environment.TickCount;
477             client.Self.AutoPilotCancel();
478             walking = true;
479             client.Self.AutoPilot(walkToTarget.X, walkToTarget.Y, walkToTarget.Z);
480             FireWalkStateCanged();
481         }
482
483         void walkTimerElapsed(object sender)
484         {
485
486             double distance = Vector3d.Distance(client.Self.GlobalPosition, walkToTarget);
487
488             if (distance < 2d)
489             {
490                 // We're there
491                 EndWalking();
492             }
493             else
494             {
495                 if (lastDistance != (int)distance)
496                 {
497                     lastDistanceChanged = System.Environment.TickCount;
498                     lastDistance = (int)distance;
499                 }
500                 else if ((System.Environment.TickCount - lastDistanceChanged) > 10000)
501                 {
502                     // Our distance to the target has not changed in 10s, give up
503                     EndWalking();
504                     return;
505                 }
506                 walkTimer.Change(walkChekInterval, Timeout.Infinite);
507             }
508         }
509
510         void Self_AlertMessage(object sender, AlertMessageEventArgs e)
511         {
512             if (e.Message.Contains("Autopilot cancel"))
513             {
514                 if (walking)
515                 {
516                     EndWalking();
517                 }
518             }
519         }
520
521         void FireWalkStateCanged()
522         {
523             if (OnWalkStateCanged != null)
524             {
525                 try { OnWalkStateCanged(walking); }
526                 catch (Exception) { }
527             }
528         }
529
530         public void EndWalking()
531         {
532             if (walking)
533             {
534                 walking = false;
535                 Logger.Log("Finished walking.", Helpers.LogLevel.Debug, client);
536                 walkTimer.Dispose();
537                 walkTimer = null;
538                 client.Self.AutoPilotCancel();
539                 FireWalkStateCanged();
540             }
541         }
542         #endregion
543
544         public void SetTyping(bool typing)
545         {
546             if (!client.Network.Connected) return;
547
548             Dictionary<UUID, bool> typingAnim = new Dictionary<UUID, bool>();
549             typingAnim.Add(typingAnimationID, typing);
550
551             client.Self.Animate(typingAnim, false);
552
553             if (typing)
554                 client.Self.Chat(string.Empty, 0, ChatType.StartTyping);
555             else
556                 client.Self.Chat(string.Empty, 0, ChatType.StopTyping);
557
558             this.typing = typing;
559         }
560
561         public void SetAway(bool away)
562         {
563             Dictionary<UUID, bool> awayAnim = new Dictionary<UUID, bool>();
564             awayAnim.Add(awayAnimationID, away);
565
566             client.Self.Animate(awayAnim, true);
567             this.away = away;
568         }
569
570         public void SetBusy(bool busy)
571         {
572             Dictionary<UUID, bool> busyAnim = new Dictionary<UUID, bool>();
573             busyAnim.Add(busyAnimationID, busy);
574
575             client.Self.Animate(busyAnim, true);
576             this.busy = busy;
577         }
578
579         public void SetFlying(bool flying)
580         {
581             this.flying = client.Self.Movement.Fly = flying;
582         }
583
584         public void SetAlwaysRun(bool alwaysrun)
585         {
586             this.alwaysrun = client.Self.Movement.AlwaysRun = alwaysrun;
587         }
588
589         public void SetSitting(bool sitting, UUID target)
590         {
591             this.sitting = sitting;
592
593             if (sitting)
594             {
595                 client.Self.RequestSit(target, Vector3.Zero);
596                 client.Self.Sit();
597             }
598             else
599             {
600                 client.Self.Stand();
601             }
602         }
603
604         public Vector3d GlobalPosition(Simulator sim, Vector3 pos)
605         {
606             uint globalX, globalY;
607             Utils.LongToUInts(sim.Handle, out globalX, out globalY);
608
609             return new Vector3d(
610                 (double)globalX + (double)pos.X,
611                 (double)globalY + (double)pos.Y,
612                 (double)pos.Z);
613         }
614
615         public Vector3d GlobalPosition(Primitive prim)
616         {
617             return GlobalPosition(client.Network.CurrentSim, prim.Position);
618         }
619
620         private System.Timers.Timer beamTimer;
621         private List<Vector3d> beamTarget;
622         private Random beamRandom = new Random();
623         private UUID pointID;
624         private UUID sphereID;
625         private List<UUID> beamID;
626         private int numBeans;
627         private Color4[] beamColors = new Color4[3] { new Color4(0, 255, 0, 255), new Color4(255, 0, 0, 255), new Color4(0, 0, 255, 255) };
628         private Primitive targetPrim;
629         private UUID effectSource;
630
631         public void UnSetPointing()
632         {
633             beamTimer.Enabled = false;
634             if (pointID != UUID.Zero)
635             {
636                 client.Self.PointAtEffect(effectSource, UUID.Zero, Vector3d.Zero, PointAtType.None, pointID);
637                 pointID = UUID.Zero;
638             }
639
640             if (beamID != null)
641             {
642                 foreach (UUID id in beamID)
643                 {
644                     client.Self.BeamEffect(UUID.Zero, UUID.Zero, Vector3d.Zero, new Color4(255, 255, 255, 255), 0, id);
645                 }
646                 beamID = null;
647             }
648
649             if (sphereID != UUID.Zero)
650             {
651                 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0, sphereID);
652                 sphereID = UUID.Zero;
653             }
654
655         }
656
657         void beamTimer_Elapsed(object sender, EventArgs e)
658         {
659             if (beamID == null) return;
660
661             try
662             {
663                 client.Self.SphereEffect(GlobalPosition(targetPrim), beamColors[beamRandom.Next(0, 3)], 0.85f, sphereID);
664                 int i = 0;
665                 for (i = 0; i < numBeans; i++)
666                 {
667                     UUID newBeam = UUID.Random();
668                     Vector3d scatter;
669
670                     if (i == 0)
671                     {
672                         scatter = GlobalPosition(targetPrim);
673                     }
674                     else
675                     {
676                         Vector3d direction = client.Self.GlobalPosition - GlobalPosition(targetPrim);
677                         Vector3d cross = direction % new Vector3d(0, 0, 1);
678                         cross.Normalize();
679                         scatter = GlobalPosition(targetPrim) + cross * (i * 0.2d) * (i % 2 == 0 ? 1 : -1);
680                     }
681                     client.Self.BeamEffect(effectSource, UUID.Zero, scatter, beamColors[beamRandom.Next(0, 3)], 1.0f, beamID[i]);
682                 }
683
684                 for (int j = 1; j < numBeans; j++)
685                 {
686                     UUID newBeam = UUID.Random();
687                     Vector3d scatter;
688                     Vector3d cross = new Vector3d(0, 0, 1);
689                     cross.Normalize();
690                     scatter = GlobalPosition(targetPrim) + cross * (j * 0.2d) * (j % 2 == 0 ? 1 : -1);
691
692                     client.Self.BeamEffect(effectSource, UUID.Zero, scatter, beamColors[beamRandom.Next(0, 3)], 1.0f, beamID[j + i - 1]);
693                 }
694             }
695             catch (Exception) { };
696
697         }
698
699         public void SetPointing(Primitive prim, int numBeans)
700         {
701             UnSetPointing();
702             client.Self.Movement.TurnToward(prim.Position);
703             pointID = UUID.Random();
704             sphereID = UUID.Random();
705             beamID = new List<UUID>();
706             beamTarget = new List<Vector3d>();
707             targetPrim = prim;
708             this.numBeans = numBeans;
709
710             client.Self.PointAtEffect(effectSource, prim.ID, Vector3d.Zero, PointAtType.Select, pointID);
711
712             for (int i = 0; i < numBeans; i++)
713             {
714                 UUID newBeam = UUID.Random();
715                 beamID.Add(newBeam);
716                 beamTarget.Add(Vector3d.Zero);
717             }
718
719             for (int i = 1; i < numBeans; i++)
720             {
721                 UUID newBeam = UUID.Random();
722                 beamID.Add(newBeam);
723                 beamTarget.Add(Vector3d.Zero);
724             }
725
726             beamTimer.Interval = 1000;
727             beamTimer.Enabled = true;
728         }
729
730         public UUID TypingAnimationID
731         {
732             get { return typingAnimationID; }
733             set { typingAnimationID = value; }
734         }
735
736         public UUID AwayAnimationID
737         {
738             get { return awayAnimationID; }
739             set { awayAnimationID = value; }
740         }
741
742         public UUID BusyAnimationID
743         {
744             get { return busyAnimationID; }
745             set { busyAnimationID = value; }
746         }
747
748         public UUID EffectSource
749         {
750             get { return effectSource; }
751             set { effectSource = value; }
752         }
753
754         public bool IsTyping
755         {
756             get { return typing; }
757         }
758
759         public bool IsAway
760         {
761             get { return away; }
762         }
763
764         public bool IsBusy
765         {
766             get { return busy; }
767         }
768
769         public bool IsFlying
770         {
771             get { return flying; }
772         }
773
774         public bool IsSitting
775         {
776             get { return sitting; }
777         }
778
779         public bool IsPointing
780         {
781             get { return pointID != UUID.Zero; }
782         }
783
784         public bool IsFollowing
785         {
786             get { return following; }
787         }
788
789         public string FollowName
790         {
791             get { return followName; }
792             set { followName = value; }
793         }
794
795         public float FollowDistance
796         {
797             get { return followDistance; }
798             set { followDistance = value; }
799         }
800
801         public bool IsWalking
802         {
803             get { return walking; }
804         }
805     }
806 }