public override void Bind(Entity result, Main main, bool creating = false)
        {
            this.SetMain(result, main);

            Transform transform = result.Get<Transform>();
            Sound sound = result.Get<Sound>("Sound");

            sound.Add(new Binding<Vector3>(sound.Position, transform.Position));

            result.CannotSuspendByDistance = !sound.Is3D;
            result.Add(new NotifyBinding(delegate()
            {
                result.CannotSuspendByDistance = !sound.Is3D;
            }, sound.Is3D));

            Property<float> min = result.GetProperty<float>("MinimumInterval");
            Property<float> max = result.GetProperty<float>("MaximumInterval");

            Random random = new Random();
            float interval = min + ((float)random.NextDouble() * (max - min));
            result.Add(new Updater
            {
                delegate(float dt)
                {
                    if (!sound.IsPlaying)
                        interval -= dt;
                    if (interval <= 0)
                    {
                        sound.Play.Execute();
                        interval = min + ((float)random.NextDouble() * (max - min));
                    }
                }
            });
        }
 public override void Bind(Entity result, Main main, bool creating = false)
 {
     this.SetMain(result, main);
     Transform transform = result.Get<Transform>();
     AnimatedModel model = result.Get<AnimatedModel>("Model");
     model.Add(new Binding<Matrix>(model.Transform, transform.Matrix));
     Property<string> animation = result.GetProperty<string>("Animation");
     Property<bool> loop = result.GetProperty<bool>("Loop");
     model.Add(new NotifyBinding(delegate()
     {
         model.Stop();
         if (animation != null)
             model.StartClip(animation, 0, loop);
     }, animation, loop));
 }
Beispiel #3
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            result.CannotSuspend = true;

            Script script = result.Get<Script>();

            Property<bool> executeOnLoad = result.GetProperty<bool>("ExecuteOnLoad");
            if (executeOnLoad && !main.EditorEnabled)
            {
                result.Add("Executor", new PostInitialization
                {
                    delegate()
                    {
                        if (executeOnLoad)
                            script.Execute.Execute();
                    }
                });
            }

            Property<bool> deleteOnExecute = result.GetOrMakeProperty<bool>("DeleteOnExecute", true, false);
            if (deleteOnExecute)
                result.Add(new CommandBinding(script.Execute, result.Delete));

            this.SetMain(result, main);
        }
Beispiel #4
0
        protected override void Validate(Entity entity, RuleArgs e)
        {
            var value = Convert.ToDouble(entity.GetProperty(e.Property));

            var min = this.Min;
            if (value < min)
            {
                if (this.MessageBuilder != null)
                {
                    e.BrokenDescription = this.MessageBuilder(entity);
                }
                else
                {
                    e.BrokenDescription = string.Format("{0} 不能低于 {1}。".Translate(), e.DisplayProperty(), min);
                }
            }
            else
            {
                var max = this.Max;
                if (value > max)
                {
                    if (this.MessageBuilder != null)
                    {
                        e.BrokenDescription = this.MessageBuilder(entity);
                    }
                    else
                    {
                        e.BrokenDescription = string.Format("{0} 不能超过 {1}。".Translate(), e.DisplayProperty(), max);
                    }
                }
            }
        }
Beispiel #5
0
        protected override void Validate(Entity entity, RuleArgs e)
        {
            var value = Convert.ToDouble(entity.GetProperty(e.Property));

            if (value <= 0)
            {
                if (this.MessageBuilder != null)
                {
                    e.BrokenDescription = this.MessageBuilder(entity);
                }
                else
                {
                    e.BrokenDescription = string.Format("{0} 需要是正数。".Translate(), e.DisplayProperty());
                }
            }
        }
Beispiel #6
0
        public override void AttachEditorComponents(Entity result, Main main)
        {
            Model model = new Model();
            model.Filename.Value = "Models\\light";
            model.Color.Value = this.Color;
            model.Editable = false;
            model.Serialize = false;

            result.Add("EditorModel", model);

            Transform transform = result.Get<Transform>();
            Property<Direction> dir = result.GetProperty<Direction>("Direction");

            model.Add(new Binding<Matrix>(model.Transform, delegate()
            {
                Vector3 pos = transform.Position;
                return PlatformFactory.rotationMatrices[(int)dir.Value] * Matrix.CreateTranslation(pos);
            }, transform.Position, dir));
        }
 protected override void Validate(Entity entity, RuleArgs e)
 {
     var value = entity.GetProperty(e.Property) as string;
     if (!string.IsNullOrEmpty(value))
     {
         var min = this.Min;
         if (value.Length < min)
         {
             if (this.MessageBuilder != null)
             {
                 e.BrokenDescription = this.MessageBuilder(entity);
             }
             else
             {
                 e.BrokenDescription = string.Format(
                     "{0} 不能低于 {1} 个字符。".Translate(),
                     e.DisplayProperty(), min
                     );
             }
         }
         else
         {
             var max = this.Max;
             if (value.Length > max)
             {
                 if (this.MessageBuilder != null)
                 {
                     e.BrokenDescription = this.MessageBuilder(entity);
                 }
                 else
                 {
                     e.BrokenDescription = string.Format(
                         "{0} 不能超过 {1} 个字符。".Translate(),
                         e.DisplayProperty(), max
                         );
                 }
             }
         }
     }
 }
Beispiel #8
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            result.CannotSuspend = true;

            Transform transform = result.Get<Transform>();
            PhysicsBlock physics = result.Get<PhysicsBlock>();
            ModelInstance model = result.Get<ModelInstance>();

            physics.Add(new TwoWayBinding<Matrix>(transform.Matrix, physics.Transform));

            Property<string> soundCue = result.GetProperty<string>("CollisionSoundCue");

            Property<Vector3> scale = new Property<Vector3> { Value = Vector3.One };

            model.Add(new Binding<Matrix>(model.Transform, () => Matrix.CreateScale(scale) * transform.Matrix, scale, transform.Matrix));

            const float volumeMultiplier = 0.003f;

            physics.Add(new CommandBinding<Collidable, ContactCollection>(physics.Collided, delegate(Collidable collidable, ContactCollection contacts)
            {
                float volume = contacts[contacts.Count - 1].NormalImpulse * volumeMultiplier;
                if (volume > 0.2f && soundCue.Value != null)
                {
                    Sound sound = Sound.PlayCue(main, soundCue, transform.Position, volume, 0.05f);
                    if (sound != null)
                        sound.GetProperty("Pitch").Value = 1.0f;
                }
            }));

            result.Add("Fade", new Animation
            (
                new Animation.Delay(5.0f),
                new Animation.Vector3MoveTo(scale, Vector3.Zero, 1.0f),
                new Animation.Execute(delegate() { result.Delete.Execute(); })
            ));

            this.SetMain(result, main);
            PhysicsBlock.CancelPlayerCollisions(physics);
        }
Beispiel #9
0
        protected override void Validate(Entity entity, RuleArgs e)
        {
            var value = (string)entity.GetProperty(e.Property) ?? string.Empty;

            if (!string.IsNullOrEmpty(value))
            {
                if (!this.Regex.IsMatch(value))
                {
                    if (this.MessageBuilder != null)
                    {
                        e.BrokenDescription = this.MessageBuilder(entity);
                    }
                    else
                    {
                        e.BrokenDescription = string.Format(
                            "{0} 必须是 {1}。".Translate(),
                            e.DisplayProperty(),
                            this.RegexLabel.Translate()
                            );
                    }
                }
            }
        }
Beispiel #10
0
        protected override void Validate(Entity entity, RuleArgs e)
        {
            var property = e.Property;

            bool isNull = false;

            if (property is IRefProperty)
            {
                var id = entity.GetRefNullableId((property as IRefProperty).RefIdProperty);
                isNull = id == null;
            }
            else
            {
                var value = entity.GetProperty(property);
                if (property.PropertyType == typeof(string))
                {
                    isNull = string.IsNullOrEmpty(value as string);
                }
                else
                {
                    isNull = value == null;
                }
            }

            if (isNull)
            {
                if (this.MessageBuilder != null)
                {
                    e.BrokenDescription = this.MessageBuilder(entity);
                }
                else
                {
                    e.BrokenDescription = string.Format("{0} 里没有输入值。".Translate(), e.DisplayProperty());
                }
            }
        }
Beispiel #11
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            base.Bind(result, main);

            Transform transform = result.Get<Transform>();
            DynamicMap map = result.Get<DynamicMap>();

            Direction initialDirection = result.GetProperty<Direction>("Direction");
            Direction dir = initialDirection;

            Entity.Handle limit1 = result.GetProperty<Entity.Handle>("Limit 1");
            Entity.Handle limit2 = result.GetProperty<Entity.Handle>("Limit 2");

            Property<bool> isAtStart = new Property<bool> { Editable = false, Serialize = false };
            Property<bool> isAtEnd = new Property<bool> { Editable = false, Serialize = false };
            result.Add("IsAtStart", isAtStart);
            result.Add("IsAtEnd", isAtEnd);

            EntityMover mover = null;
            EntityRotator rotator = null;
            if (!main.EditorEnabled)
            {
                mover = new EntityMover(map.PhysicsEntity);
                mover.TargetPosition = transform.Position;
                main.Space.Add(mover);
                rotator = new EntityRotator(map.PhysicsEntity);
                rotator.TargetOrientation = transform.Quaternion;
                main.Space.Add(rotator);
            }

            Vector3 targetPosition = transform.Position;

            Property<float> speed = result.GetProperty<float>("Speed");
            Property<bool> stopOnEnd = result.GetProperty<bool>("StopOnEnd");

            Updater update = null;
            update = new Updater
            {
                delegate(float dt)
                {
                    if (!result.Active || limit1.Target == null || limit2.Target == null)
                        return;

                    float currentLocation = targetPosition.GetComponent(dir);

                    targetPosition = targetPosition.SetComponent(dir, currentLocation + dt * speed);

                    mover.TargetPosition = targetPosition;

                    float limit1Location = limit1.Target.Get<Transform>().Position.Value.GetComponent(dir);
                    float limit2Location = limit2.Target.Get<Transform>().Position.Value.GetComponent(dir);
                    float limitLocation = Math.Max(limit1Location, limit2Location);
                    if (currentLocation > limitLocation)
                    {
                        dir = dir.GetReverse();
                        if (limitLocation == limit1Location)
                        {
                            isAtStart.Value = true;
                            isAtEnd.Value = false;
                        }
                        else
                        {
                            isAtStart.Value = false;
                            isAtEnd.Value = true;
                        }
                        if (stopOnEnd)
                            update.Enabled.Value = false;
                    }
                }
            };
            update.Add(new TwoWayBinding<bool>(result.GetProperty<bool>("Enabled"), update.Enabled));

            result.Add(update);
        }
