// LiveStats Rifts Timeline by HaKache

using Turbo.Plugins.Default;
using System.Drawing;
using SharpDX.DirectInput;
using SharpDX.DirectWrite;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Media;
using System.Threading;
using System.Windows.Forms;

namespace Turbo.Plugins.LiveStats.Modules
{
    public class LiveStats_RiftTimeline : BasePlugin, ICustomizer, INewAreaHandler, IAfterCollectHandler, IInGameWorldPainter
    {	
	private string NameBoss { get; set; }
	private int TBoss { get; set; }
	private int TBossKilled { get; set; }
	private int TPylon { get; set; }
	private int TBossSpawned { get; set; }
	private int TEvent { get; set; }
	private int TBossEvent { get; set; }
	private uint InRiftLastAreaSno { get; set; }
        public Dictionary<uint, string> Lnn { get; set; }
	
	public int RiftHistoryTimeout { get; set; } = 0;	// Stop showing old rift events after this many seconds (0 = never time out)
	public int RiftHistoryHighlight { get; set; } = 10;	// Highlight if the event is new for this many seconds
	public int RiftHistoryShown { get; set; } = 20;		// Max # of events to show for rift history
        public int RiftLogShown { get; set; }

	public List<EventSnapshot> EventHistory { get; private set; }
	public List<MapSnapshot> MapHistory { get; private set; }

	public TopLabelDecorator Label { get; set; }
	public List<TopLabelDecorator> ExpandUpLabels { get; private set; }

	public IFont RiftFont { get; set; }
	public IFont GreyFont { get; set; }
	public IFont BossFont { get; set; }
	public IFont TimeFont { get; set; }
	public IFont WizFont { get; set; }
	public IFont NecroFont { get; set; }
	public IFont YellowFont { get; set; }
	public IFont DeadFont { get; set; }
        public IFont OrangeFont { get; set; }
        public IFont TealFont { get; set; }

	public IBrush BgBrush { get; set; }
	public IBrush BgBrushAlt { get; set; }
	public IBrush HighlightBrush { get; set; }

	public double GRProgress { get; set; } = 0;
	public string GRTier { get; set; }
	public int GRPlayers { get; set; }

	public bool Streak100 { get; set; } = false;
	public bool Streak150 { get; set; } = false;
	public bool Streak200 { get; set; } = false;
	public bool Streak400 { get; set; } = false;
	public bool Streak500 { get; set; } = false;
	public bool Streak1000 { get; set; } = false;

	public bool Progress20 { get; set; } = false;
	public bool Progress40 { get; set; } = false;
	public bool Progress60 { get; set; } = false;
	public bool Progress80 { get; set; } = false;

	public bool HauntedVisions { get; set; } = false;

	public BuffRuleCalculator ProcRuleCalculator { get; set; } // Track if a player is procced
	public bool ProcP1 { get; set; } = false;
	public bool ProcP2 { get; set; } = false;
	public bool ProcP3 { get; set; } = false;
	public bool ProcP4 { get; set; } = false;
	
        public int Priority { get; set; } = 99;
        public int Hook { get; set; } = 0;

	public class EventSnapshot 
	{
		public string Id { get; set; }
		public int EventType { get; set; }
		public DateTime Timestamp { get; set; }
		public string EventName { get; set; }
		public uint Area { get; set; }
		public double Progress { get; set; }
		public string Difficulty { get; set; }
		public string TimerKill { get; set; }
		public string Timer { get; set; }
	}

	public class MapSnapshot 
	{
		public string Id { get; set; }
		public uint WorldId { get; set; }
		public int EventType { get; set; }
		public DateTime Timestamp { get; set; }
		public string Name { get; set; }
		public IWorldCoordinate FloorCoordinate { get; set; }
		public uint Area { get; set; }
		public bool Taken { get; set; }
	}

	// Marker style for the minimap
        public WorldDecoratorCollection MapMarkerPylon { get; set; }
        public WorldDecoratorCollection MapMarkerPylonUsed { get; set; }
        public WorldDecoratorCollection MapMarkerArchon { get; set; }
        public WorldDecoratorCollection MapMarkerDead { get; set; }

	// Dynamic Menu
        public class MenuRule
        {
            // Rules
	    public float Index { get; set; }
	    public StringGeneratorFunc Menu { get; set; }
            public Func<bool> IsRelevant { get; set; }	
            public StringGeneratorFunc Description { get; set; }

            // Track and display
            public TopLabelDecorator Label { get; set; }
            public IBrush BgBrush { get; set; } // Optional
            public IFont Font { get; set; } // Optional
            
            public MenuRule() {}
        }
        public List<MenuRule> Tracking { get; set; }
			
	public LiveStats_RiftTimeline()
	{
	    Enabled = true;
	}
	
	public void Customize()
	{
	    // Add this display to the LiveStats readout with a specified positional order priority of 26
	    Hud.RunOnPlugin<LiveStatsPlugin>(plugin => { 
			plugin.Add(this.Label, this.Priority, this.Hook); 
	    }); 
	}

