コード例 #1
0
ファイル: WipeDetector.cs プロジェクト: xarfger/cactbot
        public void OnPlayerChanged(JSEvents.PlayerChangedEvent player)
        {
            if (player_name_ == null)
            {
                player_name_             = player.name;
                player_name_with_colons_ = ":" + player.name + ":";
                weakness_strs_[0]        = " 1A:" + player_name_ + " gains the effect of Weakness from";
                weakness_strs_[1]        = " 1A:" + player_name_ + " gains the effect of Brink Of Death from";
                weakness_strs_[2]        = " 1A:" + player_name_ + " gains the effect of Affaiblissement from";
                weakness_strs_[3]        = " 1A:" + player_name_ + " gains the effect of Mourant from";
                weakness_strs_[4]        = " 1A:" + player_name_ + " gains the effect of Schwäche from";
                weakness_strs_[5]        = " 1A:" + player_name_ + " gains the effect of Sterbenselend from";
                weakness_strs_[6]        = " 1A:" + player_name_ + " gains the effect of 衰弱 from";
                weakness_strs_[7]        = " 1A:" + player_name_ + " gains the effect of 衰弱[強] from";
            }

            var now = DateTime.Now;

            // Note: can't use "You were revived" from log, as it doesn't happen for
            // fights that auto-restart when everybody is defeated.
            if (!player_dead_ && player.currentHP == 0)
            {
                player_dead_   = true;
                last_lb3_time_ = null;
            }
            else if (player_dead_ && player.currentHP > 0)
            {
                player_dead_       = false;
                last_revived_time_ = now;

                // If an LB3 hit the player recently, then it wasn't a wipe so just
                // forget that they were revived.
                if (last_lb3_time_.HasValue && (now - last_lb3_time_.Value).TotalSeconds <= kLogWaitSeconds)
                {
                    last_revived_time_ = null;
                }
            }

            // Heuristic: if a player is revived and a weakness message doesn't happen
            // soon after, then it was a wipe.
            if (last_revived_time_.HasValue && (now - last_revived_time_.Value).TotalSeconds > kLogWaitSeconds)
            {
                WipeIt();
            }
        }
コード例 #2
0
ファイル: WipeDetector.cs プロジェクト: knall/cactbot
        public void OnPlayerChanged(JSEvents.PlayerChangedEvent player)
        {
            if (player_name_ == null)
            {
                player_name_             = player.name;
                player_name_with_colons_ = ":" + player.name + ":";
            }

            var now = DateTime.Now;

            // Note: can't use "You were revived" from log, as it doesn't happen for
            // fights that auto-restart when everybody is defeated.
            if (!player_dead_ && player.currentHP == 0)
            {
                player_dead_   = true;
                last_lb3_time_ = null;
            }
            else if (player_dead_ && player.currentHP > 0)
            {
                player_dead_       = false;
                last_revived_time_ = now;

                // If an LB3 hit the player recently, then it wasn't a wipe so just
                // forget that they were revived.
                if (last_lb3_time_.HasValue && (now - last_lb3_time_.Value).TotalSeconds <= kLogWaitSeconds)
                {
                    last_revived_time_ = null;
                }
            }

            // Heuristic: if a player is revived and a weakness message doesn't happen
            // soon after, then it was a wipe.
            if (last_revived_time_.HasValue && (now - last_revived_time_.Value).TotalSeconds > kLogWaitSeconds)
            {
                WipeIt();
            }
        }