Beispiel #12
0
		private static void attachEditorComponents(Entity result, Main main)
		{
			Transform transform = result.Get<Transform>();

			Property<Entity.Handle> parentMap = result.GetOrMakeProperty<Entity.Handle>("Parent");

			EntityConnectable.AttachEditorComponents(result, parentMap);
			Model model = new Model();
			model.Filename.Value = "Models\\cone";
			model.Editable = false;
			model.Serialize = false;
			result.Add("DirectionModel", model);

			Property<Direction> dir = result.GetProperty<Direction>("Direction");
			Transform mapTransform = result.Get<Transform>("MapTransform");
			model.Add(new Binding<Matrix>(model.Transform, delegate()
			{
				Matrix m = Matrix.Identity;
				m.Translation = transform.Position;

				if (dir == Direction.None)
					m.Forward = m.Right = m.Up = Vector3.Zero;
				else
				{
					Vector3 normal = Vector3.TransformNormal(dir.Value.GetVector(), mapTransform.Matrix);

					m.Forward = -normal;
					if (normal.Equals(Vector3.Up))
						m.Right = Vector3.Left;
					else if (normal.Equals(Vector3.Down))
						m.Right = Vector3.Right;
					else
						m.Right = Vector3.Normalize(Vector3.Cross(normal, Vector3.Down));
					m.Up = Vector3.Cross(normal, m.Left);
				}
				return m;
			}, transform.Matrix, mapTransform.Matrix));
		}
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            Transform transform = result.Get<Transform>();
            EnemyBase enemy = result.GetOrCreate<EnemyBase>("Base");
            PlayerCylinderTrigger trigger = result.Get<PlayerCylinderTrigger>();

            PointLight light = result.GetOrCreate<PointLight>();
            light.Color.Value = new Vector3(1.3f, 0.5f, 0.5f);
            light.Attenuation.Value = 15.0f;
            light.Shadowed.Value = false;
            light.Serialize = false;

            ListProperty<Entity.Handle> dynamicMaps = result.GetListProperty<Entity.Handle>("DynamicMaps");
            Property<float> timeUntilRebuild = result.GetProperty<float>("TimeUntilRebuild");
            Property<float> timeUntilRebuildComplete = result.GetProperty<float>("TimeUntilRebuildComplete");
            Property<float> rebuildDelay = result.GetProperty<float>("RebuildDelay");
            Property<float> rebuildTime = result.GetProperty<float>("RebuildTime");

            const float rebuildTimeMultiplier = 0.03f;

            enemy.Add(new CommandBinding(enemy.Delete, result.Delete));
            enemy.Add(new Binding<Matrix>(enemy.Transform, transform.Matrix));
            light.Add(new Binding<Vector3>(light.Position, enemy.Position));

            trigger.Add(new Binding<Matrix>(trigger.Transform, () => Matrix.CreateTranslation(0.0f, 0.0f, enemy.Offset) * transform.Matrix, transform.Matrix, enemy.Offset));

            Action<Entity> fall = delegate(Entity player)
            {
                if (timeUntilRebuild.Value > 0 || timeUntilRebuildComplete.Value > 0)
                    return;

                if (!enemy.IsValid)
                {
                    result.Delete.Execute();
                    return;
                }

                // Disable the cell-emptied notification.
                // This way, we won't think that the base has been destroyed by the player.
                // We are not in fact dying, we're just destroying the base so we can fall over.
                enemy.EnableCellEmptyBinding = false;

                Map m = enemy.Map.Value.Target.Get<Map>();

                m.Empty(enemy.BaseBoxes.SelectMany(x => x.GetCoords()));

                m.Regenerate(delegate(List<DynamicMap> spawnedMaps)
                {
                    Vector3 playerPos = player.Get<Transform>().Position;
                    playerPos += player.Get<Player>().LinearVelocity.Value * 0.65f;
                    foreach (DynamicMap newMap in spawnedMaps)
                    {
                        Vector3 toPlayer = playerPos - newMap.PhysicsEntity.Position;
                        toPlayer.Normalize();
                        if (Math.Abs(toPlayer.Y) < 0.9f)
                        {
                            toPlayer *= 25.0f * newMap.PhysicsEntity.Mass;

                            Vector3 positionAtPlayerHeight = newMap.PhysicsEntity.Position;
                            Vector3 impulseAtBase = toPlayer * -0.75f;
                            impulseAtBase.Y = 0.0f;
                            positionAtPlayerHeight.Y = playerPos.Y;
                            newMap.PhysicsEntity.ApplyImpulse(ref positionAtPlayerHeight, ref impulseAtBase);

                            newMap.PhysicsEntity.ApplyLinearImpulse(ref toPlayer);
                        }
                        newMap.PhysicsEntity.Material.KineticFriction = 1.0f;
                        newMap.PhysicsEntity.Material.StaticFriction = 1.0f;
                        dynamicMaps.Add(newMap.Entity);
                    }
                });

                timeUntilRebuild.Value = rebuildDelay;
            };

            result.Add(new PostInitialization
            {
                delegate()
                {
                    foreach (Entity.Handle map in dynamicMaps)
                    {
                        if (map.Target != null)
                        {
                            BEPUphysics.Entities.MorphableEntity e = map.Target.Get<DynamicMap>().PhysicsEntity;
                            e.Material.KineticFriction = 1.0f;
                            e.Material.StaticFriction = 1.0f;
                        }
                    }
                }
            });

            result.Add(new CommandBinding<Entity>(trigger.PlayerEntered, fall));

            result.Add(new Updater
            {
                delegate(float dt)
                {
                    if (timeUntilRebuild > 0)
                    {
                        if (enemy.Map.Value.Target == null || !enemy.Map.Value.Target.Active)
                        {
                            result.Delete.Execute();
                            return;
                        }

                        float newValue = Math.Max(0.0f, timeUntilRebuild.Value - dt);
                        timeUntilRebuild.Value = newValue;
                        if (newValue == 0.0f)
                        {
                            // Rebuild
                            Map m = enemy.Map.Value.Target.Get<Map>();

                            int index = 0;

                            Vector3 baseCenter = Vector3.Zero;

                            EffectBlockFactory factory = Factory.Get<EffectBlockFactory>();

                            Entity targetMap = enemy.Map.Value.Target;

                            foreach (Map.Coordinate c in enemy.BaseBoxes.SelectMany(x => x.GetCoords()))
                            {
                                if (m[c].ID == 0)
                                {
                                    Entity block = factory.CreateAndBind(main);
                                    c.Data.ApplyToEffectBlock(block.Get<ModelInstance>());
                                    block.GetProperty<Vector3>("Offset").Value = m.GetRelativePosition(c);
                                    block.GetProperty<Vector3>("StartPosition").Value = m.GetAbsolutePosition(c) + new Vector3(0.25f, 0.5f, 0.25f) * index;
                                    block.GetProperty<Matrix>("StartOrientation").Value = Matrix.CreateRotationX(0.15f * index) * Matrix.CreateRotationY(0.15f * index);
                                    block.GetProperty<float>("TotalLifetime").Value = 0.05f + (index * rebuildTimeMultiplier * rebuildTime);
                                    factory.Setup(block, targetMap, c, c.Data.ID);
                                    main.Add(block);
                                    index++;
                                    baseCenter += new Vector3(c.X, c.Y, c.Z);
                                }
                            }

                            baseCenter /= index; // Get the average position of the base cells

                            foreach (Entity.Handle e in dynamicMaps)
                            {
                                Entity dynamicMap = e.Target;
                                Map dynamicMapComponent = dynamicMap != null && dynamicMap.Active ? dynamicMap.Get<Map>() : null;

                                if (dynamicMap == null || !dynamicMap.Active)
                                    continue;

                                Matrix orientation = dynamicMapComponent.Transform.Value;
                                orientation.Translation = Vector3.Zero;

                                List<Map.Coordinate> coords = new List<Map.Coordinate>();

                                foreach (Map.Coordinate c in dynamicMapComponent.Chunks.SelectMany(x => x.Boxes).SelectMany(x => x.GetCoords()))
                                {
                                    if (m[c].ID == 0)
                                        coords.Add(c);
                                }

                                foreach (Map.Coordinate c in coords.OrderBy(x => (new Vector3(x.X, x.Y, x.Z) - baseCenter).LengthSquared()))
                                {
                                    Entity block = factory.CreateAndBind(main);
                                    c.Data.ApplyToEffectBlock(block.Get<ModelInstance>());
                                    block.GetProperty<Vector3>("Offset").Value = m.GetRelativePosition(c);
                                    block.GetProperty<bool>("Scale").Value = dynamicMapComponent == null;
                                    if (dynamicMapComponent != null && dynamicMapComponent[c].ID == c.Data.ID)
                                    {
                                        block.GetProperty<Vector3>("StartPosition").Value = dynamicMapComponent.GetAbsolutePosition(c);
                                        block.GetProperty<Matrix>("StartOrientation").Value = orientation;
                                    }
                                    else
                                    {
                                        block.GetProperty<Vector3>("StartPosition").Value = m.GetAbsolutePosition(c) + new Vector3(0.25f, 0.5f, 0.25f) * index;
                                        block.GetProperty<Matrix>("StartOrientation").Value = Matrix.CreateRotationX(0.15f * index) * Matrix.CreateRotationY(0.15f * index);
                                    }
                                    block.GetProperty<float>("TotalLifetime").Value = 0.05f + (index * rebuildTimeMultiplier * rebuildTime);
                                    factory.Setup(block, targetMap, c, c.Data.ID);
                                    main.Add(block);
                                    index++;
                                }
                                dynamicMap.Delete.Execute();
                            }
                            timeUntilRebuildComplete.Value = 0.05f + (index * rebuildTimeMultiplier * rebuildTime);
                            dynamicMaps.Clear();
                        }
                    }
                    else if (timeUntilRebuildComplete > 0)
                    {
                        // Rebuilding
                        float newValue = Math.Max(0.0f, timeUntilRebuildComplete.Value - dt);
                        timeUntilRebuildComplete.Value = newValue;
                        if (newValue == 0.0f)
                        {
                            // Done rebuilding
                            if (!enemy.IsValid)
                                result.Delete.Execute();
                            else
                            {
                                enemy.EnableCellEmptyBinding = !main.EditorEnabled;
                                if (trigger.IsTriggered)
                                    fall(trigger.Player.Value.Target);
                            }
                        }
                    }
                }
            });

            this.SetMain(result, main);
        }
Beispiel #14
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            this.SetMain(result, main);
            Transform transform = result.Get<Transform>();
            PlayerTrigger trigger = result.Get<PlayerTrigger>();
            Property<string> nextMap = result.GetProperty<string>("NextMap");
            Property<string> startSpawnPoint = result.GetProperty<string>("SpawnPoint");

            trigger.Add(new TwoWayBinding<Vector3>(transform.Position, trigger.Position));
            trigger.Add(new CommandBinding<Entity>(trigger.PlayerEntered, delegate(Entity player)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<Entity>));

                Container notification = new Container();
                notification.Tint.Value = Microsoft.Xna.Framework.Color.Black;
                notification.Opacity.Value = 0.5f;
                TextElement notificationText = new TextElement();
                notificationText.Name.Value = "Text";
                notificationText.FontFile.Value = "Font";
                notificationText.Text.Value = "Loading...";
                notification.Children.Add(notificationText);
                ((GameMain)main).UI.Root.GetChildByName("Notifications").Children.Add(notification);

                Stream stream = new MemoryStream();
                main.AddComponent(new Animation
                (
                    new Animation.Delay(0.01f),
                    new Animation.Execute(delegate()
                    {
                        // We are exiting the map; just save the state of the map without the player.
                        ListProperty<PlayerFactory.RespawnLocation> respawnLocations = Factory.Get<PlayerDataFactory>().Instance(main).GetOrMakeListProperty<PlayerFactory.RespawnLocation>("RespawnLocations");
                        respawnLocations.Clear();

                        List<Entity> persistentEntities = main.Entities.Where((Func<Entity, bool>)MapExitFactory.isPersistent).ToList();

                        serializer.Serialize(stream, persistentEntities);

                        foreach (Entity e in persistentEntities)
                            e.Delete.Execute();

                        ((GameMain)main).StartSpawnPoint = startSpawnPoint;
                    }),
                    new Animation.Execute(((GameMain)main).SaveCurrentMap),
                    new Animation.Set<string>(main.MapFile, nextMap),
                    new Animation.Execute(delegate()
                    {
                        notification.Visible.Value = false;
                        stream.Seek(0, SeekOrigin.Begin);
                        List<Entity> entities = (List<Entity>)serializer.Deserialize(stream);
                        foreach (Entity entity in entities)
                        {
                            Factory factory = Factory.Get(entity.Type);
                            factory.Bind(entity, main);
                            main.Add(entity);
                        }
                        stream.Dispose();
                    }),
                    new Animation.Delay(1.5f),
                    new Animation.Set<string>(notificationText.Text, "Saving..."),
                    new Animation.Set<bool>(notification.Visible, true),
                    new Animation.Delay(0.01f),
                    new Animation.Execute(((GameMain)main).Save),
                    new Animation.Set<string>(notificationText.Text, "Saved"),
                    new Animation.Parallel
                    (
                        new Animation.FloatMoveTo(notification.Opacity, 0.0f, 1.0f),
                        new Animation.FloatMoveTo(notificationText.Opacity, 0.0f, 1.0f)
                    ),
                    new Animation.Execute(notification.Delete)
                ));
            }));
        }
Beispiel #15
0
        private static bool isPersistent(Entity entity)
        {
            if (MapExitFactory.persistentTypes.Contains(entity.Type))
                return true;

            if (MapExitFactory.attachedTypes.Contains(entity.Type))
            {
                Property<bool> attached = entity.GetProperty<bool>("Attached");
                if (attached != null && attached)
                    return true;
            }
            return false;
        }