        public override void Load(IController hud)
        {
            base.Load(hud);

	    EventHistory = new List<EventSnapshot>();
	    MapHistory = new List<MapSnapshot>();

	    TimeFont = Hud.Render.CreateFont("tahoma", 7, 190, 255, 255, 255, false, false, true);
	    RiftFont = Hud.Render.CreateFont("tahoma", 7, 255, 128, 90, 255, false, false, true); //198, 86, 255
	    GreyFont = Hud.Render.CreateFont("tahoma", 7, 255, 120, 120, 120, false, false, true);
	    BossFont = Hud.Render.CreateFont("tahoma", 7, 190, 255, 0, 0, false, false, true);
	    YellowFont = Hud.Render.CreateFont("tahoma", 7, 190, 255, 255, 55, false, false, true);
	    OrangeFont = Hud.Render.CreateFont("tahoma", 7, 255, 255, 128, 43, false, false, true);
	    TealFont = Hud.Render.CreateFont("tahoma", 7, 255, 32, 180, 140, false, false, true);

	    WizFont = Hud.Render.CreateFont("tahoma", 7, 255, 186, 14, 137, false, false, true);
	    NecroFont = Hud.Render.CreateFont("tahoma", 7, 255, 64, 192, 176, false, false, true);
	    DeadFont = Hud.Render.CreateFont("tahoma", 7, 255, 255, 60, 60, false, false, true);

	    ProcRuleCalculator = new BuffRuleCalculator(Hud);
	    ProcRuleCalculator.SizeMultiplier = 0.65f;
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.Wizard_Passive_UnstableAnomaly.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Passive downtime
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.Monk_Passive_NearDeathExperience.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Passive downtime
	    ProcRuleCalculator.Rules.Add(new BuffRule(359580) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});							// Firebird 2P downtime
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.Barbarian_Passive_NervesOfSteel.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Cooldown
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.Necromancer_Passive_FinalService.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Cooldown
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.Crusader_Passive_Indestructible.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Passive cooldown
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.DemonHunter_Passive_Awareness.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Passive cooldown
	    ProcRuleCalculator.Rules.Add(new BuffRule(Hud.Sno.SnoPowers.WitchDoctor_Passive_SpiritVessel.Sno) { IconIndex = 1, MinimumIconCount = 1, ShowTimeLeft = true, ShowStacks = false});	// Passive cooldown

            Lnn = new Dictionary<uint,string>   // <SnoArea.Sno, Level string>
            {
                {288482, "L1"},   {288684, "L2"},   {288686, "L3"},   {288797, "L4"},   {288799, "L5"},
                {288801, "L6"},   {288803, "L7"},   {288809, "L8"},   {288812, "L9"},   {288813, "L10"},
                {0, "L?"}  // Safecheck
            };  
			
	    var plugin = Hud.GetPlugin<LiveStatsPlugin>();
	    BgBrush = plugin.BgBrush;
	    BgBrushAlt = plugin.BgBrushAlt;
	    HighlightBrush = Hud.Render.CreateBrush(200, 72, 132, 84, 0); // 208, 156, 255 Light Purple

            Tracking = new List<MenuRule>();
            
            // Bazooka Rotas (Outdated -> Removed)
            // Tracking.Add(new MenuRule()
            // {
		// Index = 0,
		// Menu = () => "Bazooka Rotas",
                // IsRelevant = () => (EventHistory.Where(d => d.EventType == 3 && d.Progress < 100).Count() > 0), 
                // Description = () => { 
		// 		int archonrota = EventHistory.Where(d => d.EventType == 3 && d.Progress < 100).Count();
		// 		return archonrota.ToString();
		// 	},
              //  Font = RiftFont,
           // });

            // Pylons Discovered
            Tracking.Add(new MenuRule()
            {
		Index = 1,
		Menu = () => "Pylons Discovered",
                IsRelevant = () => (Hud.Game.Me.IsInGame), // (EventHistory.Where(d => (d.EventType == 2 || d.EventType == 5)).Count() > 0)
                Description = () => { 
				int pylons = MapHistory.Where(d => d.EventType == 2).Count();
				return pylons.ToString();
			},
                Font = TealFont,
            });
            // GR Tracker
            Tracking.Add(new MenuRule()
            {
		Index = 2,
		Menu = () => GRTier != null ? "GR" + GRTier : "No GR Started",
                IsRelevant = () => (Hud.Game.Me.IsInGame), 
                Description = () => GRPlayers > 1 ? GRPlayers.ToString() + "P" : "Solo",
                Font = RiftFont,
            });

            // Create all the players hover labels based on number of players in game
            ExpandUpLabels = new List<TopLabelDecorator>();
            foreach (MenuRule rule in Tracking)
            {
                TopLabelDecorator label = new TopLabelDecorator(Hud) {
                    TextFont = (rule.Font == null ? RiftFont : rule.Font),
                    BackgroundBrush = (rule.BgBrush == null ? BgBrush : rule.BgBrush),
                    ExpandedHintFont = (rule.Font == null ? RiftFont : rule.Font),
                    ExpandedHintWidthMultiplier = (float)((Hud.Window.Size.Width / 1000)* 1.0),
                    TextFunc = rule.Description,
                    HintFunc = rule.Menu,
                };
                
                rule.Label = label;
                ExpandUpLabels.Add(label);
            }

                    TopLabelDecorator labelhistory = new TopLabelDecorator(Hud) {
			TextFont = RiftFont,
			TextFunc = () => {
			float w = plugin.WidthFunc();
			float h = plugin.HeightFunc();
			float x = Hook == 0 ? (plugin.PinnedLabel == Label ? plugin.ExpandLabelHook : plugin.SelectedRectangle.X) : (plugin.PinnedTopLabel == Label ? plugin.ExpandLabelTopHook : plugin.SelectedTopRectangle.X);
			float y = Hook == 0 ? (plugin.SelectedRectangle.Y - h * (ExpandUpLabels.Count - 0.5f)) : (plugin.SelectedTopRectangle.Y + h * (ExpandUpLabels.Count - 0.5f));
						
			DrawRiftHistory(new RectangleF(x, y, w, h));  
			return " ";
		    }
		};