コード例 #3
0
        // Events that we want to update as soon as possible.  Return next time this should be called.
        private int SendFastRateEvents()
        {
            // Handle startup and shutdown. And do not fire any events until the page has loaded and had a chance to
            // register its event handlers.
            if (Overlay == null || Overlay.Renderer == null || Overlay.Renderer.Browser == null || Overlay.Renderer.Browser.IsLoading)
            {
                return(kSlowTimerMilli);
            }

            if (reset_notify_state_)
            {
                notify_state_ = new NotifyState();
            }
            reset_notify_state_ = false;

            // Loading dance:
            // * wait for !CefBrowser::IsLoading.
            // * Execute JS in all overlays to wait for DOMContentReady and send back an event.
            // * When this OverlayMessage comes in, send back an onInitializeOverlay message.
            // * Overlays should load options from the provider user data dir in that message.
            // * As DOMContentReady happened, overlays should also construct themselves and attach to the DOM.
            // * Now, messages can be sent freely and everything is initialized.
            if (!notify_state_.added_dom_content_listener)
            {
                // Send this from C# so that overlays that are using non-cactbot html
                // (e.g. dps overlays) don't have to be modified.
                const string waitForDOMContentReady = @"
          (function() {
            var sendDOMContentLoaded = function() {
              if (!window.OverlayPluginApi) {
                window.setTimeout(sendDOMContentLoaded, 100);
              } else {
                window.OverlayPluginApi.overlayMessage(OverlayPluginApi.overlayName, JSON.stringify({'onDOMContentLoaded': true}));
              }
            };
            if (document.readyState == 'loaded' || document.readyState == 'complete') {
              sendDOMContentLoaded();
            } else {
              document.addEventListener('DOMContentLoaded', sendDOMContentLoaded);
            }
          })();
        ";
                this.Overlay.Renderer.ExecuteScript(waitForDOMContentReady);
                notify_state_.added_dom_content_listener = true;
            }

            // This flag set as a result of onDOMContentLoaded overlay message.
            if (!notify_state_.dom_content_loaded)
            {
                return(kSlowTimerMilli);
            }

            if (!notify_state_.sent_data_dir && Config.Url.Length > 0)
            {
                notify_state_.sent_data_dir = true;

                var url = Config.Url;
                // If file is a remote pointer, load that file explicitly so that the manifest
                // is relative to the pointed to url and not the local file.
                if (url.StartsWith("file:///"))
                {
                    var html  = File.ReadAllText(new Uri(url).LocalPath);
                    var match = System.Text.RegularExpressions.Regex.Match(html, @"<meta http-equiv=""refresh"" content=""0; url=(.*)?""\/?>");
                    if (match.Groups.Count > 1)
                    {
                        url = match.Groups[1].Value;
                    }
                }

                var web = new System.Net.WebClient();
                web.Encoding = System.Text.Encoding.UTF8;
                System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;

                var data_file_paths = new List <string>();
                try {
                    var data_dir_manifest = new Uri(new Uri(url), "data/manifest.txt");
                    var manifest_reader   = new StringReader(web.DownloadString(data_dir_manifest));
                    for (var line = manifest_reader.ReadLine(); line != null; line = manifest_reader.ReadLine())
                    {
                        line = line.Trim();
                        if (line.Length > 0)
                        {
                            data_file_paths.Add(line);
                        }
                    }
                } catch (System.Net.WebException e) {
                    if (e.Status == System.Net.WebExceptionStatus.ProtocolError &&
                        e.Response is System.Net.HttpWebResponse &&
                        ((System.Net.HttpWebResponse)e.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
                    {
                        // Ignore file not found.
                    }
                    else if (e.InnerException != null &&
                             (e.InnerException is FileNotFoundException || e.InnerException is DirectoryNotFoundException))
                    {
                        // Ignore file not found.
                    }
                    else if (e.InnerException != null && e.InnerException.InnerException != null &&
                             (e.InnerException.InnerException is FileNotFoundException || e.InnerException.InnerException is DirectoryNotFoundException))
                    {
                        // Ignore file not found.
                    }
                    else
                    {
                        LogError("Unable to read manifest file: " + e.Message);
                    }
                } catch (Exception e) {
                    LogError("Unable to read manifest file: " + e.Message);
                }

                if (data_file_paths.Count > 0)
                {
                    var file_data = new Dictionary <string, string>();
                    foreach (string data_filename in data_file_paths)
                    {
                        try {
                            var file_path = new Uri(new Uri(url), "data/" + data_filename);
                            file_data[data_filename] = web.DownloadString(file_path);
                        } catch (Exception e) {
                            LogError("Unable to read data file: " + e.Message);
                        }
                    }
                    OnDataFilesRead(new JSEvents.DataFilesRead(file_data));
                }
            }

            bool game_exists = ffxiv_.FindProcess();

            if (game_exists != notify_state_.game_exists)
            {
                notify_state_.game_exists = game_exists;
                OnGameExists(new JSEvents.GameExistsEvent(game_exists));
            }

            bool game_active = game_active = ffxiv_.IsActive();

            if (game_active != notify_state_.game_active)
            {
                notify_state_.game_active = game_active;
                OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
            }

            // Silently stop sending other messages if the ffxiv process isn't around.
            if (!game_exists)
            {
                return(kUberSlowTimerMilli);
            }

            // onInCombatChangedEvent: Fires when entering or leaving combat.
            bool in_act_combat  = FFXIV_ACT_Plugin.ACTWrapper.InCombat;
            bool in_game_combat = ffxiv_.GetInGameCombat();

            if (!notify_state_.in_act_combat.HasValue || in_act_combat != notify_state_.in_act_combat ||
                !notify_state_.in_game_combat.HasValue || in_game_combat != notify_state_.in_game_combat)
            {
                notify_state_.in_act_combat  = in_act_combat;
                notify_state_.in_game_combat = in_game_combat;
                OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_act_combat, in_game_combat));
            }

            // onZoneChangedEvent: Fires when the player changes their current zone.
            string zone_name = FFXIV_ACT_Plugin.ACTWrapper.CurrentZone;

            if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name))
            {
                notify_state_.zone_name = zone_name;
                OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
            }

            DateTime now = DateTime.Now;
            // The |player| can be null, such as during a zone change.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> player_data = ffxiv_.GetSelfData();
            var player      = player_data != null ? player_data.Item1 : null;
            var player_cast = player_data != null ? player_data.Item2 : null;
            // The |target| can be null when no target is selected.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> target_data = ffxiv_.GetTargetData();
            var target      = target_data != null ? target_data.Item1 : null;
            var target_cast = target_data != null ? target_data.Item2 : null;
            // The |focus| can be null when no focus target is selected.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> focus_data = ffxiv_.GetFocusData();
            var focus      = focus_data != null ? focus_data.Item1 : null;
            var focus_cast = focus_data != null ? focus_data.Item2 : null;

            // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
            // lost.
            if (player != null)
            {
                bool dead = player.hp == 0;
                if (dead != notify_state_.dead)
                {
                    notify_state_.dead = dead;
                    if (dead)
                    {
                        OnPlayerDied(new JSEvents.PlayerDiedEvent());
                    }
                }
            }

            // onPlayerChangedEvent: Fires when current player data changes.
            if (player != null)
            {
                bool send = false;
                if (player != notify_state_.player)
                {
                    notify_state_.player = player;
                    send = true;
                }

                if (player.job == FFXIVProcess.EntityJob.RDM)
                {
                    var job = ffxiv_.GetRedMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.rdm))
                        {
                            notify_state_.rdm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.RedMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.WAR)
                {
                    var job = ffxiv_.GetWarrior();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.war))
                        {
                            notify_state_.war = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.WarriorDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.DRK)
                {
                    var job = ffxiv_.GetDarkKnight();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.drk))
                        {
                            notify_state_.drk = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.DarkKnightDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.PLD)
                {
                    var job = ffxiv_.GetPaladin();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.pld))
                        {
                            notify_state_.pld = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.PaladinDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.BRD)
                {
                    var job = ffxiv_.GetBard();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.brd))
                        {
                            notify_state_.brd = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.BardDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.NIN)
                {
                    var job = ffxiv_.GetNinja();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.nin))
                        {
                            notify_state_.nin = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.NinjaDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.DRG)
                {
                    var job = ffxiv_.GetDragoon();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.drg))
                        {
                            notify_state_.drg = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.DragoonDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.BLM || player.job == FFXIVProcess.EntityJob.THM)
                {
                    var job = ffxiv_.GetBlackMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.blm))
                        {
                            notify_state_.blm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.BlackMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.WHM)
                {
                    var job = ffxiv_.GetWhiteMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.whm))
                        {
                            notify_state_.whm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.WhiteMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.SMN || player.job == FFXIVProcess.EntityJob.SCH || player.job == FFXIVProcess.EntityJob.ACN)
                {
                    var job = ffxiv_.GetSummonerAndScholar();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.smn_sch))
                        {
                            notify_state_.smn_sch = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.SummonerAndScholarDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.MNK || player.job == FFXIVProcess.EntityJob.PGL)
                {
                    var job = ffxiv_.GetMonk();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.mnk))
                        {
                            notify_state_.mnk = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.MonkDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.MCH)
                {
                    var job = ffxiv_.GetMachinist();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.mch))
                        {
                            notify_state_.mch = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.MachinistDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.AST)
                {
                    var job = ffxiv_.GetAstrologian();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.ast))
                        {
                            notify_state_.ast = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.AstrologianDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (send)
                {
                    // TODO: SAM everything
                    // No job-specific data.
                    OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
                }
            }

            // onTargetChangedEvent: Fires when current target or their state changes.
            if (target != notify_state_.target)
            {
                notify_state_.target = target;
                if (target != null)
                {
                    OnTargetChanged(new JSEvents.TargetChangedEvent(target));
                }
                else
                {
                    OnTargetChanged(new JSEvents.TargetChangedEvent(null));
                }
            }

            // onTargetCastingEvent: Fires each tick while the target is casting, and once
            // with null when not casting.
            int target_cast_id = target_cast != null ? target_cast.casting_id : 0;

            if (target_cast_id != 0 || target_cast_id != notify_state_.target_cast_id)
            {
                notify_state_.target_cast_id = target_cast_id;
                // The game considers things to be casting once progress reaches the end for a while, as the server is
                // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
                // be casting anymore once it reaches the end.
                if (target_cast_id != 0 && target_cast.casting_time_progress < target_cast.casting_time_length)
                {
                    DateTime start = now.AddSeconds(-target_cast.casting_time_progress);
                    // If the start is within the timer interval, assume it's the same cast. Since we sample the game
                    // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
                    // helps avoid it.
                    TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
                    if (start + range < notify_state_.target_cast_start || start - range > notify_state_.target_cast_start)
                    {
                        notify_state_.target_cast_start = start;
                    }
                    TimeSpan progress = now - notify_state_.target_cast_start;
                    OnTargetCasting(new JSEvents.TargetCastingEvent(target_cast.casting_id, progress.TotalSeconds, target_cast.casting_time_length));
                }
                else
                {
                    notify_state_.target_cast_start = new DateTime();
                    OnTargetCasting(new JSEvents.TargetCastingEvent(0, 0, 0));
                }
            }

            // onFocusChangedEvent: Fires when current focus target or their state changes.
            if (focus != notify_state_.focus)
            {
                notify_state_.focus = focus;
                if (target != null)
                {
                    OnFocusChanged(new JSEvents.FocusChangedEvent(focus));
                }
                else
                {
                    OnFocusChanged(new JSEvents.FocusChangedEvent(null));
                }
            }

            // onFocusCastingEvent: Fires each tick while the focus target is casting, and
            // once with null when not casting.
            int focus_cast_id = focus_cast != null ? focus_cast.casting_id : 0;

            if (focus_cast_id != 0 || focus_cast_id != notify_state_.focus_cast_id)
            {
                notify_state_.focus_cast_id = focus_cast_id;
                // The game considers things to be casting once progress reaches the end for a while, as the server is
                // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
                // be casting anymore once it reaches the end.
                if (focus_cast_id != 0 && focus_cast.casting_time_progress < focus_cast.casting_time_length)
                {
                    DateTime start = now.AddSeconds(-focus_cast.casting_time_progress);
                    // If the start is within the timer interval, assume it's the same cast. Since we sample the game
                    // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
                    // helps avoid it.
                    TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
                    if (start + range < notify_state_.focus_cast_start || start - range > notify_state_.focus_cast_start)
                    {
                        notify_state_.focus_cast_start = start;
                    }
                    TimeSpan progress = now - notify_state_.focus_cast_start;
                    OnFocusCasting(new JSEvents.FocusCastingEvent(focus_cast.casting_id, progress.TotalSeconds, focus_cast.casting_time_length));
                }
                else
                {
                    notify_state_.focus_cast_start = new DateTime();
                    OnFocusCasting(new JSEvents.FocusCastingEvent(0, 0, 0));
                }
            }

            // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
            // more specific events, some of which may involve parsing the logs as well.
            List <string> logs;

            log_lines_semaphore_.Wait();
            logs       = log_lines_;
            log_lines_ = last_log_lines_;
            log_lines_semaphore_.Release();
            if (logs.Count > 0)
            {
                OnLogsChanged(new JSEvents.LogEvent(logs));
                logs.Clear();
            }
            last_log_lines_ = logs;

            fight_tracker_.Tick(DateTime.Now);

            return(game_active ? kFastTimerMilli : kSlowTimerMilli);
        }