Beispiel #16
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            this.SetMain(result, main);
            Editor editor = result.Get<Editor>();
            EditorUI ui = result.Get<EditorUI>();
            Model model = result.Get<Model>("Model");
            FPSInput input = result.Get<FPSInput>("Input");
            UIRenderer uiRenderer = result.Get<UIRenderer>();

            ModelAlpha radiusVisual = new ModelAlpha();
            radiusVisual.Filename.Value = "Models\\alpha-sphere";
            radiusVisual.Color.Value = new Vector3(1.0f);
            radiusVisual.Alpha.Value = 0.1f;
            radiusVisual.Editable = false;
            radiusVisual.Serialize = false;
            radiusVisual.DrawOrder.Value = 11; // In front of water
            radiusVisual.DisableCulling.Value = true;
            result.Add(radiusVisual);
            radiusVisual.Add(new Binding<Matrix, Vector3>(radiusVisual.Transform, x => Matrix.CreateTranslation(x), editor.Position));
            radiusVisual.Add(new Binding<Vector3, int>(radiusVisual.Scale, x => new Vector3(x), editor.BrushSize));
            radiusVisual.Add(new Binding<bool>(radiusVisual.Enabled, () => editor.BrushSize > 1 && editor.MapEditMode, editor.BrushSize, editor.MapEditMode));
            radiusVisual.CullBoundingBox.Value = false;

            ModelAlpha selection = new ModelAlpha();
            selection.Filename.Value = "Models\\alpha-box";
            selection.Color.Value = new Vector3(1.0f, 0.7f, 0.4f);
            selection.Alpha.Value = 0.25f;
            selection.Editable = false;
            selection.Serialize = false;
            selection.DrawOrder.Value = 12; // In front of water and radius visualizer
            selection.DisableCulling.Value = true;
            result.Add(selection);
            selection.Add(new Binding<bool>(selection.Enabled, editor.VoxelSelectionActive));
            selection.Add(new Binding<Matrix>(selection.Transform, delegate()
            {
                const float padding = 0.1f;
                Map map = editor.SelectedEntities[0].Get<Map>();
                Vector3 start = map.GetRelativePosition(editor.VoxelSelectionStart) - new Vector3(0.5f), end = map.GetRelativePosition(editor.VoxelSelectionEnd) - new Vector3(0.5f);
                return Matrix.CreateScale((end - start) + new Vector3(padding)) * Matrix.CreateTranslation((start + end) * 0.5f) * map.Transform;
            }, () => selection.Enabled, editor.VoxelSelectionStart, editor.VoxelSelectionEnd));
            selection.CullBoundingBox.Value = false;

            Action<string, PCInput.Chord, Func<bool>, Command> addCommand = delegate(string description, PCInput.Chord chord, Func<bool> enabled, Command action)
            {
                ui.PopupCommands.Add(new EditorUI.PopupCommand { Description = description, Chord = chord, Enabled = enabled, Action = action });

                if (chord.Modifier != Keys.None)
                    input.Add(new CommandBinding(input.GetChord(chord), () => enabled() && !ui.StringPropertyLocked, action));
                else
                    input.Add(new CommandBinding(input.GetKeyDown(chord.Key), () => enabled() && !ui.StringPropertyLocked, action));

                ui.Add(new CommandBinding(action, delegate()
                {
                    Container container = new Container();
                    container.Tint.Value = Microsoft.Xna.Framework.Color.Black;
                    container.Opacity.Value = 0.2f;
                    container.AnchorPoint.Value = new Vector2(1.0f, 0.0f);
                    container.Add(new Binding<Vector2, Point>(container.Position, x => new Vector2(x.X - 10.0f, 10.0f), main.ScreenSize));
                    TextElement display = new TextElement();
                    display.FontFile.Value = "Font";
                    display.Text.Value = description;
                    container.Children.Add(display);
                    uiRenderer.Root.Children.Add(container);
                    main.AddComponent(new Animation
                    (
                        new Animation.Parallel
                        (
                            new Animation.FloatMoveTo(container.Opacity, 0.0f, 1.0f),
                            new Animation.FloatMoveTo(display.Opacity, 0.0f, 1.0f)
                        ),
                        new Animation.Execute(delegate() { uiRenderer.Root.Children.Remove(container); })
                    ));
                }));
            };

            foreach (string key in Factory.factories.Keys)
            {
                string entityType = key;
                ui.PopupCommands.Add(new EditorUI.PopupCommand
                {
                    Description = "Add " + entityType,
                    Enabled = () => editor.SelectedEntities.Count == 0 && !editor.MapEditMode,
                    Action = new Command { Action = () => editor.Spawn.Execute(entityType) },
                });
            }

            Scroller scroller = (Scroller)uiRenderer.Root.GetChildByName("Scroller");

            Container popup = (Container)uiRenderer.Root.GetChildByName("Popup");
            ListContainer popupList = (ListContainer)popup.GetChildByName("PopupList");

            input.Add(new CommandBinding(input.GetKeyUp(Keys.Space), () => !editor.MapEditMode && !ui.StringPropertyLocked && !editor.MovementEnabled, delegate()
            {
                Vector2 pos = input.Mouse;
                pos.X = Math.Min(main.ScreenSize.Value.X - popup.Size.Value.X, pos.X);
                pos.Y = Math.Min(main.ScreenSize.Value.Y - popup.Size.Value.Y, pos.Y);
                popup.Position.Value = pos;
                ui.PopupVisible.Value = true;
            }));

            input.Add(new CommandBinding(input.GetKeyUp(Keys.Escape), () => ui.PopupVisible, delegate()
            {
                if (ui.PopupSearchText.Value == "_")
                    ui.PopupVisible.Value = false;
                else
                    ui.ClearSelectedStringProperty();
            }));

            input.Add(new CommandBinding(input.RightMouseButtonUp, () => ui.PopupVisible, delegate()
            {
                ui.PopupVisible.Value = false;
            }));

            uiRenderer.Add(new Binding<bool>(popup.Visible, ui.PopupVisible));
            uiRenderer.Add(new Binding<string>(((TextElement)popup.GetChildByName("PopupSearch")).Text, ui.PopupSearchText));
            uiRenderer.Add(new ListBinding<UIComponent>(popupList.Children, ui.PopupElements));

            AudioListener audioListener = result.Get<AudioListener>();
            audioListener.Add(new Binding<Vector3>(audioListener.Position, main.Camera.Position));
            audioListener.Add(new Binding<Vector3>(audioListener.Forward, main.Camera.Forward));

            model.Add(new Binding<bool>(model.Enabled, editor.MapEditMode));
            model.Add(new Binding<Matrix>(model.Transform, () => editor.Orientation.Value * Matrix.CreateTranslation(editor.Position), editor.Position, editor.Orientation));

            editor.Add(new TwoWayBinding<string>(main.MapFile, editor.MapFile));

            result.Add(new TwoWayBinding<string>(((GameMain)main).StartSpawnPoint, result.GetProperty<string>("StartSpawnPoint")));

            uiRenderer.Add(new ListBinding<UIComponent>(uiRenderer.Root.GetChildByName("PropertyList").Children, ui.UIElements));
            ui.Add(new ListBinding<Entity>(ui.SelectedEntities, editor.SelectedEntities));
            ui.Add(new Binding<bool>(ui.MapEditMode, editor.MapEditMode));
            ui.Add(new Binding<bool>(ui.EnablePrecision, x => !x, input.GetKey(Keys.LeftShift)));
            editor.Add(new Binding<bool>(editor.MovementEnabled, () => !ui.StringPropertyLocked && (input.MiddleMouseButton || editor.MapEditMode), ui.StringPropertyLocked, input.MiddleMouseButton, editor.MapEditMode));
            ui.Add(new TwoWayBinding<bool>(editor.NeedsSave, ui.NeedsSave));

            editor.Add(new Binding<Vector2>(editor.Movement, input.Movement));
            editor.Add(new Binding<bool>(editor.Up, input.GetKey(Keys.Space)));
            editor.Add(new Binding<bool>(editor.Down, input.GetKey(Keys.LeftControl)));
            editor.Add(new Binding<bool>(editor.Empty, input.RightMouseButton));
            editor.Add(new Binding<bool>(editor.SpeedMode, input.GetKey(Keys.LeftShift)));
            editor.Add(new Binding<bool>(editor.Extend, input.GetKey(Keys.F)));
            editor.Add(new Binding<bool>(editor.Fill, input.LeftMouseButton));
            editor.Add(new Binding<bool>(editor.EditSelection, () => input.MiddleMouseButton && editor.MapEditMode, input.MiddleMouseButton, editor.MapEditMode));

            addCommand("Delete", new PCInput.Chord { Key = Keys.X }, () => !editor.MapEditMode && editor.TransformMode.Value == Editor.TransformModes.None && editor.SelectedEntities.Count > 0, editor.DeleteSelected);
            addCommand("Duplicate", new PCInput.Chord { Modifier = Keys.LeftShift, Key = Keys.D }, () => !editor.MovementEnabled && editor.SelectedEntities.Count > 0, editor.Duplicate);

            // Start playing
            addCommand("Run", new PCInput.Chord { Modifier = Keys.LeftControl, Key = Keys.R }, () => !editor.MovementEnabled, new Command
            {
                Action = delegate()
                {
                    if (editor.NeedsSave)
                        editor.Save.Execute();
                    main.EditorEnabled.Value = false;
                    IO.MapLoader.Load(main, null, main.MapFile);
                }
            });

            result.Add(new CommandBinding(main.MapLoaded, delegate()
            {
                editor.Position.Value = Vector3.Zero;
                editor.NeedsSave.Value = false;
            }));

            addCommand("Quit", new PCInput.Chord { Modifier = Keys.LeftControl, Key = Keys.Q }, () => !editor.MovementEnabled, new Command
            {
                Action = delegate()
                {
                    throw new GameMain.ExitException();
                }
            });

            Property<bool> analyticsEnable = new Property<bool>();
            ListProperty<SessionEntry> analyticsSessions = new ListProperty<SessionEntry>();
            ListProperty<SessionEntry> analyticsActiveSessions = new ListProperty<SessionEntry>();
            ListProperty<EventEntry> analyticsEvents = new ListProperty<EventEntry>();
            ListProperty<EventEntry> analyticsActiveEvents = new ListProperty<EventEntry>();
            ListProperty<PropertyEntry> analyticsProperties = new ListProperty<PropertyEntry>();
            ListProperty<PropertyEntry> analyticsActiveProperties = new ListProperty<PropertyEntry>();
            Dictionary<Session, ModelInstance> sessionPositionModels = new Dictionary<Session, ModelInstance>();
            Dictionary<Session.EventList, List<ModelInstance>> eventPositionModels = new Dictionary<Session.EventList, List<ModelInstance>>();
            Property<bool> analyticsPlaying = new Property<bool>();
            Property<float> playbackSpeed = new Property<float> { Value = 1.0f };
            Property<float> playbackLocation = new Property<float>();

            const float timelineHeight = 32.0f;

            Scroller timelineScroller = new Scroller();
            timelineScroller.ScrollAmount.Value = 60.0f;
            timelineScroller.EnableScissor.Value = false;
            timelineScroller.DefaultScrollHorizontal.Value = true;
            timelineScroller.AnchorPoint.Value = new Vector2(0, 1);
            timelineScroller.ResizeVertical.Value = true;
            timelineScroller.Add(new Binding<Vector2, Point>(timelineScroller.Position, x => new Vector2(0, x.Y), main.ScreenSize));
            timelineScroller.Add(new Binding<Vector2, Point>(timelineScroller.Size, x => new Vector2(x.X, timelineHeight), main.ScreenSize));
            timelineScroller.Add(new Binding<bool>(timelineScroller.Visible, analyticsEnable));
            timelineScroller.Add(new Binding<bool>(timelineScroller.EnableScroll, x => !x, input.GetKey(Keys.LeftAlt)));
            uiRenderer.Root.Children.Add(timelineScroller);

            scroller.Add(new Binding<Vector2>(scroller.Size, () => new Vector2(scroller.Size.Value.X, main.ScreenSize.Value.Y - 20 - timelineScroller.ScaledSize.Value.Y), main.ScreenSize, timelineScroller.ScaledSize));

            ListContainer timelines = new ListContainer();
            timelines.Alignment.Value = ListContainer.ListAlignment.Min;
            timelines.Orientation.Value = ListContainer.ListOrientation.Vertical;
            timelines.Reversed.Value = true;
            timelineScroller.Children.Add(timelines);

            Container timeline = new Container();
            timeline.Size.Value = new Vector2(0, timelineHeight);
            timeline.Tint.Value = Microsoft.Xna.Framework.Color.Black;
            timeline.ResizeHorizontal.Value = false;
            timeline.ResizeVertical.Value = false;
            timelines.Children.Add(timeline);

            ui.PopupCommands.Add(new EditorUI.PopupCommand
            {
                Description = "Load analytics data",
                Enabled = () => editor.SelectedEntities.Count == 0 && !editor.MapEditMode && !analyticsEnable,
                Action = new Command
                {
                    Action = delegate()
                    {
                        if (main.MapFile.Value != null)
                        {
                            List<Session> sessions = ((GameMain)main).LoadAnalytics(main.MapFile);
                            if (sessions.Count > 0)
                            {
                                analyticsEnable.Value = true;
                                Dictionary<string, bool> distinctEventNames = new Dictionary<string, bool>();
                                Dictionary<string, bool> distinctPropertyNames = new Dictionary<string, bool>();
                                foreach (Session s in sessions)
                                {
                                    foreach (Session.EventList el in s.Events)
                                    {
                                        distinctEventNames[el.Name] = true;
                                        s.TotalTime = Math.Max(s.TotalTime, el.Events[el.Events.Count - 1].Time);
                                    }
                                    foreach (Session.ContinuousProperty p in s.ContinuousProperties)
                                    {
                                        if (p.Independent)
                                            distinctPropertyNames[p.Name] = true;
                                    }
                                    analyticsSessions.Add(new SessionEntry { Session = s });
                                }
                                analyticsEvents.Add(distinctEventNames.Keys.Select(x => new EventEntry { Name = x }));
                                analyticsProperties.Add(distinctPropertyNames.Keys.Select(x => new PropertyEntry { Name = x }));
                                timeline.Size.Value = new Vector2(analyticsSessions.Max(x => x.Session.TotalTime), timelineScroller.Size.Value.Y);
                                timelines.Scale.Value = new Vector2(timelineScroller.Size.Value.X / timeline.Size.Value.X, 1.0f);
                            }
                        }
                    }
                },
            });

            ListContainer sessionsSidebar = new ListContainer();
            sessionsSidebar.AnchorPoint.Value = new Vector2(1, 1);
            sessionsSidebar.Add(new Binding<Vector2>(sessionsSidebar.Position, () => new Vector2(main.ScreenSize.Value.X - 10, main.ScreenSize.Value.Y - timelineScroller.ScaledSize.Value.Y - 10), main.ScreenSize, timelineScroller.ScaledSize));
            sessionsSidebar.Add(new Binding<bool>(sessionsSidebar.Visible, analyticsEnable));
            sessionsSidebar.Alignment.Value = ListContainer.ListAlignment.Max;
            sessionsSidebar.Reversed.Value = true;
            uiRenderer.Root.Children.Add(sessionsSidebar);

            Func<string, ListContainer> createCheckboxListItem = delegate(string text)
            {
                ListContainer layout = new ListContainer();
                layout.Orientation.Value = ListContainer.ListOrientation.Horizontal;

                TextElement label = new TextElement();
                label.FontFile.Value = "Font";
                label.Text.Value = text;
                label.Name.Value = "Label";
                layout.Children.Add(label);

                Container checkboxContainer = new Container();
                checkboxContainer.PaddingBottom.Value = checkboxContainer.PaddingLeft.Value = checkboxContainer.PaddingRight.Value = checkboxContainer.PaddingTop.Value = 1.0f;
                layout.Children.Add(checkboxContainer);

                Container checkbox = new Container();
                checkbox.Name.Value = "Checkbox";
                checkbox.ResizeHorizontal.Value = checkbox.ResizeVertical.Value = false;
                checkbox.Size.Value = new Vector2(16.0f, 16.0f);
                checkboxContainer.Children.Add(checkbox);
                return layout;
            };

            Container sessionsContainer = new Container();
            sessionsContainer.Tint.Value = Microsoft.Xna.Framework.Color.Black;
            sessionsContainer.Opacity.Value = 0.5f;
            sessionsContainer.AnchorPoint.Value = new Vector2(1, 1);
            sessionsSidebar.Children.Add(sessionsContainer);

            Scroller sessionsScroller = new Scroller();
            sessionsScroller.ResizeHorizontal.Value = true;
            sessionsScroller.ResizeVertical.Value = true;
            sessionsScroller.MaxVerticalSize.Value = 256;
            sessionsContainer.Children.Add(sessionsScroller);

            ListContainer sessionList = new ListContainer();
            sessionList.Orientation.Value = ListContainer.ListOrientation.Vertical;
            sessionList.Alignment.Value = ListContainer.ListAlignment.Max;
            sessionsScroller.Children.Add(sessionList);

            Property<bool> allSessions = new Property<bool>();

            sessionList.Add(new ListBinding<UIComponent, SessionEntry>(sessionList.Children, analyticsSessions, delegate(SessionEntry entry)
            {
                ListContainer item = createCheckboxListItem(entry.Session.Date.ToShortDateString() + " (" + new TimeSpan(0, 0, (int)entry.Session.TotalTime).ToString() + ")");

                Container checkbox = (Container)item.GetChildByName("Checkbox");
                checkbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(checkbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, entry.Active));

                item.Add(new CommandBinding<Point>(item.MouseLeftDown, delegate(Point p)
                {
                    if (entry.Active)
                    {
                        allSessions.Value = false;
                        analyticsActiveSessions.Remove(entry);
                    }
                    else
                        analyticsActiveSessions.Add(entry);
                }));

                return new[] { item };
            }));

            ListContainer allSessionsButton = createCheckboxListItem("[All]");
            allSessionsButton.Add(new CommandBinding<Point>(allSessionsButton.MouseLeftDown, delegate(Point p)
            {
                if (allSessions)
                {
                    allSessions.Value = false;
                    foreach (SessionEntry s in analyticsActiveSessions.ToList())
                        analyticsActiveSessions.Remove(s);
                }
                else
                {
                    allSessions.Value = true;
                    foreach (SessionEntry s in analyticsSessions)
                    {
                        if (!s.Active)
                            analyticsActiveSessions.Add(s);
                    }
                }
            }));

            Container allSessionsCheckbox = (Container)allSessionsButton.GetChildByName("Checkbox");
            allSessionsCheckbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(allSessionsCheckbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, allSessions));
            sessionList.Children.Add(allSessionsButton);

            Container eventsContainer = new Container();
            eventsContainer.Tint.Value = Microsoft.Xna.Framework.Color.Black;
            eventsContainer.Opacity.Value = 0.5f;
            eventsContainer.AnchorPoint.Value = new Vector2(1, 1);
            sessionsSidebar.Children.Add(eventsContainer);

            Scroller eventsScroller = new Scroller();
            eventsScroller.ResizeHorizontal.Value = true;
            eventsScroller.ResizeVertical.Value = true;
            eventsScroller.MaxVerticalSize.Value = 256;
            eventsContainer.Children.Add(eventsScroller);

            ListContainer eventList = new ListContainer();
            eventList.Orientation.Value = ListContainer.ListOrientation.Vertical;
            eventList.Alignment.Value = ListContainer.ListAlignment.Max;
            eventsScroller.Children.Add(eventList);

            Property<bool> allEvents = new Property<bool>();

            eventList.Add(new ListBinding<UIComponent, EventEntry>(eventList.Children, analyticsEvents, delegate(EventEntry e)
            {
                ListContainer item = createCheckboxListItem(e.Name);

                Container checkbox = (Container)item.GetChildByName("Checkbox");
                checkbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(checkbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, e.Active));

                TextElement label = (TextElement)item.GetChildByName("Label");
                label.Tint.Value = new Microsoft.Xna.Framework.Color(this.colorHash(e.Name));

                item.Add(new CommandBinding<Point>(item.MouseLeftDown, delegate(Point p)
                {
                    if (e.Active)
                    {
                        allEvents.Value = false;
                        analyticsActiveEvents.Remove(e);
                    }
                    else
                        analyticsActiveEvents.Add(e);
                }));

                return new[] { item };
            }));

            ListContainer allEventsButton = createCheckboxListItem("[All]");
            allEventsButton.Add(new CommandBinding<Point>(allEventsButton.MouseLeftDown, delegate(Point p)
            {
                if (allEvents)
                {
                    allEvents.Value = false;
                    foreach (EventEntry e in analyticsActiveEvents.ToList())
                        analyticsActiveEvents.Remove(e);
                }
                else
                {
                    allEvents.Value = true;
                    foreach (EventEntry e in analyticsEvents)
                    {
                        if (!e.Active)
                            analyticsActiveEvents.Add(e);
                    }
                }
            }));
            Container allEventsCheckbox = (Container)allEventsButton.GetChildByName("Checkbox");
            allEventsCheckbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(allEventsCheckbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, allEvents));
            eventList.Children.Add(allEventsButton);

            Container propertiesContainer = new Container();
            propertiesContainer.Tint.Value = Microsoft.Xna.Framework.Color.Black;
            propertiesContainer.Opacity.Value = 0.5f;
            propertiesContainer.AnchorPoint.Value = new Vector2(1, 1);
            sessionsSidebar.Children.Add(propertiesContainer);

            Scroller propertiesScroller = new Scroller();
            propertiesScroller.ResizeHorizontal.Value = true;
            propertiesScroller.ResizeVertical.Value = true;
            propertiesScroller.MaxVerticalSize.Value = 256;
            propertiesContainer.Children.Add(propertiesScroller);

            ListContainer propertiesList = new ListContainer();
            propertiesList.Orientation.Value = ListContainer.ListOrientation.Vertical;
            propertiesList.Alignment.Value = ListContainer.ListAlignment.Max;
            propertiesScroller.Children.Add(propertiesList);

            Property<bool> allProperties = new Property<bool>();

            propertiesList.Add(new ListBinding<UIComponent, PropertyEntry>(propertiesList.Children, analyticsProperties, delegate(PropertyEntry e)
            {
                ListContainer item = createCheckboxListItem(e.Name);

                Container checkbox = (Container)item.GetChildByName("Checkbox");
                checkbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(checkbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, e.Active));

                TextElement label = (TextElement)item.GetChildByName("Label");
                label.Tint.Value = new Microsoft.Xna.Framework.Color(this.colorHash(e.Name));

                item.Add(new CommandBinding<Point>(item.MouseLeftDown, delegate(Point p)
                {
                    if (e.Active)
                    {
                        allProperties.Value = false;
                        analyticsActiveProperties.Remove(e);
                    }
                    else
                        analyticsActiveProperties.Add(e);
                }));

                return new[] { item };
            }));

            ListContainer allPropertiesButton = createCheckboxListItem("[All]");
            allPropertiesButton.Add(new CommandBinding<Point>(allPropertiesButton.MouseLeftDown, delegate(Point p)
            {
                if (allProperties)
                {
                    allProperties.Value = false;
                    foreach (PropertyEntry e in analyticsActiveProperties.ToList())
                        analyticsActiveProperties.Remove(e);
                }
                else
                {
                    allProperties.Value = true;
                    foreach (PropertyEntry e in analyticsProperties)
                    {
                        if (!e.Active)
                            analyticsActiveProperties.Add(e);
                    }
                }
            }));
            Container allPropertiesCheckbox = (Container)allPropertiesButton.GetChildByName("Checkbox");
            allPropertiesCheckbox.Add(new Binding<Microsoft.Xna.Framework.Color, bool>(allPropertiesCheckbox.Tint, x => x ? Microsoft.Xna.Framework.Color.White : Microsoft.Xna.Framework.Color.Black, allProperties));
            propertiesList.Children.Add(allPropertiesButton);

            Func<Session.EventList, LineDrawer2D> createEventLines = delegate(Session.EventList el)
            {
                LineDrawer2D line = new LineDrawer2D();
                line.Color.Value = this.colorHash(el.Name);
                line.UserData.Value = el;

                foreach (Session.Event e in el.Events)
                {
                    line.Lines.Add(new LineDrawer2D.Line
                    {
                        A = new Microsoft.Xna.Framework.Graphics.VertexPositionColor(new Vector3(e.Time, 0.0f, 0.0f), Microsoft.Xna.Framework.Color.White),
                        B = new Microsoft.Xna.Framework.Graphics.VertexPositionColor(new Vector3(e.Time, timeline.Size.Value.Y, 0.0f), Microsoft.Xna.Framework.Color.White),
                    });
                }
                return line;
            };

            analyticsActiveEvents.ItemAdded += delegate(int index, EventEntry ee)
            {
                ee.Active.Value = true;
                foreach (SessionEntry s in analyticsActiveSessions)
                {
                    Session.PositionProperty positionProperty = s.Session.PositionProperties[0];
                    foreach (Session.EventList el in s.Session.Events)
                    {
                        if (el.Name == ee.Name)
                        {
                            List<ModelInstance> models = new List<ModelInstance>();
                            Vector4 color = this.colorHash(el.Name);
                            int hash = (int)(new Color(color).PackedValue);
                            foreach (Session.Event e in el.Events)
                            {
                                ModelInstance i = new ModelInstance();
                                i.Setup("Models\\position-model", hash);
                                if (i.IsFirstInstance)
                                    i.Model.Color.Value = new Vector3(color.X, color.Y, color.Z);
                                i.Scale.Value = new Vector3(0.25f);
                                i.Transform.Value = Matrix.CreateTranslation(positionProperty[e.Time]);
                                models.Add(i);
                                result.Add(i);
                            }
                            eventPositionModels[el] = models;
                        }
                    }

                    timeline.Children.Add(s.Session.Events.Where(x => x.Name == ee.Name).Select(createEventLines));
                }
            };

            analyticsActiveEvents.ItemRemoved += delegate(int index, EventEntry e)
            {
                e.Active.Value = false;
                foreach (KeyValuePair<Session.EventList, List<ModelInstance>> pair in eventPositionModels.ToList())
                {
                    if (pair.Key.Name == e.Name)
                    {
                        foreach (ModelInstance instance in pair.Value)
                            instance.Delete.Execute();
                        eventPositionModels.Remove(pair.Key);
                    }
                }
                timeline.Children.Remove(timeline.Children.Where(x => x.UserData.Value != null && ((Session.EventList)x.UserData.Value).Name == e.Name).ToList());
            };

            analyticsActiveProperties.ItemAdded += delegate(int index, PropertyEntry e)
            {
                e.Active.Value = true;
            };

            analyticsActiveProperties.ItemRemoved += delegate(int index, PropertyEntry e)
            {
                e.Active.Value = false;
            };

            ListContainer propertyTimelines = new ListContainer();
            propertyTimelines.Alignment.Value = ListContainer.ListAlignment.Min;
            propertyTimelines.Orientation.Value = ListContainer.ListOrientation.Vertical;
            timelines.Children.Add(propertyTimelines);

            Action<LineDrawer2D> refreshPropertyGraph = delegate(LineDrawer2D lines)
            {
                string propertyName = ((PropertyEntry)lines.UserData.Value).Name;
                lines.Lines.Clear();
                float time = 0.0f, lastTime = 0.0f;
                float lastValue = 0.0f;
                bool firstLine = true;
                float max = float.MinValue, min = float.MaxValue;
                while (true)
                {
                    bool stop = true;

                    // Calculate average
                    int count = 0;
                    float sum = 0.0f;
                    foreach (SessionEntry s in analyticsActiveSessions)
                    {
                        if (time < s.Session.TotalTime)
                        {
                            Session.ContinuousProperty prop = s.Session.GetContinuousProperty(propertyName);
                            if (prop != null)
                            {
                                stop = false;
                                sum += prop[time];
                                count++;
                            }
                        }
                    }

                    if (stop)
                        break;
                    else
                    {
                        float value = sum / (float)count;
                        if (firstLine)
                            firstLine = false;
                        else
                        {
                            lines.Lines.Add(new LineDrawer2D.Line
                            {
                                A = new Microsoft.Xna.Framework.Graphics.VertexPositionColor
                                {
                                    Color = Microsoft.Xna.Framework.Color.White,
                                    Position = new Vector3(lastTime, lastValue, 0.0f),
                                },
                                B = new Microsoft.Xna.Framework.Graphics.VertexPositionColor
                                {
                                    Color = Microsoft.Xna.Framework.Color.White,
                                    Position = new Vector3(time, value, 0.0f),
                                },
                            });
                        }
                        min = Math.Min(min, value);
                        max = Math.Max(max, value);
                        lastValue = value;
                        lastTime = time;
                        time += Session.Recorder.Interval;
                    }

                    if (min < max)
                    {
                        float scale = -timelineHeight / (max - min);
                        lines.Scale.Value = new Vector2(1, scale);
                        lines.Position.Value = new Vector2(0, max * -scale);
                    }
                    else
                    {
                        lines.AnchorPoint.Value = Vector2.Zero;
                        if (min <= 0.0f)
                            lines.Position.Value = new Vector2(0, timelineHeight);
                        else
                            lines.Position.Value = new Vector2(0, timelineHeight * 0.5f);
                    }
                }
            };

            Action refreshPropertyGraphs = delegate()
            {
                foreach (LineDrawer2D line in propertyTimelines.Children.Select(x => x.Children.First()))
                    refreshPropertyGraph(line);
            };

            propertyTimelines.Add(new ListBinding<UIComponent, PropertyEntry>(propertyTimelines.Children, analyticsActiveProperties, delegate(PropertyEntry e)
            {
                Container propertyTimeline = new Container();
                propertyTimeline.Add(new Binding<Vector2>(propertyTimeline.Size, timeline.Size));
                propertyTimeline.Tint.Value = Microsoft.Xna.Framework.Color.Black;
                propertyTimeline.Opacity.Value = 0.5f;
                propertyTimeline.ResizeHorizontal.Value = false;
                propertyTimeline.ResizeVertical.Value = false;

                LineDrawer2D line = new LineDrawer2D();
                line.Color.Value = this.colorHash(e.Name);
                line.UserData.Value = e;
                propertyTimeline.Children.Add(line);

                refreshPropertyGraph(line);

                return new[] { propertyTimeline };
            }));

            analyticsActiveSessions.ItemAdded += delegate(int index, SessionEntry s)
            {
                Session.PositionProperty positionProperty = s.Session.PositionProperties[0];
                foreach (Session.EventList el in s.Session.Events)
                {
                    if (analyticsActiveEvents.FirstOrDefault(x => x.Name == el.Name) != null)
                    {
                        List<ModelInstance> models = new List<ModelInstance>();
                        Vector4 color = this.colorHash(el.Name);
                        int hash = (int)(new Color(color).PackedValue);
                        foreach (Session.Event e in el.Events)
                        {
                            ModelInstance i = new ModelInstance();
                            i.Setup("Models\\position-model", hash);
                            if (i.IsFirstInstance)
                                i.Model.Color.Value = new Vector3(color.X, color.Y, color.Z);
                            i.Scale.Value = new Vector3(0.25f);
                            i.Transform.Value = Matrix.CreateTranslation(positionProperty[e.Time]);
                            result.Add(i);
                            models.Add(i);
                        }
                        eventPositionModels[el] = models;
                    }
                }

                ModelInstance instance = new ModelInstance();
                instance.Setup("Models\\position-model", 0);
                instance.Scale.Value = new Vector3(0.25f);
                result.Add(instance);
                sessionPositionModels.Add(s.Session, instance);
                s.Active.Value = true;
                timeline.Children.Add(s.Session.Events.Where(x => analyticsActiveEvents.FirstOrDefault(y => y.Name == x.Name) != null).Select(createEventLines));
                playbackLocation.Reset();

                refreshPropertyGraphs();
            };

            analyticsActiveSessions.ItemRemoved += delegate(int index, SessionEntry s)
            {
                ModelInstance instance = sessionPositionModels[s.Session];
                instance.Delete.Execute();

                foreach (KeyValuePair<Session.EventList, List<ModelInstance>> pair in eventPositionModels.ToList())
                {
                    if (pair.Key.Session == s.Session)
                    {
                        foreach (ModelInstance i in pair.Value)
                            i.Delete.Execute();
                        eventPositionModels.Remove(pair.Key);
                    }
                }

                sessionPositionModels.Remove(s.Session);
                s.Active.Value = false;
                timeline.Children.Remove(timeline.Children.Where(x => x.UserData.Value != null && ((Session.EventList)x.UserData.Value).Session == s.Session).ToList());

                refreshPropertyGraphs();
            };

            playbackLocation.Set = delegate(float value)
            {
                if (analyticsActiveSessions.Count == 0)
                    return;

                value = Math.Max(0.0f, value);
                float end = analyticsActiveSessions.Max(x => x.Session.TotalTime);
                if (value > end)
                {
                    playbackLocation.InternalValue = end;
                    analyticsPlaying.Value = false;
                }
                else
                    playbackLocation.InternalValue = value;

                foreach (KeyValuePair<Session, ModelInstance> pair in sessionPositionModels)
                    pair.Value.Transform.Value = Matrix.CreateTranslation(pair.Key.PositionProperties[0][playbackLocation]);
            };

            LineDrawer2D playbackLine = new LineDrawer2D();
            playbackLine.Color.Value = Vector4.One;
            playbackLine.Lines.Add(new LineDrawer2D.Line
            {
                A = new Microsoft.Xna.Framework.Graphics.VertexPositionColor
                {
                    Color = Microsoft.Xna.Framework.Color.White,
                    Position = new Vector3(0.0f, -10.0f, 0.0f),
                },
                B = new Microsoft.Xna.Framework.Graphics.VertexPositionColor
                {
                    Color = Microsoft.Xna.Framework.Color.White,
                    Position = new Vector3(0.0f, timeline.Size.Value.Y, 0.0f),
                },
            });
            playbackLine.Add(new Binding<Vector2, float>(playbackLine.Position, x => new Vector2(x, 0.0f), playbackLocation));
            timeline.Children.Add(playbackLine);

            result.Add(new NotifyBinding(delegate()
            {
                allEventsButton.Detach();
                allSessionsButton.Detach();
                allPropertiesButton.Detach();
                analyticsSessions.Clear();
                analyticsEvents.Clear();
                analyticsProperties.Clear();
                eventList.Children.Add(allEventsButton);
                sessionList.Children.Add(allSessionsButton);
                propertiesList.Children.Add(allPropertiesButton);

                foreach (ModelInstance instance in sessionPositionModels.Values)
                    instance.Delete.Execute();
                sessionPositionModels.Clear();

                foreach (ModelInstance instance in eventPositionModels.Values.SelectMany(x => x))
                    instance.Delete.Execute();
                eventPositionModels.Clear();

                allEvents.Value = false;
                allSessions.Value = false;
                allProperties.Value = false;
                analyticsEnable.Value = false;

                analyticsActiveEvents.Clear();
                analyticsActiveSessions.Clear();
                analyticsActiveProperties.Clear();

                propertyTimelines.Children.Clear();

                playbackLine.Detach();
                timeline.Children.Clear();
                timeline.Children.Add(playbackLine);

                analyticsPlaying.Value = false;
                playbackLocation.Value = 0.0f;
            }, main.MapFile));

            addCommand("Toggle analytics playback", new PCInput.Chord { Modifier = Keys.LeftAlt, Key = Keys.A }, () => analyticsEnable && !editor.MovementEnabled && analyticsActiveSessions.Count > 0, new Command
            {
                Action = delegate()
                {
                    analyticsPlaying.Value = !analyticsPlaying;
                }
            });

            addCommand("Stop analytics playback", new PCInput.Chord { Key = Keys.Escape }, () => analyticsPlaying, new Command
            {
                Action = delegate()
                {
                    analyticsPlaying.Value = false;
                }
            });

            Container playbackContainer = new Container();
            playbackContainer.Tint.Value = Microsoft.Xna.Framework.Color.Black;
            playbackContainer.Opacity.Value = 0.5f;
            sessionsSidebar.Children.Add(playbackContainer);
            playbackContainer.Add(new CommandBinding<Point, int>(playbackContainer.MouseScrolled, delegate(Point p, int delta)
            {
                playbackSpeed.Value = Math.Max(1.0f, Math.Min(10.0f, playbackSpeed.Value + delta));
            }));

            TextElement playbackLabel = new TextElement();
            playbackLabel.FontFile.Value = "Font";
            playbackLabel.Add(new Binding<string>(playbackLabel.Text, delegate()
            {
                return playbackLocation.Value.ToString("F") + " " + (analyticsPlaying ? "Playing" : "Stopped") + " " + playbackSpeed.Value.ToString("F") + "x";
            }, playbackLocation, playbackSpeed, analyticsPlaying));
            playbackContainer.Children.Add(playbackLabel);

            Container descriptionContainer = null;

            Updater timelineUpdate = new Updater
            {
                delegate(float dt)
                {
                    bool setTimelinePosition = false;

                    if (timelines.Highlighted || descriptionContainer != null)
                    {
                        if (input.LeftMouseButton)
                        {
                            setTimelinePosition = true;
                            playbackLocation.Value = Vector3.Transform(new Vector3(input.Mouse.Value.X, 0.0f, 0.0f), Matrix.Invert(timeline.GetAbsoluteTransform())).X;
                        }

                        float threshold = 3.0f / timelines.Scale.Value.X;
                        float mouseRelative = Vector3.Transform(new Vector3(input.Mouse, 0.0f), Matrix.Invert(timelines.GetAbsoluteTransform())).X;

                        if (descriptionContainer != null)
                        {
                            if (!timelines.Highlighted || (float)Math.Abs(descriptionContainer.Position.Value.X - mouseRelative) > threshold)
                            {
                                descriptionContainer.Delete.Execute();
                                descriptionContainer = null;
                            }
                        }

                        if (descriptionContainer == null && timeline.Highlighted)
                        {
                            bool stop = false;
                            foreach (UIComponent component in timeline.Children)
                            {
                                LineDrawer2D lines = component as LineDrawer2D;

                                if (lines == null)
                                    continue;

                                foreach (LineDrawer2D.Line line in lines.Lines)
                                {
                                    Session.EventList el = lines.UserData.Value as Session.EventList;
                                    if (el != null && (float)Math.Abs(line.A.Position.X - mouseRelative) < threshold)
                                    {
                                        descriptionContainer = new Container();
                                        descriptionContainer.AnchorPoint.Value = new Vector2(0.5f, 1.0f);
                                        descriptionContainer.Position.Value = new Vector2(line.A.Position.X, 0.0f);
                                        descriptionContainer.Opacity.Value = 1.0f;
                                        descriptionContainer.Tint.Value = Microsoft.Xna.Framework.Color.Black;
                                        descriptionContainer.Add(new Binding<Vector2>(descriptionContainer.Scale, x => new Vector2(1.0f / x.X, 1.0f / x.Y), timelines.Scale));
                                        timeline.Children.Add(descriptionContainer);
                                        TextElement description = new TextElement();
                                        description.WrapWidth.Value = 256;
                                        description.Text.Value = el.Name;
                                        description.FontFile.Value = "Font";
                                        descriptionContainer.Children.Add(description);
                                        stop = true;
                                        break;
                                    }
                                }
                                if (stop)
                                    break;
                            }
                        }
                    }

                    if (analyticsPlaying && !setTimelinePosition)
                    {
                        if (analyticsActiveSessions.Count == 0)
                            analyticsPlaying.Value = false;
                        else
                            playbackLocation.Value += dt * playbackSpeed;
                    }
                }
            };
            timelineUpdate.EnabledInEditMode.Value = true;
            result.Add(timelineUpdate);

            // Save
            addCommand("Save", new PCInput.Chord { Modifier = Keys.LeftControl, Key = Keys.S }, () => !editor.MovementEnabled, editor.Save);

            // Deselect all entities
            addCommand("Deselect all", new PCInput.Chord { Modifier = Keys.LeftControl, Key = Keys.A }, () => !editor.MovementEnabled, new Command
            {
                Action = delegate()
                {
                    editor.SelectedEntities.Clear();
                }
            });

            int brush = 1;
            Action<int> changeBrush = delegate(int delta)
            {
                int foundIndex = WorldFactory.StateList.FindIndex(x => x.Name == editor.Brush);
                if (foundIndex != -1)
                    brush = foundIndex;
                int stateCount = WorldFactory.States.Count + 1;
                brush = 1 + ((brush - 1 + delta) % (stateCount - 1));
                if (brush < 1)
                    brush = stateCount + ((brush - 1) % stateCount);
                if (brush == stateCount - 1)
                    editor.Brush.Value = "[Procedural]";
                else
                    editor.Brush.Value = WorldFactory.StateList[brush].Name;
            };
            result.Add(new CommandBinding(input.GetKeyDown(Keys.Q), () => editor.MapEditMode, delegate()
            {
                changeBrush(-1);
            }));
            result.Add(new CommandBinding(input.GetKeyDown(Keys.E), () => editor.MapEditMode && !input.GetKey(Keys.LeftShift), delegate()
            {
                changeBrush(1);
            }));
            result.Add(new CommandBinding<int>(input.MouseScrolled, () => editor.MapEditMode && !input.GetKey(Keys.LeftAlt), delegate(int delta)
            {
                editor.BrushSize.Value = Math.Max(1, editor.BrushSize.Value + delta);
            }));

            addCommand("Propagate current material", new PCInput.Chord { Modifier = Keys.LeftShift, Key = Keys.E }, () => editor.MapEditMode, editor.PropagateMaterial);
            addCommand("Sample current material", new PCInput.Chord { Modifier = Keys.LeftShift, Key = Keys.Q }, () => editor.MapEditMode, editor.SampleMaterial);
            addCommand("Delete current material", new PCInput.Chord { Modifier = Keys.LeftShift, Key = Keys.X }, () => editor.MapEditMode, editor.DeleteMaterial);

            editor.Add(new Binding<Vector2>(editor.Mouse, input.Mouse, () => !input.EnableLook));

            Camera camera = main.Camera;

            Property<float> cameraDistance = new Property<float> { Value = 10.0f };
            scroller.Add(new Binding<bool>(scroller.EnableScroll, x => !x, input.GetKey(Keys.LeftAlt)));
            input.Add(new CommandBinding<int>(input.MouseScrolled, () => input.GetKey(Keys.LeftAlt), delegate(int delta)
            {
                if (timelineScroller.Highlighted && !editor.MapEditMode)
                {
                    float newScale = Math.Max(timelines.Scale.Value.X + delta * 6.0f, timelineScroller.Size.Value.X / timelines.Size.Value.X);
                    Matrix absoluteTransform = timelines.GetAbsoluteTransform();
                    float x = input.Mouse.Value.X + ((absoluteTransform.Translation.X - input.Mouse.Value.X) * (newScale / timelines.Scale.Value.X));
                    timelines.Position.Value = new Vector2(x, 0.0f);
                    timelines.Scale.Value = new Vector2(newScale, 1.0f);
                }
                else
                    cameraDistance.Value = Math.Max(5, cameraDistance.Value + delta * -2.0f);
            }));
            input.Add(new Binding<bool>(input.EnableLook, () => editor.MapEditMode || (input.MiddleMouseButton && editor.TransformMode.Value == Editor.TransformModes.None), input.MiddleMouseButton, editor.MapEditMode, editor.TransformMode));
            input.Add(new Binding<Vector3, Vector2>(camera.Angles, x => new Vector3(-x.Y, x.X, 0.0f), input.Mouse, () => input.EnableLook));
            input.Add(new Binding<bool>(main.IsMouseVisible, x => !x, input.EnableLook));
            editor.Add(new Binding<Vector3>(camera.Position, () => editor.Position.Value - (camera.Forward.Value * cameraDistance), editor.Position, input.Mouse, cameraDistance));

            PointLight editorLight = result.GetOrCreate<PointLight>("EditorLight");
            editorLight.Serialize = false;
            editorLight.Editable = false;
            editorLight.Shadowed.Value = false;
            editorLight.Add(new Binding<float>(editorLight.Attenuation, x => x * 2.0f, cameraDistance));
            editorLight.Color.Value = Vector3.One;
            editorLight.Add(new Binding<Vector3>(editorLight.Position, main.Camera.Position));
            editorLight.Enabled.Value = false;

            ui.PopupCommands.Add(new EditorUI.PopupCommand
            {
                Description = "Toggle editor light",
                Enabled = () => editor.SelectedEntities.Count == 0 && !editor.MapEditMode,
                Action = new Command { Action = () => editorLight.Enabled.Value = !editorLight.Enabled },
            });

            editor.Add(new CommandBinding(input.RightMouseButtonDown, () => !ui.PopupVisible && !editor.MapEditMode && !input.EnableLook && editor.TransformMode.Value == Editor.TransformModes.None, delegate()
            {
                // We're not editing a map
                // And we're not transforming entities
                // So we must be selecting / deselecting entities
                bool multiselect = input.GetKey(Keys.LeftShift);

                Vector2 mouse = input.Mouse;
                Microsoft.Xna.Framework.Graphics.Viewport viewport = main.GraphicsDevice.Viewport;
                Vector3 ray = Vector3.Normalize(viewport.Unproject(new Vector3(mouse.X, mouse.Y, 1), camera.Projection, camera.View, Matrix.Identity) - viewport.Unproject(new Vector3(mouse.X, mouse.Y, 0), camera.Projection, camera.View, Matrix.Identity));

                Entity closestEntity;
                Transform closestTransform;
                this.raycast(main, ray, out closestEntity, out closestTransform);

                if (closestEntity != null)
                {
                    if (editor.SelectedEntities.Count == 1 && input.GetKey(Keys.LeftControl).Value)
                    {
                        // The user is trying to connect the two entities
                        Entity entity = editor.SelectedEntities.First();
                        Command<Entity> toggleConnection = entity.GetCommand<Entity>("ToggleEntityConnected");
                        if (toggleConnection != null)
                        {
                            toggleConnection.Execute(closestEntity);
                            editor.NeedsSave.Value = true;
                        }
                        return;
                    }

                    if (multiselect)
                    {
                        if (editor.SelectedEntities.Contains(closestEntity))
                            editor.SelectedEntities.Remove(closestEntity);
                        else
                            editor.SelectedEntities.Add(closestEntity);
                    }
                    else
                    {
                        editor.SelectedEntities.Clear();
                        editor.SelectedEntities.Add(closestEntity);
                        editor.SelectedTransform.Value = closestTransform;
                    }
                }
                else
                {
                    editor.SelectedEntities.Clear();
                    editor.SelectedTransform.Value = null;
                }
            }));

            editor.Add(new CommandBinding(input.GetKeyDown(Keys.Escape), delegate()
            {
                if (editor.TransformMode.Value != Editor.TransformModes.None)
                    editor.RevertTransform.Execute();
                else if (editor.MapEditMode)
                    editor.MapEditMode.Value = false;
            }));

            addCommand("Toggle voxel edit", new PCInput.Chord { Key = Keys.Tab }, delegate()
            {
                if (editor.TransformMode.Value != Editor.TransformModes.None)
                    return false;

                if (editor.MapEditMode)
                    return true;
                else
                    return editor.SelectedEntities.Count == 1 && editor.SelectedEntities[0].Get<Map>() != null;
            },
            new Command
            {
                Action = delegate()
                {
                    editor.MapEditMode.Value = !editor.MapEditMode;
                }
            });

            addCommand
            (
                "Grab (move)",
                new PCInput.Chord { Key = Keys.G },
                () => editor.SelectedEntities.Count > 0 && !input.EnableLook && !editor.MapEditMode && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.StartTranslation
            );
            addCommand
            (
                "Grab (move)",
                new PCInput.Chord { Key = Keys.G },
                () => editor.MapEditMode && editor.VoxelSelectionActive && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.StartVoxelTranslation
            );
            addCommand
            (
                "Voxel duplicate",
                new PCInput.Chord { Key = Keys.C },
                () => editor.MapEditMode && editor.VoxelSelectionActive && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.VoxelDuplicate
            );
            addCommand
            (
                "Voxel yank",
                new PCInput.Chord { Key = Keys.Y },
                () => editor.MapEditMode && editor.VoxelSelectionActive && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.VoxelCopy
            );
            addCommand
            (
                "Voxel paste",
                new PCInput.Chord { Key = Keys.P },
                () => editor.MapEditMode && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.VoxelPaste
            );
            addCommand
            (
                "Rotate",
                new PCInput.Chord { Key = Keys.R },
                () => editor.SelectedEntities.Count > 0 && !editor.MapEditMode && !input.EnableLook && editor.TransformMode.Value == Editor.TransformModes.None,
                editor.StartRotation
            );
            addCommand
            (
                "Lock X axis",
                new PCInput.Chord { Key = Keys.X },
                () => !editor.MapEditMode && editor.TransformMode.Value != Editor.TransformModes.None,
                new Command { Action = () => editor.TransformAxis.Value = Editor.TransformAxes.X }
            );
            addCommand
            (
                "Lock Y axis",
                new PCInput.Chord { Key = Keys.Y },
                () => !editor.MapEditMode && editor.TransformMode.Value != Editor.TransformModes.None,
                new Command { Action = () => editor.TransformAxis.Value = Editor.TransformAxes.Y }
            );
            addCommand
            (
                "Lock Z axis",
                new PCInput.Chord { Key = Keys.Z },
                () => !editor.MapEditMode && editor.TransformMode.Value != Editor.TransformModes.None,
                new Command { Action = () => editor.TransformAxis.Value = Editor.TransformAxes.Z }
            );

            editor.Add(new CommandBinding
            (
                input.LeftMouseButtonDown,
                () => editor.TransformMode.Value != Editor.TransformModes.None,
                editor.CommitTransform
            ));
            editor.Add(new CommandBinding
            (
                input.RightMouseButtonDown,
                () => editor.TransformMode.Value != Editor.TransformModes.None,
                editor.RevertTransform
            ));
        }