                ExpandUpLabels.Add(labelhistory);

            // Create the main menu label
            Label = new TopLabelDecorator(Hud)
            {
                TextFont = TealFont,
                TextFunc = () => { int archonrota = EventHistory.Where(d => d.EventType == 3 && d.Progress < 100).Count(); return archonrota != 0  ? (GRProgress / archonrota).ToString("F2", CultureInfo.InvariantCulture) + "%" : (GRProgress == 100 || GRProgress == 0) ? GRProgress.ToString("F0", CultureInfo.InvariantCulture) + "%" : GRProgress.ToString("F2", CultureInfo.InvariantCulture) + "%"; },
                HintFunc = () => "Rift Progression",
                ExpandUpLabels = this.ExpandUpLabels,
            };



            MapMarkerPylon = new WorldDecoratorCollection(
                new MapShapeDecorator(Hud)
                {
                    ShapePainter = new RectangleShapePainter(Hud),
                    Brush = Hud.Render.CreateBrush(225, 215, 110, 215, 3),
                    ShadowBrush = Hud.Render.CreateBrush(96, 0, 0, 0, 1),
                    Radius = 4f,
                  //RadiusTransformator = new StandardPingRadiusTransformator(Hud, 333),
                },
                new MapLabelDecorator(Hud)
                {
                    LabelFont = Hud.Render.CreateFont("tahoma", 6f, 255, 215, 110, 215, true, false, 128, 0, 0, 0, true),
                    Up = true,
                    //RadiusOffset = 6.0f,
                }
            );

            MapMarkerPylonUsed = new WorldDecoratorCollection(
                new MapShapeDecorator(Hud)
                {
                    ShapePainter = new RectangleShapePainter(Hud),
                    Brush = Hud.Render.CreateBrush(225, 200, 200, 200, 3),
                    ShadowBrush = Hud.Render.CreateBrush(96, 0, 0, 0, 1),
                    Radius = 4f,
                  //RadiusTransformator = new StandardPingRadiusTransformator(Hud, 333),
                },
                new MapLabelDecorator(Hud)
                {
                    LabelFont = Hud.Render.CreateFont("tahoma", 6f, 255, 200, 200, 200, true, false, 128, 0, 0, 0, true),
                    Up = true,
                    //RadiusOffset = 6.0f,
                }
            );

            MapMarkerArchon = new WorldDecoratorCollection(
                new MapShapeDecorator(Hud)
                {
                    ShapePainter = new TriangleShapePainter(Hud),
                    Brush = Hud.Render.CreateBrush(240, 186, 14, 137, 3),
                    ShadowBrush = Hud.Render.CreateBrush(96, 0, 0, 0, 1),
                    Radius = 8f,
                  //RadiusTransformator = new StandardPingRadiusTransformator(Hud, 333),
                },
                new MapLabelDecorator(Hud)
                {
                    LabelFont = Hud.Render.CreateFont("tahoma", 6f, 255, 186, 14, 137, true, false, 128, 0, 0, 0, true),
                  //Up = true,
                  //RadiusOffset = 6.0f,
                }
            );

            MapMarkerDead = new WorldDecoratorCollection(
                new MapShapeDecorator(Hud)
                {
                    ShapePainter = new CrossShapePainter(Hud),
                    Brush = Hud.Render.CreateBrush(240, 180, 0, 0, 3),
                    ShadowBrush = Hud.Render.CreateBrush(96, 0, 0, 0, 1),
                    Radius = 8f,
                  //RadiusTransformator = new StandardPingRadiusTransformator(Hud, 333),
                },
                new MapLabelDecorator(Hud)
                {
                    LabelFont = Hud.Render.CreateFont("tahoma", 6f, 255, 180, 0, 0, true, false, 128, 0, 0, 0, true),
                    Up = true,
                    RadiusOffset = 7.0f,
                }
            );
	}

	public void OnNewArea(bool newGame, ISnoArea area)
	{
	    if (newGame) 
	    {
		InRiftLastAreaSno = 0;
	    }

	    if ((area.HostAreaSno == 288482) || (area.Sno == 288482))
	    {
		if (InRiftLastAreaSno != area.Sno)
		{ 
		    if (InRiftLastAreaSno == 0 && Hud.Game.Me.InGreaterRiftRank != 0) // Initialize a new Rift (Nephalem or GR)
		    {
			TBoss = -1; TBossKilled = -1; TBossSpawned = -1; NameBoss = null;
			Progress20 = false; Progress40 = false; Progress60 = false; Progress80 = false;
			GRProgress = 0; GRPlayers = Hud.Game.NumberOfPlayersInGame;
			MapHistory.Clear(); EventHistory.Clear();
			GRTier = Hud.Game.Me.InGreaterRiftRank.ToString("");
		    }
		    InRiftLastAreaSno = area.Sno;
		}
	    }
 	}

