2 // Radegast Metaverse Client
3 // Copyright (c) 2009, Radegast Development Team
4 // All rights reserved.
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
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.
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.
32 using System.Collections.Generic;
34 using System.Threading;
36 using Radegast.Netcom;
40 public class KnownHeading
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)
49 this.Heading = heading;
53 public class StateManager : IDisposable
55 public Parcel Parcel { get; set; }
57 private RadegastInstance instance;
58 private GridClient client { get { return instance.Client; } }
59 private RadegastNetcom netcom { get { return instance.Netcom; } }
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;
68 private bool following = false;
69 private string followName = string.Empty;
70 private float followDistance = 3.0f;
71 private UUID followID;
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;
82 /// <param name="walking">True if we are walking towards a targer</param>
83 public delegate void WalkStateCanged(bool walking);
86 /// Fires when we start or stop walking towards a target
88 public event WalkStateCanged OnWalkStateCanged;
90 static List<KnownHeading> m_Headings;
91 public static List<KnownHeading> KnownHeadings
95 if (m_Headings == null)
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)));
119 public static Vector3 RotToEuler(Quaternion r)
121 Quaternion t = new Quaternion(r.X * r.X, r.Y * r.Y, r.Z * r.Z, r.W * r.W);
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;
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)
137 (float)(Math.PI / 2d),
138 (float)Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Y)
143 -(float)(Math.PI / 2d),
144 (float)Math.Atan2((r.Z * r.W + r.X * r.Y), 0.5 - t.X - t.Z)
148 public static KnownHeading ClosestKnownHeading(int degrees)
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);
155 for (int i = 1; i < KnownHeadings.Count; i++)
157 facing = (int)(57.2957795d * RotToEuler(KnownHeadings[i].Heading).Z);
158 if (facing < 0) facing += 360;
160 int distance = Math.Abs(degrees - facing);
161 if (distance < minDistance)
163 ret = KnownHeadings[i];
164 minDistance = distance;
171 public StateManager(RadegastInstance instance)
173 this.instance = instance;
174 this.instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
176 beamTimer = new System.Timers.Timer();
177 beamTimer.Enabled = false;
178 beamTimer.Elapsed += new ElapsedEventHandler(beamTimer_Elapsed);
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);
188 private void RegisterClientEvents(GridClient client)
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);
196 private void UnregisterClientEvents(GridClient client)
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);
204 void instance_ClientChanged(object sender, ClientChangedEventArgs e)
206 UnregisterClientEvents(e.OldClient);
207 RegisterClientEvents(client);
210 public void Dispose()
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);
219 if (lookAtTimer != null)
221 lookAtTimer.Dispose();
225 if (walkTimer != null)
232 public void SetRandomHeading()
234 client.Self.Movement.UpdateFromHeading(Utils.TWO_PI * rnd.NextDouble(), true);
238 void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
240 if (e.Simulator == client.Network.CurrentSim)
246 private UUID teleportEffect = UUID.Random();
248 void Self_TeleportProgress(object sender, TeleportEventArgs e)
250 if (!client.Network.Connected) return;
252 if (e.Status == TeleportStatus.Progress)
254 client.Self.SphereEffect(client.Self.GlobalPosition, Color4.White, 4f, teleportEffect);
257 if (e.Status == TeleportStatus.Finished)
259 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0f, teleportEffect);
263 if (e.Status == TeleportStatus.Failed)
265 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0f, teleportEffect);
269 void netcom_ClientDisconnected(object sender, DisconnectedEventArgs e)
271 typing = away = busy = walking = false;
273 if (lookAtTimer != null)
275 lookAtTimer.Dispose();
281 void netcom_ClientConnected(object sender, EventArgs e)
283 client.Self.Movement.Camera.Far = 256f;
284 effectSource = client.Self.AgentID;
286 if (lookAtTimer == null)
287 lookAtTimer = new System.Threading.Timer(new TimerCallback(lookAtTimerTick), null, Timeout.Infinite, Timeout.Infinite);
290 void Objects_TerseObjectUpdate(object sender, TerseObjectUpdateEventArgs e)
292 if (!e.Update.Avatar) return;
293 if (!following) return;
296 client.Network.CurrentSim.ObjectsAvatars.TryGetValue(e.Update.LocalID, out av);
297 if (av == null) return;
299 if (av.Name == followName)
301 Vector3 pos = AvatarPosition(client.Network.CurrentSim, av);
307 void FollowUpdate(Vector3 pos)
309 if (Vector3.Distance(pos, client.Self.SimPosition) > followDistance)
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);
318 client.Self.AutoPilotCancel();
319 client.Self.Movement.TurnToward(pos);
323 public Quaternion AvatarRotation(Simulator sim, UUID avID)
325 Quaternion rot = Quaternion.Identity;
326 Avatar av = sim.ObjectsAvatars.Find((Avatar a) => { return a.ID == avID; });
331 if (av.ParentID == 0)
338 if (sim.ObjectsPrimitives.TryGetValue(av.ParentID, out prim))
340 rot = prim.Rotation + av.Rotation;
348 public Vector3 AvatarPosition(Simulator sim, UUID avID)
350 Vector3 pos = Vector3.Zero;
351 Avatar av = sim.ObjectsAvatars.Find((Avatar a) => { return a.ID == avID; });
354 return AvatarPosition(sim, av);
359 if (sim.AvatarPositions.TryGetValue(avID, out coarse))
368 public Vector3 AvatarPosition(Simulator sim, Avatar av)
370 Vector3 pos = Vector3.Zero;
372 if (av.ParentID == 0)
379 if (sim.ObjectsPrimitives.TryGetValue(av.ParentID, out prim))
381 pos = prim.Position + av.Position;
388 public void Follow(string name, UUID id)
392 following = followID != UUID.Zero;
398 Vector3 target = AvatarPosition(client.Network.CurrentSim, id);
399 if (Vector3.Zero != target)
401 client.Self.Movement.TurnToward(target);
402 FollowUpdate(target);
408 #region Look at effect
409 private int lastLookAtEffect = 0;
410 private UUID lookAtEffect = UUID.Random();
413 /// Set eye focus 3m in front of us
415 public void LookInFront()
417 if (!client.Network.Connected) return;
419 client.Self.LookAtEffect(client.Self.AgentID, client.Self.AgentID,
420 new Vector3d(new Vector3(3, 0, 0) * Quaternion.Identity),
421 LookAtType.Idle, lookAtEffect);
424 void lookAtTimerTick(object state)
429 void netcom_ChatReceived(object sender, ChatEventArgs e)
431 if (e.SourceID != client.Self.AgentID && (e.SourceType == ChatSourceType.Agent || e.Type == ChatType.StartTyping))
433 // change focus max every 4 seconds
434 if (Environment.TickCount - lastLookAtEffect > 4000)
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)
441 lookAtTimer.Change(10000, Timeout.Infinite);
446 #endregion Look at effect
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;
456 public void WalkTo(Primitive prim)
458 WalkTo(GlobalPosition(prim));
461 public void WalkTo(Vector3d globalPos)
463 walkToTarget = globalPos;
468 followName = string.Empty;
471 if (walkTimer == null)
473 walkTimer = new System.Threading.Timer(new TimerCallback(walkTimerElapsed), null, walkChekInterval, Timeout.Infinite);
476 lastDistanceChanged = System.Environment.TickCount;
477 client.Self.AutoPilotCancel();
479 client.Self.AutoPilot(walkToTarget.X, walkToTarget.Y, walkToTarget.Z);
480 FireWalkStateCanged();
483 void walkTimerElapsed(object sender)
486 double distance = Vector3d.Distance(client.Self.GlobalPosition, walkToTarget);
495 if (lastDistance != (int)distance)
497 lastDistanceChanged = System.Environment.TickCount;
498 lastDistance = (int)distance;
500 else if ((System.Environment.TickCount - lastDistanceChanged) > 10000)
502 // Our distance to the target has not changed in 10s, give up
506 walkTimer.Change(walkChekInterval, Timeout.Infinite);
510 void Self_AlertMessage(object sender, AlertMessageEventArgs e)
512 if (e.Message.Contains("Autopilot cancel"))
521 void FireWalkStateCanged()
523 if (OnWalkStateCanged != null)
525 try { OnWalkStateCanged(walking); }
526 catch (Exception) { }
530 public void EndWalking()
535 Logger.Log("Finished walking.", Helpers.LogLevel.Debug, client);
538 client.Self.AutoPilotCancel();
539 FireWalkStateCanged();
544 public void SetTyping(bool typing)
546 if (!client.Network.Connected) return;
548 Dictionary<UUID, bool> typingAnim = new Dictionary<UUID, bool>();
549 typingAnim.Add(typingAnimationID, typing);
551 client.Self.Animate(typingAnim, false);
554 client.Self.Chat(string.Empty, 0, ChatType.StartTyping);
556 client.Self.Chat(string.Empty, 0, ChatType.StopTyping);
558 this.typing = typing;
561 public void SetAway(bool away)
563 Dictionary<UUID, bool> awayAnim = new Dictionary<UUID, bool>();
564 awayAnim.Add(awayAnimationID, away);
566 client.Self.Animate(awayAnim, true);
570 public void SetBusy(bool busy)
572 Dictionary<UUID, bool> busyAnim = new Dictionary<UUID, bool>();
573 busyAnim.Add(busyAnimationID, busy);
575 client.Self.Animate(busyAnim, true);
579 public void SetFlying(bool flying)
581 this.flying = client.Self.Movement.Fly = flying;
584 public void SetAlwaysRun(bool alwaysrun)
586 this.alwaysrun = client.Self.Movement.AlwaysRun = alwaysrun;
589 public void SetSitting(bool sitting, UUID target)
591 this.sitting = sitting;
595 client.Self.RequestSit(target, Vector3.Zero);
604 public Vector3d GlobalPosition(Simulator sim, Vector3 pos)
606 uint globalX, globalY;
607 Utils.LongToUInts(sim.Handle, out globalX, out globalY);
610 (double)globalX + (double)pos.X,
611 (double)globalY + (double)pos.Y,
615 public Vector3d GlobalPosition(Primitive prim)
617 return GlobalPosition(client.Network.CurrentSim, prim.Position);
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;
631 public void UnSetPointing()
633 beamTimer.Enabled = false;
634 if (pointID != UUID.Zero)
636 client.Self.PointAtEffect(effectSource, UUID.Zero, Vector3d.Zero, PointAtType.None, pointID);
642 foreach (UUID id in beamID)
644 client.Self.BeamEffect(UUID.Zero, UUID.Zero, Vector3d.Zero, new Color4(255, 255, 255, 255), 0, id);
649 if (sphereID != UUID.Zero)
651 client.Self.SphereEffect(Vector3d.Zero, Color4.White, 0, sphereID);
652 sphereID = UUID.Zero;
657 void beamTimer_Elapsed(object sender, EventArgs e)
659 if (beamID == null) return;
663 client.Self.SphereEffect(GlobalPosition(targetPrim), beamColors[beamRandom.Next(0, 3)], 0.85f, sphereID);
665 for (i = 0; i < numBeans; i++)
667 UUID newBeam = UUID.Random();
672 scatter = GlobalPosition(targetPrim);
676 Vector3d direction = client.Self.GlobalPosition - GlobalPosition(targetPrim);
677 Vector3d cross = direction % new Vector3d(0, 0, 1);
679 scatter = GlobalPosition(targetPrim) + cross * (i * 0.2d) * (i % 2 == 0 ? 1 : -1);
681 client.Self.BeamEffect(effectSource, UUID.Zero, scatter, beamColors[beamRandom.Next(0, 3)], 1.0f, beamID[i]);
684 for (int j = 1; j < numBeans; j++)
686 UUID newBeam = UUID.Random();
688 Vector3d cross = new Vector3d(0, 0, 1);
690 scatter = GlobalPosition(targetPrim) + cross * (j * 0.2d) * (j % 2 == 0 ? 1 : -1);
692 client.Self.BeamEffect(effectSource, UUID.Zero, scatter, beamColors[beamRandom.Next(0, 3)], 1.0f, beamID[j + i - 1]);
695 catch (Exception) { };
699 public void SetPointing(Primitive prim, int numBeans)
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>();
708 this.numBeans = numBeans;
710 client.Self.PointAtEffect(effectSource, prim.ID, Vector3d.Zero, PointAtType.Select, pointID);
712 for (int i = 0; i < numBeans; i++)
714 UUID newBeam = UUID.Random();
716 beamTarget.Add(Vector3d.Zero);
719 for (int i = 1; i < numBeans; i++)
721 UUID newBeam = UUID.Random();
723 beamTarget.Add(Vector3d.Zero);
726 beamTimer.Interval = 1000;
727 beamTimer.Enabled = true;
730 public UUID TypingAnimationID
732 get { return typingAnimationID; }
733 set { typingAnimationID = value; }
736 public UUID AwayAnimationID
738 get { return awayAnimationID; }
739 set { awayAnimationID = value; }
742 public UUID BusyAnimationID
744 get { return busyAnimationID; }
745 set { busyAnimationID = value; }
748 public UUID EffectSource
750 get { return effectSource; }
751 set { effectSource = value; }
756 get { return typing; }
771 get { return flying; }
774 public bool IsSitting
776 get { return sitting; }
779 public bool IsPointing
781 get { return pointID != UUID.Zero; }
784 public bool IsFollowing
786 get { return following; }
789 public string FollowName
791 get { return followName; }
792 set { followName = value; }
795 public float FollowDistance
797 get { return followDistance; }
798 set { followDistance = value; }
801 public bool IsWalking
803 get { return walking; }