Beispiel #17
0
        private static void attachEditorComponents(Entity result, Main main)
        {
            Transform transform = result.Get<Transform>();

            Property<bool> selected = new Property<bool> { Value = false, Editable = false, Serialize = false };
            result.Add("EditorSelected", selected);

            Property<Entity.Handle> parentMap = result.GetOrMakeProperty<Entity.Handle>("Parent");

            Command<Entity> toggleEntityConnected = new Command<Entity>
            {
                Action = delegate(Entity entity)
                {
                    parentMap.Value = entity;
                }
            };
            result.Add("ToggleEntityConnected", toggleEntityConnected);

            LineDrawer connectionLines = new LineDrawer { Serialize = false };
            connectionLines.Add(new Binding<bool>(connectionLines.Enabled, selected));

            Color connectionLineColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);

            Action recalculateLine = delegate()
            {
                connectionLines.Lines.Clear();
                Entity parent = parentMap.Value.Target;
                if (parent != null)
                {
                    connectionLines.Lines.Add(new LineDrawer.Line
                    {
                        A = new Microsoft.Xna.Framework.Graphics.VertexPositionColor(transform.Position, connectionLineColor),
                        B = new Microsoft.Xna.Framework.Graphics.VertexPositionColor(parent.Get<Transform>().Position, connectionLineColor)
                    });
                }
            };

            Model model = new Model();
            model.Filename.Value = "Models\\cone";
            model.Editable = false;
            model.Serialize = false;
            result.Add("DirectionModel", model);

            Property<Direction> dir = result.GetProperty<Direction>("Direction");
            Transform mapTransform = result.Get<Transform>("MapTransform");
            model.Add(new Binding<Matrix>(model.Transform, delegate()
            {
                Matrix m = Matrix.Identity;
                m.Translation = transform.Position;

                if (dir == Direction.None)
                    m.Forward = m.Right = m.Up = Vector3.Zero;
                else
                {
                    Vector3 normal = Vector3.TransformNormal(dir.Value.GetVector(), mapTransform.Matrix);

                    m.Forward = -normal;
                    if (normal.Equals(Vector3.Up))
                        m.Right = Vector3.Left;
                    else if (normal.Equals(Vector3.Down))
                        m.Right = Vector3.Right;
                    else
                        m.Right = Vector3.Normalize(Vector3.Cross(normal, Vector3.Down));
                    m.Up = Vector3.Cross(normal, m.Left);
                }
                return m;
            }, transform.Matrix, mapTransform.Matrix));

            NotifyBinding recalculateBinding = null;
            Action rebuildBinding = delegate()
            {
                if (recalculateBinding != null)
                {
                    connectionLines.Remove(recalculateBinding);
                    recalculateBinding = null;
                }
                if (parentMap.Value.Target != null)
                {
                    recalculateBinding = new NotifyBinding(recalculateLine, parentMap.Value.Target.Get<Transform>().Matrix);
                    connectionLines.Add(recalculateBinding);
                }
                recalculateLine();
            };
            connectionLines.Add(new NotifyBinding(rebuildBinding, parentMap));

            connectionLines.Add(new NotifyBinding(recalculateLine, selected));
            connectionLines.Add(new NotifyBinding(recalculateLine, () => selected, transform.Position));
            result.Add(connectionLines);
        }
        /// <summary>
        /// 递归读取根对象的所有子对象
        /// </summary>
        /// <param name="entity"></param>
        private void ReadChildren(Entity entity)
        {
            var allProperties = entity.PropertiesContainer.GetNonReadOnlyCompiledProperties();

            var childrenList = new List<IList<Entity>>();

            //遍历所有子属性,读取孩子列表
            for (int i = 0, c = allProperties.Count; i < c; i++)
            {
                var property = allProperties[i];
                if (property is IListProperty)
                {
                    var children = entity.GetProperty(property) as IList<Entity>;
                    if (children != null && children.Count > 0)
                    {
                        //所有孩子列表中的实体,都加入到对应的实体列表中。
                        //并递归读取孩子的孩子实体。
                        var entityType = children[0].GetType();
                        var list = this.FindAggregateList(entityType);
                        childrenList.Add(list);
                        for (int j = 0, c2 = children.Count; j < c2; j++)
                        {
                            var child = children[j];

                            list.Add(child);
                            this.ReadChildren(child);
                        }
                    }
                }
            }
        }