        public void PaintWorld(WorldLayer layer)
        {

	if (Hud.Game.Me.InGreaterRiftRank == 0) return;

	    // Pylons
            foreach (MapSnapshot pylon in MapHistory.Where(p => p.WorldId == Hud.Game.Me.WorldId && p.EventType == 2 && p.Area == Hud.Game.Me.SnoArea.Sno && !p.Taken && p.FloorCoordinate != null && p.FloorCoordinate.XYDistanceTo(Hud.Game.Me.FloorCoordinate) > 160))
                MapMarkerPylon.Paint(layer, null, pylon.FloorCoordinate, pylon.Name);
            foreach (MapSnapshot pylon in MapHistory.Where(p => p.WorldId == Hud.Game.Me.WorldId && p.EventType == 2 && p.Area == Hud.Game.Me.SnoArea.Sno && p.Taken && p.FloorCoordinate != null && p.FloorCoordinate.XYDistanceTo(Hud.Game.Me.FloorCoordinate) > 160))
                MapMarkerPylonUsed.Paint(layer, null, pylon.FloorCoordinate, pylon.Name);

	    // Archon Rotations
            foreach (MapSnapshot archon in MapHistory.Where(p => p.WorldId == Hud.Game.Me.WorldId && p.EventType == 3 && p.Area == Hud.Game.Me.SnoArea.Sno && p.FloorCoordinate != null && p.FloorCoordinate.XYDistanceTo(Hud.Game.Me.FloorCoordinate) > 100))
                MapMarkerArchon.Paint(layer, null, archon.FloorCoordinate, archon.Name);

	    // Deaths
            foreach (MapSnapshot dead in MapHistory.Where(p => p.WorldId == Hud.Game.Me.WorldId && p.EventType == 4 && p.Area == Hud.Game.Me.SnoArea.Sno && p.FloorCoordinate != null && p.FloorCoordinate.XYDistanceTo(Hud.Game.Me.FloorCoordinate) > 100))
                MapMarkerDead.Paint(layer, null, dead.FloorCoordinate, dead.Name + " Death");
        }
		
	public string FindNameBoss()
	{
	    var b = Hud.Game.AliveMonsters.FirstOrDefault(s => (s.Rarity == ActorRarity.Boss) && (s.SummonerAcdDynamicId == 0));
	    if (b != null)
	    {
		return b.SnoMonster.NameLocalized; 
	    }
	    else
	    {
		var m = Hud.Game.Markers.FirstOrDefault(k => (k.SnoActor != null) && k.SnoActor.Code.Contains("_Boss_"));
		if (m != null)
		{ 
		    return m.Name; 
		}
	    }
	    return null;
	}
		