コード例 #4
0
        // Events that we want to update as soon as possible.  Return next time this should be called.
        private int SendFastRateEvents()
        {
            if (reset_notify_state_)
            {
                notify_state_ = new NotifyState();
            }
            reset_notify_state_ = false;

            // Loading dance:
            // * OverlayPlugin loads addons and initializes event sources.
            // * OverlayPlugin loads its configuration.
            // * Event sources are told to load their configuration and start (LoadConfig and Start are called).
            // * Overlays are initialised and the browser instances are started. At this points the overlays start loading.
            // * At some point the overlay's JavaScript is executed and OverlayPluginApi is injected. This order isn't
            //   deterministic and depends on what the ACT process is doing at that point in time. During startup the
            //   OverlayPluginApi is usually injected after the overlay is done loading while an overlay that's reloaded or
            //   loaded later on will see the OverlayPluginApi before the page has loaded.
            // * The overlay JavaScript sets up the initial event handlers and calls the cactbotLoadUser handler through
            //   getUserConfigLocation. These actions are queued by the JS implementation in common.js until OverlayPluginApi
            //   (or the WebSocket) is available. Once it is, the event subscriptions and handler calls are transmitted.
            // * OverlayPlugin stores the event subscriptions and executes the C# handlers which in this case means
            //   FetchUserFiles is called. That method loads the user files and returns them. The result is now transmitted
            //   back to the overlay that called the handler and the Promise in JS is resolved with the result.
            // * getUserConfigLocation processes the received information and calls the passed callback. This constructs the
            //   overlay specific objects and registers additional event handlers. Finally, the cactbotRequestState handler
            //   is called.
            // * OverlayPlugin processes the new event subscriptions and executes the cactbotRequestState handler.
            // * The next time SendFastRateEvents is called, it resets notify_state_ (since the previous handler set
            //   reset_notify_state_ to true) which causes it to dispatch all state events again. These events are now
            //   dispatched to all subscribed overlays. However, this means that overlays can receive state events multiple
            //   times during startup. If the user has three Cactbot overlays, all of them will call cactbotRequestState and
            //   thus cause this to happen one to three times depending on their timing. This shouldn't cause any issues but
            //   it's a waste of CPU cycles.
            // * Since this only happens during startup, it's probably not worth fixing though. Not sure.
            // * Some overlays behave slightly different from the above explanation. Raidboss for example loads data files
            //   in addition to the listed steps. I think it's even loading them twice since raidboss.js loads the data files
            //   for gTimelineController and popup-text.js requests them again for its own purposes.

            bool game_exists = ffxiv_.FindProcess();

            if (game_exists != notify_state_.game_exists)
            {
                notify_state_.game_exists = game_exists;
                OnGameExists(new JSEvents.GameExistsEvent(game_exists));
            }

            bool game_active = game_active = ffxiv_.IsActive();

            if (game_active != notify_state_.game_active)
            {
                notify_state_.game_active = game_active;
                OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
            }

            // Silently stop sending other messages if the ffxiv process isn't around.
            if (!game_exists)
            {
                return(kUberSlowTimerMilli);
            }

            // onInCombatChangedEvent: Fires when entering or leaving combat.
            bool in_act_combat  = Advanced_Combat_Tracker.ActGlobals.oFormActMain.InCombat;
            bool in_game_combat = ffxiv_.GetInGameCombat();

            if (!notify_state_.in_act_combat.HasValue || in_act_combat != notify_state_.in_act_combat ||
                !notify_state_.in_game_combat.HasValue || in_game_combat != notify_state_.in_game_combat)
            {
                notify_state_.in_act_combat  = in_act_combat;
                notify_state_.in_game_combat = in_game_combat;
                OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_act_combat, in_game_combat));
            }

            // onZoneChangedEvent: Fires when the player changes their current zone.
            string zone_name = Advanced_Combat_Tracker.ActGlobals.oFormActMain.CurrentZone;

            if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name))
            {
                notify_state_.zone_name = zone_name;
                OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
                ClearFateWatcherDictionaries();
            }

            DateTime now = DateTime.Now;

            // The |player| can be null, such as during a zone change.
            FFXIVProcess.EntityData player = ffxiv_.GetSelfData();

            // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
            // lost.
            if (player != null)
            {
                bool dead = player.hp == 0;
                if (dead != notify_state_.dead)
                {
                    notify_state_.dead = dead;
                    if (dead)
                    {
                        OnPlayerDied(new JSEvents.PlayerDiedEvent());
                    }
                }
            }

            // onPlayerChangedEvent: Fires when current player data changes.
            if (player != null)
            {
                bool send = false;
                if (!player.Equals(notify_state_.player))
                {
                    // Clear the FateWatcher dictionaries if we switched characters
                    if (notify_state_.player != null && !player.name.Equals(notify_state_.player.name))
                    {
                        ClearFateWatcherDictionaries();
                    }
                    notify_state_.player = player;
                    send = true;
                }
                var job = ffxiv_.GetJobSpecificData(player.job);
                if (job != null)
                {
                    if (send || !JObject.DeepEquals(job, notify_state_.job_data))
                    {
                        notify_state_.job_data = job;
                        var e = new JSEvents.PlayerChangedEvent(player);
                        e.jobDetail = job;
                        OnPlayerChanged(e);
                    }
                }
                else if (send)
                {
                    // No job-specific data.
                    OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
                }
            }

            // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
            // more specific events, some of which may involve parsing the logs as well.
            List <string> logs;
            List <string> import_logs;

            log_lines_semaphore_.Wait();
            logs              = log_lines_;
            log_lines_        = last_log_lines_;
            import_logs       = import_log_lines_;
            import_log_lines_ = last_import_log_lines_;
            log_lines_semaphore_.Release();

            if (logs.Count > 0)
            {
                OnLogsChanged(new JSEvents.LogEvent(logs));
                logs.Clear();
            }
            if (import_logs.Count > 0)
            {
                OnImportLogsChanged(new JSEvents.ImportLogEvent(import_logs));
                import_logs.Clear();
            }

            last_log_lines_        = logs;
            last_import_log_lines_ = import_logs;

            return(game_active ? kFastTimerMilli : kSlowTimerMilli);
        }