Beispiel #19
0
        private string BuildError(Entity entity)
        {
            if (this.MessageBuilder != null)
            {
                return this.MessageBuilder(entity);
            }

            var propertyFormat = "属性 {0} 的值是 {1}".Translate();
            var error = new StringBuilder("已经存在");
            bool first = true;
            foreach (IProperty property in this.Properties)
            {
                if (!first) error.Append("、".Translate());
                first = false;

                var value = entity.GetProperty(property);
                error.AppendFormat(propertyFormat, Display(property), value);
            }
            error.Append(" 的实体 ".Translate())
                .Append(Display(entity.GetType()));

            return error.ToString();
        }
Beispiel #20
0
        /// <summary>
        /// 根据传入的属性列表,来构造 CommonQueryCriteria
        /// 返回是否有非空属性需要验证。
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="criteria"></param>
        /// <returns></returns>
        private bool AddToCriteria(Entity entity, CommonQueryCriteria criteria)
        {
            bool hasValue = false;

            foreach (IProperty property in this.Properties)
            {
                EnsurePropertyCategory(property);

                var value = entity.GetProperty(property);
                if (DomainHelper.IsNotEmpty(value))
                {
                    hasValue = true;
                    criteria.Add(property, value);
                }
            }
            if (entity.HasId)
            {
                criteria.Add(Entity.IdProperty, PropertyOperator.NotEqual, entity.Id);
            }
            return hasValue;
        }