	public void AfterCollect()  
	{
	    if (!Hud.Game.IsInGame) return;

            bool isAnyRelevant = false;
            int alternate = 0;
            foreach (MenuRule rule in Tracking)
            {
                // Show or hide labels based on whether or not the number of players in the game is relevant
                if (!rule.IsRelevant())
                {
                    rule.Label.Enabled = false;
                    continue;
                }
                rule.Label.Enabled = true;
                rule.Label.BackgroundBrush = (alternate++ % 2 == 0 ? BgBrush : BgBrushAlt);

                isAnyRelevant = true;
            }
            Label.Enabled = isAnyRelevant;

	    if (InRiftLastAreaSno != 0) 
	    {
		var riftQuest = Hud.Game.Quests.FirstOrDefault(q => (q.SnoQuest.Sno == 337492) && (q.State == QuestState.started || q.State == QuestState.completed));
		if (riftQuest != null)
		{

	TEvent = (Hud.Game.CurrentGameTick - riftQuest.CreatedOn) + Hud.Game.CurrentTimedEventEndTickMod;
	TBossEvent = riftQuest.CreatedOn - Hud.Game.CurrentTimedEventStartTick + Hud.Game.CurrentTimedEventEndTickMod;
		if (Hud.Game.Me.InGreaterRiftRank != 0 && GRProgress != Hud.Game.RiftPercentage) GRProgress = Hud.Game.RiftPercentage;

	// Wizard Archon Use
	    var wizards = Hud.Game.Players.Where(p => p.HeroClassDefinition.HeroClass == HeroClass.Wizard);
            foreach (var player in wizards)
            {
		if (Hud.Game.Me.InGreaterRiftRank == 0) return;

		var Rotation = false;
		var archonBuff = player.Powers.BuffIsActive(Hud.Sno.SnoPowers.Wizard_Archon.Sno, 2);
		var actor = Hud.Game.Actors.FirstOrDefault(a => a.SnoActor.Sno == ActorSnoEnum._wizard_meteor_pending_cost);

		if (player.HasValidActor && archonBuff)
		{

		// Bazooka Rotation
		if (player.Powers.UsedSkills.Where(x => x.SnoPower.Sno == 69190 && x.Rune == 3).FirstOrDefault() != null) {
			if (actor != null && Rotation == false && !MapHistory.Any(p => (Hud.Time.Now - p.Timestamp).TotalSeconds <= 10 && p.WorldId.Equals(Hud.Game.Me.WorldId) && p.EventType == 3)) {
		    var archonRotationMap = new MapSnapshot() { Name = player.BattleTagAbovePortrait + " Archon", EventType = 3, Timestamp = Hud.Time.Now, WorldId = Hud.Game.Me.WorldId, FloorCoordinate = actor.FloorCoordinate, Area = Hud.Game.Me.SnoArea.Sno };
		    MapHistory.Add(archonRotationMap);

		    var archonRotation = new EventSnapshot() { EventName = player.BattleTagAbovePortrait + " Archon", EventType = 3, Timestamp = Hud.Time.Now, Progress = Hud.Game.RiftPercentage, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(archonRotation);
			Rotation = true;
			}
		}
		// Archon Use
		else
		{
		    if (Rotation == false && !EventHistory.Any(p => (Hud.Time.Now - p.Timestamp).TotalSeconds <= 21 && p.EventName == player.BattleTagAbovePortrait + " Archon" && p.EventType == 3)) {
		    var archonRotation = new EventSnapshot() { EventName = player.BattleTagAbovePortrait + " Archon", EventType = 3, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(archonRotation);
			Rotation = true;
		    }
		}

		}

		if (player.HasValidActor && Rotation == true && !archonBuff)
		{ Rotation = false; }

	    }

	// Necromancer CD Use
	    var necromancers = Hud.Game.Players.Where(p => p.HeroClassDefinition.HeroClass == HeroClass.Necromancer);
            foreach (var player in necromancers)
            {
		if (Hud.Game.Me.InGreaterRiftRank == 0) return;

		var lotdBuff = player.Powers.GetBuff(Hud.Sno.SnoPowers.Necromancer_LandOfTheDead.Sno);
		var simBuff = player.Powers.GetBuff(Hud.Sno.SnoPowers.Necromancer_Simulacrum.Sno);

		// LotD Use
		if (player.HasValidActor && lotdBuff?.Active == true)
		{
		int LotDTimer = player.Powers.UsedSkills.Where(x => x.SnoPower.Sno == 465839 && x.Rune == 4).FirstOrDefault() != null ? 13 : 11; // Shallow Graves
		    if (!EventHistory.Any(p => (Hud.Time.Now - p.Timestamp).TotalSeconds <= LotDTimer && p.EventName == player.BattleTagAbovePortrait + " Land" && p.EventType == 6)) {
			var LotD = new EventSnapshot() { EventName = player.BattleTagAbovePortrait + " Land", EventType = 6, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
			EventHistory.Add(LotD);
		    }
		}

		// Simulacrum Use
		if (player.HasValidActor && simBuff?.Active == true && !player.Powers.BuffIsActive(484309, 0))
		{
		int SimTimer = player.Powers.BuffIsActive(476590, 0) ? 31 : 16; // Legacy Haunted Visions
		    if (!EventHistory.Any(p => (Hud.Time.Now - p.Timestamp).TotalSeconds <= SimTimer && p.EventName == player.BattleTagAbovePortrait + " Simulacrum" && p.EventType == 7)) {
			var Sim = new EventSnapshot() { EventName = player.BattleTagAbovePortrait + " Simulacrum", EventType = 7, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
			EventHistory.Add(Sim);
		    }
		}

		// Simulacrum Use w/ Haunted Visions

		if (player.HasValidActor && !player.Powers.BuffIsActive(465350, 1) && HauntedVisions)
		{
			HauntedVisions = false;
		}

		if (player.HasValidActor && simBuff?.Active == true && player.Powers.BuffIsActive(484309, 0) && !HauntedVisions)
		{
		    //if (!EventHistory.Any(p => p.EventName == player.BattleTagAbovePortrait + " Simulacrum" && p.EventType == 7)) {
			var Sim = new EventSnapshot() { EventName = player.BattleTagAbovePortrait + " Simulacrum", EventType = 7, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
			EventHistory.Add(Sim);
 			HauntedVisions = true;
		   // }

		}


	    }

	// Deaths
            var HeadStones = Hud.Game.HeadStones;
            foreach (var HeadStone in HeadStones)
            {
		if (Hud.Game.Me.InGreaterRiftRank == 0) return;

		if (HeadStone != null && !MapHistory.Any(h => h.WorldId.Equals(HeadStone.Player.WorldId) && h.EventType == 4 && h.FloorCoordinate.Equals(HeadStone.FloorCoordinate) && h.Name.Equals(HeadStone.Player.BattleTagAbovePortrait)))
		{
		var deadPlayerMap = new MapSnapshot() { Name = HeadStone.Player.BattleTagAbovePortrait, EventType = 4, Timestamp = Hud.Time.Now, WorldId = HeadStone.Player.WorldId, FloorCoordinate = HeadStone.FloorCoordinate, Area = HeadStone.Player.SnoArea.Sno };
		MapHistory.Add(deadPlayerMap);

		var deadPlayer = new EventSnapshot() { EventName = HeadStone.Player.BattleTagAbovePortrait, EventType = 4, Timestamp = Hud.Time.Now, Area = HeadStone.Player.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		EventHistory.Add(deadPlayer);
		}
            }

	// Pylons
	    var shrines = Hud.Game.Shrines.Where(s => s.FloorCoordinate.IsValid); 
	    foreach (var shrine in shrines) {

		if (Hud.Game.Me.InGreaterRiftRank == 0) return;

		if (!MapHistory.Any(s => s.WorldId.Equals(shrine.WorldId) && s.Name.Equals(shrine.SnoActor.NameLocalized))) {
		    var pylonFoundMap = new MapSnapshot() { Name = shrine.SnoActor.NameLocalized, EventType = 2, Timestamp = Hud.Time.Now, WorldId = Hud.Game.Me.WorldId, FloorCoordinate = shrine.FloorCoordinate, Area = Hud.Game.Me.SnoArea.Sno, Taken = (shrine.IsDisabled || shrine.IsOperated) ? true : false };
		    MapHistory.Add(pylonFoundMap);

		    var pylonFound = new EventSnapshot() { EventName = shrine.SnoActor.NameLocalized, EventType = 2, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Progress = Hud.Game.RiftPercentage, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonFound);
		}

		// To counter slow SnoArea Update (Pylon near Floor Entrance)
		if (EventHistory.Any(s => (Hud.Time.Now - s.Timestamp).TotalSeconds < 5 && s.EventType == 2 && s.EventName.Equals(shrine.SnoActor.NameLocalized) && !s.Area.Equals(Hud.Game.Me.SnoArea.Sno))) {

		    EventHistory.RemoveAll(s => !s.Area.Equals(Hud.Game.Me.SnoArea.Sno) && s.EventName.Equals(shrine.SnoActor.NameLocalized));
		    MapHistory.RemoveAll(s => !s.Area.Equals(Hud.Game.Me.SnoArea.Sno) && s.Name.Equals(shrine.SnoActor.NameLocalized));

		    var pylonFoundMap = new MapSnapshot() { Name = shrine.SnoActor.NameLocalized, EventType = 2, Timestamp = Hud.Time.Now, WorldId = Hud.Game.Me.WorldId, FloorCoordinate = shrine.FloorCoordinate, Area = Hud.Game.Me.SnoArea.Sno, Taken = (shrine.IsDisabled || shrine.IsOperated) ? true : false };
		    MapHistory.Add(pylonFoundMap);

		    var pylonFound = new EventSnapshot() { EventName = shrine.SnoActor.NameLocalized, EventType = 2, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Progress = Hud.Game.RiftPercentage, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonFound); }

		if ((shrine.IsDisabled || shrine.IsOperated) && MapHistory.Any(s => s.Taken == false && s.Name.Equals(shrine.SnoActor.NameLocalized) && s.Area.Equals(Hud.Game.Me.SnoArea.Sno) && s.WorldId.Equals(shrine.WorldId) && s.FloorCoordinate.Equals(shrine.FloorCoordinate))) {
		    MapHistory.RemoveAll(s => s.Taken == false && s.WorldId.Equals(shrine.WorldId) && s.Area.Equals(Hud.Game.Me.SnoArea.Sno) && s.Name.Equals(shrine.SnoActor.NameLocalized));
		    var pylonFoundUsed = new MapSnapshot() { Name = shrine.SnoActor.NameLocalized, EventType = 2, Timestamp = Hud.Time.Now, WorldId = Hud.Game.Me.WorldId, FloorCoordinate = shrine.FloorCoordinate, Area = Hud.Game.Me.SnoArea.Sno, Taken = (shrine.IsDisabled || shrine.IsOperated) ? true : false };
		    MapHistory.Add(pylonFoundUsed); }

	    }


	if (Hud.Game.Me.InGreaterRiftRank != 0) {

	// Player Procs
	foreach (IPlayer player in Hud.Game.Players.Where(p => p.HasValidActor && p.SnoArea == Hud.Game.Me.SnoArea && p.CoordinateKnown))
	{
		ProcRuleCalculator.CalculatePaintInfo(player);

		if (ProcRuleCalculator.PaintInfoList.Count > 0) {
			if (player.PortraitIndex == 0 && !ProcP1) {
		    var playerProc = new EventSnapshot() { EventName = player.BattleTagAbovePortrait, EventType = 13, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(playerProc); ProcP1 = true; }

			if (player.PortraitIndex == 1 && !ProcP2) {
		    var playerProc = new EventSnapshot() { EventName = player.BattleTagAbovePortrait, EventType = 13, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(playerProc); ProcP2 = true; }

			if (player.PortraitIndex == 2 && !ProcP3) {
		    var playerProc = new EventSnapshot() { EventName = player.BattleTagAbovePortrait, EventType = 13, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(playerProc); ProcP3 = true; }

			if (player.PortraitIndex == 3 && !ProcP4) {
		    var playerProc = new EventSnapshot() { EventName = player.BattleTagAbovePortrait, EventType = 13, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(playerProc); ProcP4 = true; }
		}
		else
		{
			if (player.PortraitIndex == 0 && ProcP1) { ProcP1 = false; }
			if (player.PortraitIndex == 1 && ProcP2) { ProcP2 = false; }
			if (player.PortraitIndex == 2 && ProcP3) { ProcP3 = false; }
			if (player.PortraitIndex == 3 && ProcP4) { ProcP4 = false; }
		}
	}

	// Pylons Used

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(262935, 0)) && !EventHistory.Any(s => s.EventName.Equals("Power Pylon") && s.EventType == 5))
		{ var pylonUsed = new EventSnapshot() { EventName = "Power Pylon", EventType = 5, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(403404, 0)) && !EventHistory.Any(s => s.EventName.Equals("Conduit Pylon") && s.EventType == 5))
		{ var pylonUsed = new EventSnapshot() { EventName = "Conduit Pylon", EventType = 5, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(266258, 0)) && !EventHistory.Any(s => s.EventName.Equals("Channeling Pylon") && s.EventType == 5))
		{ var pylonUsed = new EventSnapshot() { EventName = "Channeling Pylon", EventType = 5, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(266254, 0)) && !EventHistory.Any(s => s.EventName.Equals("Shield Pylon") && s.EventType == 5))
		{ var pylonUsed = new EventSnapshot() { EventName = "Shield Pylon", EventType = 5, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(266271, 0)) && !EventHistory.Any(s => s.EventName.Equals("Speed Pylon") && s.EventType == 5))
		{ var pylonUsed = new EventSnapshot() { EventName = "Speed Pylon", EventType = 5, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); }

	// S19 Pandemonium Killstreak

	if (!Streak100 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 100) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 100 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 100 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak100 = true; }

	if (!Streak150 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 150) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 150 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 150 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak150 = true; }

	if (!Streak200 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 200) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 200 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 200 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak200 = true; }

	if (!Streak400 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 400) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 400 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 400 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak400 = true; }

	if (!Streak500 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 500) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 500 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 500 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak500 = true; }

	if (!Streak1000 && Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1000) && !EventHistory.Any(s => s.EventName.Equals("Pandemonium 1000 Killstreak") && (Hud.Time.Now - s.Timestamp).TotalSeconds <= 10 && s.EventType == 8))
		{ var pylonUsed = new EventSnapshot() { EventName = "Pandemonium 1000 Killstreak", EventType = 8, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(pylonUsed); Streak1000 = true; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 100))
		{ Streak100 = false; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 150))
		{ Streak150 = false; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 200))
		{ Streak200 = false; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 400))
		{ Streak400 = false; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 500))
		{ Streak500 = false; }

	if (Hud.Game.Players.Any(p => p.HasValidActor && p.Powers.BuffIsActive(483967, 1) && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) >= 1 && p.GetAttributeValue(Hud.Sno.Attributes.Buff_Icon_Count1, 483967) < 1000))
		{ Streak1000 = false; }


	// Progress Treshold

	if (!Progress20 && Hud.Game.RiftPercentage >= 20 && Hud.Game.RiftPercentage < 40 && !EventHistory.Any(s => s.EventName.Equals("Rift Progress") && s.EventType == 9))
		{ var progress = new EventSnapshot() { EventName = "Rift Progress", EventType = 9, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Progress = Hud.Game.RiftPercentage, Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(progress); Progress20 = true; }

	if (!Progress40 && Hud.Game.RiftPercentage >= 40 && Hud.Game.RiftPercentage < 60 && !EventHistory.Any(s => s.EventName.Equals("Rift Progress") && s.EventType == 10))
		{ var progress = new EventSnapshot() { EventName = "Rift Progress", EventType = 10, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Progress = Hud.Game.RiftPercentage, Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(progress); Progress40 = true; }

	if (!Progress60 && Hud.Game.RiftPercentage >= 60 && Hud.Game.RiftPercentage < 80 && !EventHistory.Any(s => s.EventName.Equals("Rift Progress") && s.EventType == 11))
		{ var progress = new EventSnapshot() { EventName = "Rift Progress", EventType = 11, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Progress = Hud.Game.RiftPercentage, Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(progress); Progress60 = true; }

	if (!Progress80 && Hud.Game.RiftPercentage >= 80 && !EventHistory.Any(s => s.EventName.Equals("Rift Progress") && s.EventType == 12))
		{ var progress = new EventSnapshot() { EventName = "Rift Progress", EventType = 12, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Progress = Hud.Game.RiftPercentage, Timer = Hud.Game.RiftPercentage >= 100 ? ValueToString((long)(TBossEvent + TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) : ValueToString((long)(TEvent) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
		    EventHistory.Add(progress); Progress80 = true; }
	}


	// Boss Events
		    switch (riftQuest.QuestStepId)
		    {

			case 16: // GR Boss Appears 
				if (TBossSpawned == -1) 
				{
				    TBossSpawned = riftQuest.CreatedOn - Hud.Game.CurrentTimedEventStartTick + Hud.Game.CurrentTimedEventEndTickMod;
				    if (NameBoss == null) { NameBoss = FindNameBoss(); }

				    var bossSpawn = new EventSnapshot() { EventName = NameBoss ?? "Boss", EventType = 0, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), Timer = ValueToString((long)(TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
				    EventHistory.Add(bossSpawn);

				    Hud.TextLog.Log("log_riftguardians", (NameBoss ?? "Boss") + " spawned in GR" + Hud.Game.Me.InGreaterRiftRank.ToString("") + (GRPlayers > 1 ? " " + GRPlayers.ToString() + "P" : " Solo") + " in [" + ValueToString((long)(TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) + "]" + (EventHistory.Where(d => d.EventType == 3).Count() > 0 ? " with " + EventHistory.Where(d => d.EventType == 3).Count().ToString() + " rotations" : ""), true, true);

				}
				break;
			case 34: // GR Boss Killed
				if (TBossKilled == -1) 
				{
				    TBossKilled = riftQuest.CreatedOn - Hud.Game.CurrentTimedEventStartTick + Hud.Game.CurrentTimedEventEndTickMod;
				    if (NameBoss == null) { NameBoss = FindNameBoss(); }

				    var bossKill = new EventSnapshot() { EventName = NameBoss ?? "Boss", EventType = 1, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), TimerKill = ValueToString((long)(TBossKilled - TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime), Timer = ValueToString((long)(TBossKilled) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
				    EventHistory.Add(bossKill);

				    Hud.TextLog.Log("log_riftguardians", (NameBoss ?? "Boss") + " killed in [" + ValueToString((long)(TBossKilled - TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) + "] - GR" + Hud.Game.Me.InGreaterRiftRank.ToString("") + (GRPlayers > 1 ? " " + GRPlayers.ToString() + "P" : " Solo") + " cleared in [" + ValueToString((long)(TBossKilled) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) + "]", true, true);

				}
				break;
			case 46: // GR Boss Killed out of timer
				if (TBossKilled == -1) 
				{
				    TBossKilled = riftQuest.CreatedOn - Hud.Game.CurrentTimedEventStartTick + Hud.Game.CurrentTimedEventEndTickMod;
				    if (NameBoss == null) { NameBoss = FindNameBoss(); }

				    var bossKill = new EventSnapshot() { EventName = NameBoss ?? "Boss", EventType = 1, Timestamp = Hud.Time.Now, Area = Hud.Game.Me.SnoArea.Sno, Difficulty = Hud.Game.Me.InGreaterRiftRank.ToString(""), TimerKill = ValueToString((long)(TBossKilled - TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime), Timer = ValueToString((long)(TBossKilled) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) };
				    EventHistory.Add(bossKill);

				    Hud.TextLog.Log("log_riftguardians", (NameBoss ?? "Boss") + " killed in [" + ValueToString((long)(TBossKilled - TBossSpawned) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) + "] - GR" + Hud.Game.Me.InGreaterRiftRank.ToString("") + (GRPlayers > 1 ? " " + GRPlayers.ToString() + "P" : " Solo") + " cleared in [" + ValueToString((long)(TBossKilled) * 1000 * TimeSpan.TicksPerMillisecond / 60, ValueFormat.LongTime) + "]", true, true);

				}
				break;
			}

		    }
		    else 
		    {
			InRiftLastAreaSno = 0; 
		    }
		}

	}
		
	private void DrawRiftHistory(RectangleF rect) //x,y coordinates of the bottom left of the rift history window (above the other labels + space gap)
	{
		if (EventHistory == null || EventHistory.Count == 0) return;

            if (Hud.Input.IsKeyDown(Keys.Up)) RiftLogShown = 40; // When the key Up is held down, the Rift History shows 40 lines (Instead of 20 by Default)
	    if (!Hud.Input.IsKeyDown(Keys.Up)) RiftLogShown = RiftHistoryShown;

		DateTime now = Hud.Time.Now;
		IEnumerable<EventSnapshot> history = (RiftHistoryTimeout > 0 ?
			EventHistory.Where(d => (now - d.Timestamp).TotalSeconds < RiftHistoryTimeout) :
			EventHistory
		).OrderByDescending(d => d.Timestamp).Take(RiftLogShown);
		if (history.Count() == 0) return; // Safecheck
		List<Tuple<TextLayout, TextLayout, IFont, IBrush>> lines = new List<Tuple<TextLayout, TextLayout, IFont, IBrush>>(RiftHistoryShown + 1);
			
		foreach (EventSnapshot rift in history)
		{
                	// How long ago was the event
                	TimeSpan elapsed = now - rift.Timestamp;

			// Rift Timer
			string time;
				time = rift.Timer;
			// Rift Level
			string area;
			area = Lnn[Lnn.ContainsKey(rift.Area) ? rift.Area : 0];

			// Event Type
			string textevent;
                	IFont color = RiftFont;

			if (rift.EventType == 0) { textevent = " spawned"; color = RiftFont; }
			else if (rift.EventType == 1) { textevent = " killed"; color = BossFont; }
			else if (rift.EventType == 2) { textevent = " found"; color = GreyFont; }
			else if (rift.EventType == 3) { textevent = " done"; color = WizFont; }
			else if (rift.EventType == 4) { textevent = " died"; color = DeadFont; }
			else if (rift.EventType == 5) { textevent = " used"; color = YellowFont; }
			else if (rift.EventType == 6 || rift.EventType == 7) { textevent = " used"; color = NecroFont; }
			else if (rift.EventType == 8) { textevent = " reached"; color = OrangeFont; }
			else if (rift.EventType == 9 || rift.EventType == 10 || rift.EventType == 11 || rift.EventType == 12) { textevent = " reached"; color = OrangeFont; }
			else if (rift.EventType == 13) { textevent = " is under proc"; color = DeadFont; }
			else { textevent = ""; color = RiftFont; }

			// BossKill Timer & Pylon Progress
			string textrift = string.Empty;
			if (rift.TimerKill != null) textrift = " [" + rift.TimerKill + "]";
			if (rift.EventType == 2 || rift.EventType == 9 || rift.EventType == 10 || rift.EventType == 11 || rift.EventType == 12) textrift = " [" + rift.Progress.ToString("F0") + "%]";

			// Store line data
			lines.Add(new Tuple<TextLayout, TextLayout, IFont, IBrush>(
				TimeFont.GetTextLayout(time + "  " + area),
				color.GetTextLayout(rift.EventName + textevent + textrift),
				color,
				(elapsed.TotalSeconds <= RiftHistoryHighlight ? HighlightBrush : BgBrush)
			));
		}
			
		// Compute width of the loot history display
		float widthTime = lines.Select(t => t.Item1.Metrics.Width).Max();
		float widthInfos = lines.Select(t => t.Item2.Metrics.Width).Max();
		float width = Math.Max(rect.Width, 5 + widthTime + 10 + widthInfos + 5);			
			
		//x += width;
		float x = rect.X;
		if (x + width > Hud.Window.Size.Width)
			x = Hud.Window.Size.Width - width;
		float y = rect.Y;
			
		foreach (Tuple<TextLayout, TextLayout, IFont, IBrush> line in lines)
		{
			TextLayout layout = line.Item1;
			float height = Math.Max(layout.Metrics.Height, rect.Height);
			if (Hook == 0) y -= height;
				else y += height;
				
			//draw background
			line.Item4.DrawRectangle(x, y, width, height); 
				
			//draw timestamp
			TimeFont.DrawText(layout, x + 5 + widthTime - layout.Metrics.Width, y + height*0.5f - layout.Metrics.Height*0.5f); 

			//draw rift infos
			layout = line.Item2;
			line.Item3.DrawText(layout, x + 5 + widthTime + 5, y + height*0.5f - layout.Metrics.Height*0.5f);
		}
	}

    }
}