コード例 #5
0
ファイル: CactbotOverlay.cs プロジェクト: Vuzrak/cactbot
    // Events that we want to update as soon as possible.
    private void SendFastRateEvents() {
      // Hold this while we're in here to prevent the Renderer or Browser from disappearing from under us.
      fast_update_timer_semaphore_.Wait();

      // Handle startup and shutdown. And do not fire any events until the page has loaded and had a chance to
      // register its event handlers.
      if (Overlay == null || Overlay.Renderer == null || Overlay.Renderer.Browser == null || Overlay.Renderer.Browser.IsLoading) {
        fast_update_timer_semaphore_.Release();
        fast_update_timer_.Interval = kSlowTimerMilli;
        fast_update_timer_.Start();
        return;
      }

      if (reset_notify_state_)
        notify_state_ = new NotifyState();
      reset_notify_state_ = false;

      if (!notify_state_.sent_data_dir && Config.Url.Length > 0) {
        notify_state_.sent_data_dir = true;

        var web = new System.Net.WebClient();

        var data_file_paths = new List<string>();
        try {
          var data_dir_manifest = new Uri(new Uri(Config.Url), "data/manifest.txt");
          var manifest_reader = new StringReader(web.DownloadString(data_dir_manifest));
          for (var line = manifest_reader.ReadLine(); line != null; line = manifest_reader.ReadLine()) {
            line = line.Trim();
            if (line.Length > 0)
              data_file_paths.Add(line);
          }
        } catch (System.Net.WebException e) {
          if (e.Status == System.Net.WebExceptionStatus.ProtocolError &&
              e.Response is System.Net.HttpWebResponse &&
              ((System.Net.HttpWebResponse)e.Response).StatusCode == System.Net.HttpStatusCode.NotFound) {
            // Ignore file not found.
          } else if (e.InnerException != null &&
            (e.InnerException is FileNotFoundException || e.InnerException is DirectoryNotFoundException)) {
            // Ignore file not found.
          } else if (e.InnerException != null && e.InnerException.InnerException != null &&
            (e.InnerException.InnerException is FileNotFoundException || e.InnerException.InnerException is DirectoryNotFoundException)) {
            // Ignore file not found.
          } else {
            LogError("Unable to read manifest file: " + e.Message);
          }
        } catch (Exception e) {
          LogError("Unable to read manifest file: " + e.Message);
        }

        if (data_file_paths.Count > 0) {
          var file_data = new Dictionary<string, string>();
          foreach (string data_filename in data_file_paths) {
            try {
              var file_path = new Uri(new Uri(Config.Url), "data/" + data_filename);
              var file_reader = new StringReader(web.DownloadString(file_path));
              file_data[data_filename] = file_reader.ReadToEnd();
            } catch (Exception e) {
              LogError("Unable to read data file: " + e.Message);
            }
          }
          OnDataFilesRead(new JSEvents.DataFilesRead(file_data));
        }
      }

      bool game_exists = ffxiv_.FindProcess();
      if (game_exists != notify_state_.game_exists) {
        notify_state_.game_exists = game_exists;
        OnGameExists(new JSEvents.GameExistsEvent(game_exists));
      }

      bool game_active = game_active = ffxiv_.IsActive();
      if (game_active != notify_state_.game_active) {
        notify_state_.game_active = game_active;
        OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
      }

      // Silently stop sending other messages if the ffxiv process isn't around.
      if (!game_exists) {
        fast_update_timer_semaphore_.Release();
        fast_update_timer_.Interval = kUberSlowTimerMilli;
        fast_update_timer_.Start();
        return;
      }

      // onInCombatChangedEvent: Fires when entering or leaving combat.
      bool in_combat = FFXIV_ACT_Plugin.ACTWrapper.InCombat;
      if (!notify_state_.in_combat.HasValue || in_combat != notify_state_.in_combat) {
        notify_state_.in_combat = in_combat;
        OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_combat));
      }

      // onZoneChangedEvent: Fires when the player changes their current zone.
      string zone_name = FFXIV_ACT_Plugin.ACTWrapper.CurrentZone;
      if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name)) {
        notify_state_.zone_name = zone_name;
        OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
      }

      DateTime now = DateTime.Now;
      // The |player| can be null, such as during a zone change.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> player_data = ffxiv_.GetSelfData();
      var player = player_data != null ? player_data.Item1 : null;
      var player_cast = player_data != null ? player_data.Item2 : null;
      // The |target| can be null when no target is selected.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> target_data = ffxiv_.GetTargetData();
      var target = target_data != null ? target_data.Item1 : null;
      var target_cast = target_data != null ? target_data.Item2 : null;
      // The |focus| can be null when no focus target is selected.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> focus_data = ffxiv_.GetFocusData();
      var focus = focus_data != null ? focus_data.Item1 : null;
      var focus_cast = focus_data != null ? focus_data.Item2 : null;

      // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
      // lost.
      if (player != null) {
        bool dead = player.hp == 0;
        if (dead != notify_state_.dead) {
          notify_state_.dead = dead;
          if (dead)
            OnPlayerDied(new JSEvents.PlayerDiedEvent());
        }
      }

      // onPlayerChangedEvent: Fires when current player data changes.
      if (player != null) {
        bool send = false;
        if (player != notify_state_.player) {
          notify_state_.player = player;
          send = true;
        }

        if (player.job == FFXIVProcess.EntityJob.RDM) {
          var job = ffxiv_.GetRedMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.rdm)) {
              notify_state_.rdm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.RedMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.WAR) {
          var job = ffxiv_.GetWarrior();
          if (job != null) {
            if (send || !job.Equals(notify_state_.war)) {
              notify_state_.war = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.WarriorDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.DRK) {
          var job = ffxiv_.GetDarkKnight();
          if (job != null) {
            if (send || !job.Equals(notify_state_.drk)) {
              notify_state_.drk = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.DarkKnightDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.PLD) {
          var job = ffxiv_.GetPaladin();
          if (job != null) {
            if (send || !job.Equals(notify_state_.pld)) {
              notify_state_.pld = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.PaladinDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.BRD) {
          var job = ffxiv_.GetBard();
          if (job != null) {
            if (send || !job.Equals(notify_state_.brd)) {
              notify_state_.brd = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.BardDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.NIN) {
          var job = ffxiv_.GetNinja();
          if (job != null) {
            if (send || !job.Equals(notify_state_.nin)) {
              notify_state_.nin = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.NinjaDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.BLM || player.job == FFXIVProcess.EntityJob.THM) {
          var job = ffxiv_.GetBlackMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.blm)) {
              notify_state_.blm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.BlackMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.WHM) {
          var job = ffxiv_.GetWhiteMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.whm)) {
              notify_state_.whm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.WhiteMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.SMN || player.job == FFXIVProcess.EntityJob.SCH || player.job == FFXIVProcess.EntityJob.ACN) {
          var job = ffxiv_.GetSummonerAndScholar();
          if (job != null) {
            if (send || !job.Equals(notify_state_.smn_sch)) {
              notify_state_.smn_sch = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.SummonerAndScholarDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.MNK || player.job == FFXIVProcess.EntityJob.PGL) {
          var job = ffxiv_.GetMonk();
          if (job != null) {
            if (send || !job.Equals(notify_state_.mnk)) {
              notify_state_.mnk = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.MonkDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.MCH) {
          var job = ffxiv_.GetMachinist();
          if (job != null) {
            if (send || !job.Equals(notify_state_.mch)) {
              notify_state_.mch = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.MachinistDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.AST) {
          var job = ffxiv_.GetAstrologian();
          if (job != null) {
            if (send || !job.Equals(notify_state_.ast)) {
              notify_state_.ast = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.AstrologianDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (send) {
          // TODO: SAM everything
          // No job-specific data.
          OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
        }
      }

      // onTargetChangedEvent: Fires when current target or their state changes.
      if (target != notify_state_.target) {
        notify_state_.target = target;
        if (target != null)
          OnTargetChanged(new JSEvents.TargetChangedEvent(target));
        else
          OnTargetChanged(new JSEvents.TargetChangedEvent(null));
      }

      // onTargetCastingEvent: Fires each tick while the target is casting, and once
      // with null when not casting.
      int target_cast_id = target_cast != null ? target_cast.casting_id : 0;
      if (target_cast_id != 0 || target_cast_id != notify_state_.target_cast_id) {
        notify_state_.target_cast_id = target_cast_id;
        // The game considers things to be casting once progress reaches the end for a while, as the server is
        // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
        // be casting anymore once it reaches the end.
        if (target_cast_id != 0 && target_cast.casting_time_progress < target_cast.casting_time_length) {
          DateTime start = now.AddSeconds(-target_cast.casting_time_progress);
          // If the start is within the timer interval, assume it's the same cast. Since we sample the game
          // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
          // helps avoid it.
          TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
          if (start + range < notify_state_.target_cast_start || start - range > notify_state_.target_cast_start)
            notify_state_.target_cast_start = start;
          TimeSpan progress = now - notify_state_.target_cast_start;
          OnTargetCasting(new JSEvents.TargetCastingEvent(target_cast.casting_id, progress.TotalSeconds, target_cast.casting_time_length));
        } else {
          notify_state_.target_cast_start = new DateTime();
          OnTargetCasting(new JSEvents.TargetCastingEvent(0, 0, 0));
        }
      }

      // onFocusChangedEvent: Fires when current focus target or their state changes.
      if (focus != notify_state_.focus) {
        notify_state_.focus = focus;
        if (target != null)
          OnFocusChanged(new JSEvents.FocusChangedEvent(focus));
        else
          OnFocusChanged(new JSEvents.FocusChangedEvent(null));
      }

      // onFocusCastingEvent: Fires each tick while the focus target is casting, and
      // once with null when not casting.
      int focus_cast_id = focus_cast != null ? focus_cast.casting_id : 0;
      if (focus_cast_id != 0 || focus_cast_id != notify_state_.focus_cast_id) {
        notify_state_.focus_cast_id = focus_cast_id;
        // The game considers things to be casting once progress reaches the end for a while, as the server is
        // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
        // be casting anymore once it reaches the end.
        if (focus_cast_id != 0 && focus_cast.casting_time_progress < focus_cast.casting_time_length) {
          DateTime start = now.AddSeconds(-focus_cast.casting_time_progress);
          // If the start is within the timer interval, assume it's the same cast. Since we sample the game
          // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
          // helps avoid it.
          TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
          if (start + range < notify_state_.focus_cast_start || start - range > notify_state_.focus_cast_start)
            notify_state_.focus_cast_start = start;
          TimeSpan progress = now - notify_state_.focus_cast_start;
          OnFocusCasting(new JSEvents.FocusCastingEvent(focus_cast.casting_id, progress.TotalSeconds, focus_cast.casting_time_length));
        } else {
          notify_state_.focus_cast_start = new DateTime();
          OnFocusCasting(new JSEvents.FocusCastingEvent(0, 0, 0));
        }
      }

      // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
      // more specific events, some of which may involve parsing the logs as well.
      List<string> logs;
      log_lines_semaphore_.Wait();
      logs = log_lines_;
      log_lines_ = last_log_lines_;
      log_lines_semaphore_.Release();
      if (logs.Count > 0) {
        OnLogsChanged(new JSEvents.LogEvent(logs));
        logs.Clear();
      }
      last_log_lines_ = logs;

      fight_tracker_.Tick(DateTime.Now);

      fast_update_timer_semaphore_.Release();
      fast_update_timer_.Interval = game_active ? kFastTimerMilli : kSlowTimerMilli;
      fast_update_timer_.Start();
    }