Beispiel #21
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            Factory.Get<DynamicMapFactory>().Bind(result, main);

            Transform transform = result.Get<Transform>();
            DynamicMap map = result.Get<DynamicMap>();
            PointLight light = result.Get<PointLight>();

            Sound blastFireSound = result.Get<Sound>("BlastFireSound");
            blastFireSound.Add(new Binding<Vector3>(blastFireSound.Position, transform.Position));
            blastFireSound.Add(new Binding<Vector3>(blastFireSound.Velocity, map.LinearVelocity));

            Sound blastChargeSound = result.Get<Sound>("BlastChargeSound");
            blastChargeSound.Add(new Binding<Vector3>(blastChargeSound.Position, transform.Position));
            blastChargeSound.Add(new Binding<Vector3>(blastChargeSound.Velocity, map.LinearVelocity));

            map.Add(new CommandBinding(map.CompletelyEmptied, delegate()
            {
                if (!main.EditorEnabled)
                    result.Delete.Execute();
            }));

            EntityRotator rotator = null;
            EntityMover mover = null;
            if (!main.EditorEnabled)
            {
                rotator = new EntityRotator(map.PhysicsEntity);
                main.Space.Add(rotator);

                mover = new EntityMover(map.PhysicsEntity);
                mover.TargetPosition = transform.Position;
                main.Space.Add(mover);
            }

            Map.Coordinate blastSource = map.GetCoordinate(0, 0, 0);
            Map.Coordinate blastPosition = blastSource;
            Map.CellState criticalMaterial = WorldFactory.StatesByName["Critical"];
            foreach (Map.Box box in map.Chunks.SelectMany(x => x.Boxes))
            {
                if (box.Type == criticalMaterial)
                {
                    blastSource = map.GetCoordinate(box.X, box.Y, box.Z);
                    blastPosition = map.GetCoordinate(box.X, box.Y, box.Z - 3);
                    break;
                }
            }

            Property<float> blastIntervalTime = result.GetProperty<float>("BlastInterval");
            float blastInterval = 0.0f;

            Property<float> playerPositionMemoryTime = result.GetProperty<float>("PlayerPositionMemoryTime");
            float timeSinceLastSpottedPlayer = playerPositionMemoryTime;

            Property<float> visibilityCheckInterval = result.GetProperty<float>("VisibilityCheckInterval");
            float timeSinceLastVisibilityCheck = 0.0f;

            Property<float> blastChargeTime = result.GetProperty<float>("BlastChargeTime");
            float blastCharge = 0.0f;

            Property<float> blastSpeed = result.GetProperty<float>("BlastSpeed");
            Property<float> playerDetectionRadius = result.GetProperty<float>("PlayerDetectionRadius");

            Updater update = new Updater();
            update.Add(delegate(float dt)
                {
                    if (map[blastSource].ID == 0)
                    {
                        update.Delete.Execute();
                        if (rotator != null)
                        {
                            main.Space.Remove(rotator);
                            main.Space.Remove(mover);
                        }
                        light.Delete.Execute();
                        return;
                    }
                    Entity player = PlayerFactory.Instance;
                    if (player != null)
                    {
                        Vector3 playerPosition = player.Get<Transform>().Position.Value;

                        Vector3 rayStart = map.GetAbsolutePosition(blastPosition);

                        Vector3 rayDirection = playerPosition - rayStart;
                        rayDirection.Normalize();

                        timeSinceLastVisibilityCheck += dt;
                        if (timeSinceLastVisibilityCheck > visibilityCheckInterval)
                        {
                            if ((playerPosition - transform.Position).Length() < playerDetectionRadius)
                                timeSinceLastSpottedPlayer = 0.0f;
                            else if (Vector3.Dot(rayDirection, map.GetAbsoluteVector(Vector3.Forward)) > 0)
                            {
                                RayCastResult hit;
                                if (main.Space.RayCast(new Ray(rayStart, rayDirection), out hit))
                                {
                                    EntityCollidable collidable = hit.HitObject as EntityCollidable;
                                    if (collidable != null && collidable.Entity.Tag is Player)
                                        timeSinceLastSpottedPlayer = 0.0f;
                                }
                            }
                            timeSinceLastVisibilityCheck = 0.0f;
                        }
                        timeSinceLastSpottedPlayer += dt;

                        light.Attenuation.Value = 0.0f;
                        if (timeSinceLastSpottedPlayer < playerPositionMemoryTime)
                        {
                            rotator.TargetOrientation = Quaternion.CreateFromRotationMatrix(Matrix.Invert(Matrix.CreateLookAt(rayStart, playerPosition, Vector3.Up)));
                            if (blastInterval > blastIntervalTime)
                            {
                                if (blastCharge < blastChargeTime)
                                {
                                    if (blastCharge == 0.0f)
                                        blastChargeSound.Play.Execute();
                                    blastCharge += dt;
                                    light.Position.Value = rayStart;
                                    light.Attenuation.Value = (blastCharge / blastChargeTime) * 30.0f;
                                }
                                else
                                {
                                    blastCharge = 0.0f;
                                    blastFireSound.Play.Execute();
                                    blastInterval = 0.0f;
                                    Entity blast = Factory.CreateAndBind(main, "Blast");

                                    PhysicsBlock physics = blast.Get<PhysicsBlock>();
                                    Transform blastTransform = blast.Get<Transform>();
                                    blastTransform.Position.Value = rayStart;
                                    physics.LinearVelocity.Value = (rayDirection * blastSpeed) + new Vector3(0.0f, 6.0f, 0.0f);
                                    main.Add(blast);
                                }
                            }
                            else
                            {
                                blastInterval += dt;
                                blastCharge = 0.0f;
                            }
                        }
                        else
                            blastCharge = 0.0f;
                    }
                });
            result.Add("Update", update);
        }
Beispiel #22
0
 public void Setup(Entity result, Entity m, Map.Coordinate c, int s)
 {
     Property<Entity.Handle> map = result.GetProperty<Entity.Handle>("TargetMap");
     Property<Map.Coordinate> coord = result.GetProperty<Map.Coordinate>("TargetCoord");
     Property<int> stateId = result.GetProperty<int>("TargetCellStateID");
     map.InternalValue = m;
     coord.InternalValue = c;
     stateId.InternalValue = s;
     stateId.Changed();
 }
Beispiel #23
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            result.CannotSuspend = true;

            result.Add(new TwoWayBinding<string>(result.GetProperty<string>("LightRampTexture"), main.Renderer.LightRampTexture));
            result.Add(new TwoWayBinding<string>(result.GetOrMakeProperty<string>("EnvironmentMap", true, "Maps\\env0"), main.Renderer.EnvironmentMap));
            result.Add(new TwoWayBinding<Vector3>(result.GetOrMakeProperty<Vector3>("EnvironmentColor", true, Vector3.One), main.Renderer.EnvironmentColor));
            result.Add(new TwoWayBinding<Color>(result.GetProperty<Color>("BackgroundColor"), main.Renderer.BackgroundColor));
            result.Add(new TwoWayBinding<float>(result.GetProperty<float>("FarPlaneDistance"), main.Camera.FarPlaneDistance));

            WorldFactory.AddState(result.GetListProperty<Map.CellState>("AdditionalMaterials").ToArray());
            result.Add(new CommandBinding(result.Delete, delegate()
            {
                WorldFactory.RemoveState(result.GetListProperty<Map.CellState>("AdditionalMaterials").ToArray());
            }));

            // Zone management
            Zone currentZone = null;

            result.Add(new CommandBinding<Entity>(main.EntityAdded, delegate(Entity e)
            {
                if (!e.CannotSuspend)
                    processEntity(e, currentZone, getActiveBoundingBoxes(main.Camera, currentZone), main.Camera.Position, main.Camera.FarPlaneDistance);
            }));

            IEnumerable<NonAxisAlignedBoundingBox> boxes = getActiveBoundingBoxes(main.Camera, currentZone);
            Vector3 cameraPosition = main.Camera.Position;
            float suspendDistance = main.Camera.FarPlaneDistance;
            foreach (Entity e in main.Entities)
            {
                if (!e.CannotSuspend)
                    processEntity(e, currentZone, boxes, cameraPosition, suspendDistance);
            }

            result.Add("ProcessMap", new Command<Map>
            {
                Action = delegate(Map map)
                {
                    processMap(map, boxes);
                }
            });

            Property<float> reverbAmount = result.GetProperty<float>("ReverbAmount");
            Property<float> reverbSize = result.GetProperty<float>("ReverbSize");

            Sound.ReverbSettings(main, reverbAmount, reverbSize);

            Vector3 lastUpdatedCameraPosition = new Vector3(float.MinValue);
            bool lastFrameUpdated = false;

            Action<Zone> updateZones = delegate(Zone newZone)
            {
                currentZone = newZone;

                if (newZone != null)
                    Sound.ReverbSettings(main, newZone.ReverbAmount, newZone.ReverbSize);
                else
                    Sound.ReverbSettings(main, reverbAmount, reverbSize);

                boxes = getActiveBoundingBoxes(main.Camera, newZone);
                cameraPosition = main.Camera.Position;
                suspendDistance = main.Camera.FarPlaneDistance;
                foreach (Entity e in main.Entities)
                {
                    if (!e.CannotSuspend)
                        processEntity(e, newZone, boxes, cameraPosition, suspendDistance);
                }

                lastUpdatedCameraPosition = main.Camera.Position;
            };

            result.Add("UpdateZones", new Command
            {
                Action = delegate() { updateZones(Zone.Get(main.Camera.Position)); },
            });

            Updater update = new Updater
            {
                delegate(float dt)
                {
                    // Update every other frame
                    if (lastFrameUpdated)
                    {
                        lastFrameUpdated = false;
                        return;
                    }
                    lastFrameUpdated = true;

                    Zone newZone = Zone.Get(main.Camera.Position);

                    if (newZone != currentZone || (newZone == null && (main.Camera.Position - lastUpdatedCameraPosition).Length() > 10.0f))
                        updateZones(newZone);
                }
            };
            update.EnabledInEditMode.Value = true;
            result.Add(update);

            this.SetMain(result, main);
            WorldFactory.instance = result;
        }
Beispiel #24
0
        private object Read(Entity entity)
        {
            var refIdProperty = _columnInfo.Property as IRefIdProperty;
            if (refIdProperty != null)
            {
                object id = refIdProperty.Nullable ?
                    entity.GetRefNullableId(refIdProperty) : entity.GetRefId(refIdProperty);
                return id;
            }

            var value = entity.GetProperty(_columnInfo.Property);
            return value;
        }
Beispiel #25
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            result.CannotSuspend = true;
            Transform transform = result.Get<Transform>();
            ModelInstance model = result.Get<ModelInstance>();

            model.Add(new Binding<Matrix>(model.Transform, transform.Matrix));
            model.Scale.Value = Vector3.Zero;

            Property<bool> scale = result.GetProperty<bool>("Scale");
            Property<Vector3> start = result.GetProperty<Vector3>("StartPosition");
            start.Set = delegate(Vector3 value)
            {
                start.InternalValue = value;
                transform.Position.Value = value;
            };
            Property<Matrix> startOrientation = result.GetProperty<Matrix>("StartOrientation");
            Vector3 startEuler = Vector3.Zero;
            startOrientation.Set = delegate(Matrix value)
            {
                startOrientation.InternalValue = value;
                startEuler = Quaternion.CreateFromRotationMatrix(startOrientation).ToEuler();
                transform.Orientation.Value = value;
            };

            Property<Entity.Handle> map = result.GetProperty<Entity.Handle>("TargetMap");
            Property<Map.Coordinate> coord = result.GetProperty<Map.Coordinate>("TargetCoord");
            Property<int> stateId = result.GetProperty<int>("TargetCellStateID");

            Property<float> totalLifetime = result.GetProperty<float>("TotalLifetime");
            Property<float> lifetime = result.GetProperty<float>("Lifetime");

            Updater update = null;
            update = new Updater
            {
                delegate(float dt)
                {
                    lifetime.Value += dt;

                    float blend = lifetime / totalLifetime;

                    if (map.Value.Target == null || !map.Value.Target.Active)
                    {
                        result.Delete.Execute();
                        return;
                    }

                    Map m = map.Value.Target.Get<Map>();

                    if (blend > 1.0f)
                    {
                        if (stateId != 0)
                        {
                            Map.Coordinate c = coord;

                            bool foundAdjacentCell = false;
                            foreach (Direction dir in DirectionExtensions.Directions)
                            {
                                Map.Coordinate adjacent = c.Move(dir);
                                if (m[adjacent].ID != 0)
                                {
                                    foundAdjacentCell = true;
                                    break;
                                }
                            }
                            if (foundAdjacentCell)
                            {
                                bool foundConflict = false;
                                Vector3 absolutePosition = m.GetAbsolutePosition(c);
                                foreach (Map m2 in Map.ActivePhysicsMaps)
                                {
                                    if (m2 != m && m2[absolutePosition].Permanent)
                                    {
                                        foundConflict = true;
                                        break;
                                    }
                                }

                                if (!foundConflict)
                                {
                                    Map.CellState state = m[coord];
                                    if (state.Permanent)
                                        foundConflict = true;
                                    else
                                    {
                                        if (state.ID != 0)
                                            m.Empty(coord);
                                        m.Fill(coord, WorldFactory.States[stateId]);
                                        m.Regenerate();
                                        Sound.PlayCue(main, "BuildBlock", transform.Position, 1.0f, 0.06f);
                                        result.Delete.Execute();
                                        return;
                                    }
                                }
                            }

                            // For one reason or another, we can't fill the cell
                            // Animate nicely into oblivion
                            update.Delete.Execute();
                            result.Add(new Animation
                            (
                                new Animation.Vector3MoveTo(model.Scale, Vector3.Zero, 1.0f),
                                new Animation.Execute(result.Delete)
                            ));
                        }
                    }
                    else
                    {
                        if (scale)
                            model.Scale.Value = new Vector3(blend);
                        else
                            model.Scale.Value = new Vector3(1.0f);
                        Matrix finalOrientation = m.Transform;
                        finalOrientation.Translation = Vector3.Zero;
                        Vector3 finalEuler = Quaternion.CreateFromRotationMatrix(finalOrientation).ToEuler();
                        finalEuler = Vector3.Lerp(startEuler, finalEuler, blend);
                        transform.Orientation.Value = Matrix.CreateFromYawPitchRoll(finalEuler.X, finalEuler.Y, finalEuler.Z);

                        Vector3 finalPosition = m.GetAbsolutePosition(coord);
                        float distance = (finalPosition - start).Length() * 0.1f * Math.Max(0.0f, 0.5f - Math.Abs(blend - 0.5f));

                        transform.Position.Value = Vector3.Lerp(start, finalPosition, blend) + new Vector3((float)Math.Sin(blend * Math.PI) * distance);
                    }
                },
            };

            result.Add(update);

            BlockEntry entry = new BlockEntry();
            result.Add(new NotifyBinding(delegate()
            {
                if (entry.Map != null)
                    this.animatingBlocks.Remove(entry);

                Entity m = map.Value.Target;
                entry.Map = m != null ? m.Get<Map>() : null;

                if (entry.Map != null)
                {
                    entry.Coordinate = coord;
                    this.animatingBlocks[entry] = true;
                }
            }, map, coord, stateId));

            result.Add(new CommandBinding(result.Delete, delegate()
            {
                if (entry.Map != null)
                    this.animatingBlocks.Remove(entry);
                entry.Map = null;
            }));

            this.SetMain(result, main);
            IBinding offsetBinding = null;
            model.Add(new NotifyBinding(delegate()
            {
                if (offsetBinding != null)
                    model.Remove(offsetBinding);
                offsetBinding = new Binding<Vector3>(model.GetVector3Parameter("Offset"), result.GetProperty<Vector3>("Offset"));
                model.Add(offsetBinding);
            }, model.FullInstanceKey));
        }
Beispiel #26
0
        public override void Bind(Entity result, Main main, bool creating = false)
        {
            Factory.Get<DynamicMapFactory>().Bind(result, main, creating);

            Transform transform = result.Get<Transform>();
            DynamicMap map = result.Get<DynamicMap>();

            Sound zombieSound = new Sound();
            zombieSound.Serialize = false;
            zombieSound.Cue.Value = "Zombie";
            result.Add("ZombieSound", zombieSound);

            Property<bool> playerVisible = new Property<bool> { Value = false, Editable = false, Serialize = false };
            result.Add("PlayerVisible", playerVisible);

            zombieSound.Add(new Binding<Vector3>(zombieSound.Position, transform.Position));
            zombieSound.Add(new Binding<Vector3>(zombieSound.Velocity, map.LinearVelocity));

            Property<float> damage = result.GetProperty<float>("Damage");

            map.Add(new CommandBinding<Collidable, ContactCollection>(map.Collided, delegate(Collidable collidable, ContactCollection contact)
            {
                if (result.Active && collidable is EntityCollidable)
                {
                    if (((EntityCollidable)collidable).Entity.Tag is Player)
                    {
                        Player player = (Player)((EntityCollidable)collidable).Entity.Tag;
                        player.Health.Value -= damage;
                    }
                }
            }));

            Property<float> zombieSoundPitch = zombieSound.GetProperty("Pitch");

            Property<float> visibilityCheckInterval = result.GetProperty<float>("VisibilityCheckInterval");
            Property<float> torqueMultiplier = result.GetProperty<float>("TorqueMultiplier");
            Property<float> maxSpeed = result.GetProperty<float>("MaxSpeed");
            Property<float> playerPositionMemoryTime = result.GetProperty<float>("PlayerPositionMemoryTime");

            float timeSinceLastSpottedPlayer = playerPositionMemoryTime;
            float timeSinceLastVisibilityCheck = 0.0f;

            result.Add(new Updater
            {
                delegate(float dt)
                {
                    if (!result.Active)
                        return;

                    Entity player = PlayerFactory.Instance;
                    if (player != null)
                    {
                        Vector3 playerPosition = player.Get<Transform>().Position.Value;

                        Vector3 rayDirection = playerPosition - transform.Position;

                        float playerDistance = rayDirection.Length();

                        rayDirection /= playerDistance;

                        timeSinceLastVisibilityCheck += dt;
                        if (timeSinceLastVisibilityCheck > visibilityCheckInterval)
                        {
                            Map.GlobalRaycastResult hit = Map.GlobalRaycast(playerPosition + (rayDirection * -3.0f), -rayDirection, playerDistance);
                            if (hit.Map == map)
                            {
                                timeSinceLastSpottedPlayer = 0.0f;
                                playerVisible.Value = true;
                            }
                            timeSinceLastVisibilityCheck = 0.0f;
                        }
                        timeSinceLastSpottedPlayer += dt;

                        if (timeSinceLastSpottedPlayer < playerPositionMemoryTime)
                        {
                            float torque = torqueMultiplier * map.PhysicsEntity.Mass * dt;
                            Vector3 impulse = new Vector3(torque * rayDirection.Z, 0.0f, -torque * rayDirection.X);
                            map.PhysicsEntity.ApplyAngularImpulse(ref impulse);
                            Vector3 velocity = map.PhysicsEntity.AngularVelocity;
                            float speed = velocity.Length();
                            if (speed > maxSpeed)
                                map.PhysicsEntity.AngularVelocity = velocity * (maxSpeed / speed);

                            map.PhysicsEntity.ActivityInformation.Activate();
                            zombieSoundPitch.Value = ((speed / maxSpeed) / torque) - 1.0f;
                        }
                        else if (playerVisible)
                            playerVisible.Value = false;
                    }

                    if (timeSinceLastSpottedPlayer > playerPositionMemoryTime && zombieSound.IsPlaying)
                        zombieSound.Stop.Execute(Microsoft.Xna.Framework.Audio.AudioStopOptions.AsAuthored);
                    else if (timeSinceLastSpottedPlayer < playerPositionMemoryTime && !zombieSound.IsPlaying)
                        zombieSound.Play.Execute();
                }
            });
        }