WoD 战报额外统计信息与导出

Generate additional statistical data in the dungeon and duel report pages

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

//-----------------------------------------------------------------------------
// [WoD] Extra Statistics
// Copyright (c) Fenghou, Tomy, DotIN13, ShakeSS
// This script can generate additional statistical data in the dungeon and duel report pages.
// When you entered the details or statistics page of reports, a new button will appear beside
//   the details button. At the details page, the new button is "Extra Stat", which will show
//   the statistics of the current level when you click it. At the statistics page, the new
//   button is "Entire Extra Stat", which will show the statistics of entire dungeon.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// If you want to add a new Stat table, you should create a new sub class of CInfoList,
//   and use CStat::RegInfoList() to register your new info list.
// A detailed example is CILItemDamage.
//-----------------------------------------------------------------------------
// 此版本的依赖项替换为gitee以保证国内访问
// ==UserScript==
// @name         WoD 战报额外统计信息与导出
// @namespace    fenghou
// @description  Generate additional statistical data in the dungeon and duel report pages
// @icon         http://info.world-of-dungeons.org/wod/css/WOD.gif
// @include      http*://*.world-of-dungeons.*/wod/spiel/*dungeon/report.php*
// @include      http*://*.world-of-dungeons.*/wod/spiel/tournament/*duell.php*
// @include      http*://*.wannaexpresso.*/wod/spiel/*dungeon/report.php*
// @include      http*://*.wannaexpresso.*/wod/spiel/tournament/*duell.php*
// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/Blob.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.form.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/plugin/customParseFormat.js
// @require      https://code.jquery.com/ui/1.13.2/jquery-ui.min.js
// @modifier     Christophero
// @version      2023.03.14.1

// ==/UserScript==
(function () {
    // COMMON FUNCTIONS ///////////////////////////////////////////////////////////
    // Choose contents of the corresponding language
    // Contents: {Name1 : [lang1, lang2, ...], Name2 : [lang1, lang2, ...], ...}
    // return: Local contents, or null
    // It will edit the input contents directly, so the returned object is not necessary
    function GetLocalContents(Contents) {
        function GetLanguageId() {
            var langText = null;
            var allMetas = document.getElementsByTagName("meta");
            for (var i = 0; i < allMetas.length; ++i) {
                if (allMetas[i].httpEquiv == "Content-Language") {
                    langText = allMetas[i].content;
                    break;
                }
            }
            if (langText === null)
                return false;

            switch (langText) {
                case "en":
                    return 0;
                case "cn":
                    return 1;
                default:
                    return null;
            }
        }

        var nLangId = GetLanguageId();
        if (nLangId === null)
            return null;

        if (Contents instanceof Object) {
            for (var name in Contents)
                Contents[name] = Contents[name][nLangId];
            return Contents;
        } else
            return null;
    }


    function CompareString(a, b) {
        a = a || "";
        b = b || "";
        return a.toLowerCase().localeCompare(b.toLowerCase(), "zh-CN-u-co-pinyin");
    }


    function CreateElementHTML(Name, Content /* , [AttrName1, AttrValue1], [AttrName2, AttrValue2], ... */) {
        var HTML = '<' + Name;

        for (var i = 2; i < arguments.length; ++i)
            HTML += ' ' + arguments[i][0] + '="' + arguments[i][1] + '"';

        HTML += (Content != null && Content !== "") ? ('>' + Content + '</' + Name + '>') : (' />');

        return HTML;
    }


    function DbgMsg(Text) {
        if (DEBUG) alert(Text);
    }

    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (needle) {
            for (var i = 0; i < this.length; i++) {
                if (this[i] === needle) {
                    return i;
                }
            }
            return -1;
        };
    }

    initJQUIStyle();

    /**
     * 初始化JqueryUI的样式表
     */
    function initJQUIStyle() {
      let $toolbarCss = $("<link>");
      $("head").prepend($toolbarCss);
      $toolbarCss.attr({
        rel: "stylesheet",
        type: "text/css",
        href: "https://code.jquery.com/ui/1.13.2/themes/humanity/jquery-ui.css",
      });
        document.querySelector("style").textContent +=
          ".ui-progressbar .ui-progressbar-value {transition: width 0.7s ease 0s;} " +
          "#progressbar .progress-label {position: absolute; left: 48%; font-weight: bold; text-shadow: 1px 1px 0 #fff; height: 31px; display: flex; justify-content: enter; align-items: center;}" +
          "@media (min-width:1px) { .ui-dialog { width: 95% !important; } } " +
          "@media (min-width: 768px) { .ui-dialog { max-width:900px !important; width:95% !important; } }";
    }

    // COMMON STAT FUNCTIONS ///////////////////////////////////////////////////////////

    function getSum(numArr) {
      var nTotal = 0;
      for (var i = 0; i < numArr.length; i++) {
        nTotal = nTotal + Number(numArr[i]);
      }
      return nTotal;
    }

    function getAverage(numArr) {
      return getSum(numArr) / numArr.length;
    }
    // see http://www.johndcook.com/blog/2008/09/26/comparing-three-methods-of-computing-standard-deviation/
    // for discussion on choice of algorithm

    function getVariance(numArr) {
      if (numArr.length <= 1) {
        return 0;
      }
      var nAvg = getAverage(numArr);
      var nTempSum = 0;
      for (var i = 0; i < numArr.length; i++) {
        nTempSum = nTempSum + Math.pow(Number(numArr[i]) - nAvg, 2);
      }
      return nTempSum / (numArr.length - 1);
    }

    // sample standard deviation
    function getSTD(numArr) {
      return Number(Math.sqrt(getVariance(numArr)).toFixed(2));
    }

    function getMax(numArr) {
      return Math.max.apply(null, numArr);
    }

    function getMin(numArr) {
      return Math.min.apply(null, numArr);
    }

    // EXTERN FUNCTIONS ///////////////////////////////////////////////////////////

    /**
     * A utility function for defining JavaScript classes.
     *
     * This function expects a single object as its only argument.  It defines
     * a new JavaScript class based on the data in that object and returns the
     * constructor function of the new class.  This function handles the repetitive
     * tasks of defining classes: setting up the prototype object for correct
     * inheritance, copying methods from other types, and so on.
     *
     * The object passed as an argument should have some or all of the
     * following properties:
     *
     *      name: The name of the class being defined.
     *            If specified, this value will be stored in the classname
     *            property of the prototype object.
     *
     *    extend: The constructor of the class to be extended. If omitted,
     *            the Object( ) constructor will be used. This value will
     *            be stored in the superclass property of the prototype object.
     *
     * construct: The constructor function for the class. If omitted, a new
     *            empty function will be used. This value becomes the return
     *            value of the function, and is also stored in the constructor
     *            property of the prototype object.
     *
     *   methods: An object that specifies the instance methods (and other shared
     *            properties) for the class. The properties of this object are
     *            copied into the prototype object of the class. If omitted,
     *            an empty object is used instead. Properties named
     *            "classname", "superclass", and "constructor" are reserved
     *            and should not be used in this object.
     *
     *   statics: An object that specifies the static methods (and other static
     *            properties) for the class. The properties of this object become
     *            properties of the constructor function. If omitted, an empty
     *            object is used instead.
     *
     *   borrows: A constructor function or array of constructor functions.
     *            The instance methods of each of the specified classes are copied
     *            into the prototype object of this new class so that the
     *            new class borrows the methods of each specified class.
     *            Constructors are processed in the order they are specified,
     *            so the methods of a class listed at the end of the array may
     *            overwrite the methods of those specified earlier. Note that
     *            borrowed methods are stored in the prototype object before
     *            the properties of the methods object above. Therefore,
     *            methods specified in the methods object can overwrite borrowed
     *            methods. If this property is not specified, no methods are
     *            borrowed.
     *
     *  provides: A constructor function or array of constructor functions.
     *            After the prototype object is fully initialized, this function
     *            verifies that the prototype includes methods whose names and
     *            number of arguments match the instance methods defined by each
     *            of these classes. No methods are copied; this is simply an
     *            assertion that this class "provides" the functionality of the
     *            specified classes. If the assertion fails, this method will
     *            throw an exception. If no exception is thrown, any
     *            instance of the new class can also be considered (using "duck
     *            typing") to be an instance of these other types.  If this
     *            property is not specified, no such verification is performed.
     **/
    function DefineClass(data) {
      // Extract the fields we'll use from the argument object.
      // Set up default values.
      var classname = data.name;
      var superclass = data.extend || Object;
      var constructor = data.construct || function () {};
      var methods = data.methods || {};
      var statics = data.statics || {};
      var borrows;
      var provides;

      // Borrows may be a single constructor or an array of them.
      if (!data.borrows) borrows = [];
      else if (data.borrows instanceof Array) borrows = data.borrows;
      else borrows = [data.borrows];

      // Ditto for the provides property.
      if (!data.provides) provides = [];
      else if (data.provides instanceof Array) provides = data.provides;
      else provides = [data.provides];

      // Create the object that will become the prototype for our class.
      var proto = new superclass();

      // Delete any noninherited properties of this new prototype object.
      for (var p in proto) if (proto.hasOwnProperty(p)) delete proto[p];

      // Borrow methods from "mixin" classes by copying to our prototype.
      for (var i = 0; i < borrows.length; i++) {
        var c = data.borrows[i];
        borrows[i] = c;
        // Copy method properties from prototype of c to our prototype
        for (var p in c.prototype) {
          if (typeof c.prototype[p] != "function") continue;
          proto[p] = c.prototype[p];
        }
      }
      // Copy instance methods to the prototype object
      // This may overwrite methods of the mixin classes
      for (var p in methods) proto[p] = methods[p];

      // Set up the reserved "constructor", "superclass", and "classname"
      // properties of the prototype.
      proto.constructor = constructor;
      proto.superclass = superclass;
      // classname is set only if a name was actually specified.
      if (classname) proto.classname = classname;

      // Verify that our prototype provides all of the methods it is supposed to.
      for (var i = 0; i < provides.length; i++) {
        // for each class
        var c = provides[i];
        for (var p in c.prototype) {
          // for each property
          if (typeof c.prototype[p] != "function") continue; // methods only
          if (p == "constructor" || p == "superclass") continue;
          // Check that we have a method with the same name and that
          // it has the same number of declared arguments.  If so, move on
          if (
            p in proto &&
            typeof proto[p] == "function" &&
            proto[p].length == c.prototype[p].length
          )
            continue;
          // Otherwise, throw an exception
          throw new Error(
            "Class " +
              classname +
              " does not provide method " +
              c.classname +
              "." +
              p
          );
        }
      }

      // Associate the prototype object with the constructor function
      constructor.prototype = proto;

      // Copy static properties to the constructor
      for (var p in statics) constructor[p] = statics[p];

      // Finally, return the constructor function
      return constructor;
    }

    /**
     * Throughout, whitespace is defined as one of the characters
     *  "\t" TAB \u0009
     *  "\n" LF  \u000A
     *  "\r" CR  \u000D
     *  " "  SPC \u0020
     *
     * This does not use Javascript's "\s" because that includes non-breaking
     * spaces (and also some other characters).
     */

    /**
     * Determine whether a node's text content is entirely whitespace.
     *
     * @param nod  A node implementing the |CharacterData| interface (i.e.,
     *             a |Text|, |Comment|, or |CDATASection| node
     * @return     True if all of the text content of |nod| is whitespace,
     *             otherwise false.
     */
    function is_all_ws(nod) {
      // Use ECMA-262 Edition 3 String and RegExp features
      return !/[^\t\n\r ]/.test(nod.data);
    }

    /**
     * Determine if a node should be ignored by the iterator functions.
     *
     * @param nod  An object implementing the DOM1 |Node| interface.
     * @return     true if the node is:
     *                1) A |Text| node that is all whitespace
     *                2) A |Comment| node
     *             and otherwise false.
     */

    function is_ignorable(nod) {
      return (
        nod.nodeType == Node.COMMENT_NODE || // A comment node
        (nod.nodeType == Node.TEXT_NODE && is_all_ws(nod))
      ); // a text node, all ws
    }

    /**
     * Version of |previousSibling| that skips nodes that are entirely
     * whitespace or comments.  (Normally |previousSibling| is a property
     * of all DOM nodes that gives the sibling node, the node that is
     * a child of the same parent, that occurs immediately before the
     * reference node.)
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The closest previous sibling to |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    function node_before(sib) {
      while ((sib = sib.previousSibling)) {
        if (!is_ignorable(sib)) return sib;
      }
      return null;
    }

    /**
     * Version of |nextSibling| that skips nodes that are entirely
     * whitespace or comments.
     *
     * @param sib  The reference node.
     * @return     Either:
     *               1) The closest next sibling to |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    function node_after(sib) {
      while ((sib = sib.nextSibling)) {
        if (!is_ignorable(sib)) return sib;
      }
      return null;
    }

    /**
     * Version of |lastChild| that skips nodes that are entirely
     * whitespace or comments.  (Normally |lastChild| is a property
     * of all DOM nodes that gives the last of the nodes contained
     * directly in the reference node.)
     *
     * @param par  The reference node.
     * @return     Either:
     *               1) The last child of |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    function last_child(par) {
      var res = par.lastChild;
      while (res) {
        if (!is_ignorable(res)) return res;
        res = res.previousSibling;
      }
      return null;
    }

    /**
     * Version of |firstChild| that skips nodes that are entirely
     * whitespace and comments.
     *
     * @param par  The reference node.
     * @return     Either:
     *               1) The first child of |sib| that is not
     *                  ignorable according to |is_ignorable|, or
     *               2) null if no such node exists.
     */
    function first_child(par) {
      var res = par.firstChild;
      while (res) {
        if (!is_ignorable(res)) return res;
        res = res.nextSibling;
      }
      return null;
    }

    /**
     * Version of |data| that doesn't include whitespace at the beginning
     * and end and normalizes all whitespace to a single space.  (Normally
     * |data| is a property of text nodes that gives the text of the node.)
     *
     * @param txt  The text node whose data should be returned
     * @return     A string giving the contents of the text node with
     *             whitespace collapsed.
     */
    function data_of(txt) {
      var data = txt.data;
      // Use ECMA-262 Edition 3 String and RegExp features
      data = data.replace(/[\t\n\r ]+/g, " ");
      if (data.charAt(0) == " ") data = data.substring(1, data.length);
      if (data.charAt(data.length - 1) == " ")
        data = data.substring(0, data.length - 1);
      return data;
    }

    function split_id(id) {
      const segs = id.split("_");
      let obj = { a: 0, b: 0, c: 0, d: 0 };
      if (segs[1]) obj.a = parseInt(segs[1]);
      if (segs[2]) obj.a = parseInt(segs[1]);
      if (segs[3]) obj.a = parseInt(segs[1]);
      if (segs[4]) obj.a = parseInt(segs[1]);
      return obj;
    }

    function compare_id(id1, id2) {
      let obj1 = split_id(id1);
      let obj2 = split_id(id2);
      return obj1.a - obj2.a
        ? obj1.a - obj2.a
        : obj1.b - obj2.b
        ? obj1.b - obj2.b
        : obj1.c - obj2.c
        ? obj1.c - obj2.c
        : obj1.d - obj2.d;
    }


    // CLASSES ////////////////////////////////////////////////////////////////////

    // NextNode: the node next to the statistics node when it is created
    function CStat(NextNode, InfoDiv) {
        this._HTML = '';
        this._reportInfoDiv = InfoDiv;
        this._gInfoList = [];
        this.iscurrentPage = true;

        this.nTotalPages = 0;
        this.nReadPages = 0;
        this.nReadRows = 0;
        this.nTotalRows = 0;
        this.setNode = function (newNode) {
            if (newNode.id == "stat_all") {
                this._Node = newNode;
                this._Node.innerHTML = '';
            }
            else {
                if (this._Node) {
                    var divs = newNode.parentNode.getElementsByTagName('div');
                    for (var i = 0; i < divs.length; i++) {
                        if (divs[i].id == "stat_all") {
                            newNode.parentNode.removeChild(divs[i]);
                            break;
                        }
                    }
                }

                var NewSection = document.createElement("div");
                NewSection.id = "stat_all";
                NewSection.className = "stat_all tab";
                if (newNode.parentNode)
                    this._Node = newNode.parentNode.insertBefore(NewSection, newNode);
                else {
                    this._Node = NewSection;
                    newNode.appendChild(NewSection);
                }
            }
            this._HTML = '';
        };
        this.setNode(NextNode);
        this._reportInfoDiv.appendChild(document.createElement('table'));
    }

    CStat.prototype._Write = function (Text) {
        this._HTML += Text;
    };

    CStat.prototype._Flush = function () {
        this._Node.innerHTML = this._HTML;
    };

    CStat.prototype.RegInfoList = function (InfoList) {
        if (InfoList instanceof CInfoList) {
            this._gInfoList.push(InfoList);
            return true;
        }
        return false;
    };

    CStat.prototype.SaveInfo = function (Info) {
        for (var i = 0; i < this._gInfoList.length; ++i)
            this._gInfoList[i].SaveInfo(Info);
    };

    CStat.prototype.getTab = function (isExport, showhide) {
        function FactoryTabClick(Id) {
            return function () {
                CStat.OnTabClick(Id);
            };
        }
        var tab = document.createElement('li');
        tab.className = 'not_selected';
        var title = Local.Text_Button_ShowAll;
        var id = 'showall';
        if (showhide === true) {
            title = Local.Text_Button_ShowAll;
            id = 'showall';
        }
        else {
            tab.id = 'tab_hideall';
            title = Local.Text_Button_HideAll;
            id = 'hideall';
        }
        tab.id = 'tab_' + id;
        var a = document.createElement('A');
        a.id = 'show_all_a';
        var name = document.createTextNode(title);
        a.appendChild(name);
        a.href = '#';
        if (isExport)
            a.setAttribute("onclick", "st('" + id + "');");
        else
            a.addEventListener("click", FactoryTabClick(id), false);
        tab.appendChild(a);
        return tab;
    };

    CStat.prototype.Show = function (isExport) {
        this._Node.innerHTML = '';
        var tabs = document.createElement("ul");
        this._Node.appendChild(tabs);
        var bar = document.createElement('div');
        bar.id = 'stat_bar';
        bar.className = 'bar';
        this._Node.appendChild(bar);
        var tabDivs = [];
        var isFirst = true;
        for (var i = 0; i < this._gInfoList.length; ++i) {
            var infoNode = this._gInfoList[i].Show(isExport);
            var tab = this._gInfoList[i].getTab(isExport);
            if (tab && infoNode) {
                tabs.appendChild(tab);
                this._Node.appendChild(infoNode);
                tabDivs.push(infoNode.id);
                if (isFirst) {
                    tab.className = 'selected';
                    infoNode.style.display = '';
                    isFirst = false;
                }
                else {
                    tab.className = 'not_selected';
                    infoNode.style.display = 'none';
                }
            }
        }
        tabs.appendChild(this.getTab(isExport, true));
        tabs.appendChild(this.getTab(isExport, false));
        this._Node.setAttribute('tabs', tabDivs.join(','));
        if (!isExport) {
            this._Node.appendChild(document.createElement('hr'));
            //this._Node.appendChild(this._reportInfoDiv);
            this.ShowProgress();
        }
        if (!this.iscurrentPage) {
            this._reportInfoDiv.style.display = 'none';
            this._Node.appendChild(this._reportInfoDiv);
        }
        //this._reportInfoDiv.parentNode.removeChild(this._reportInfoDiv);
    };

    CStat.prototype.ShowProgress = function () {
        var widthP = 1;
        var currentP = 1;
        if (reportInfoDiv) {
            if (this.nReadPages < 1)
                currentP = 1;
            else
                currentP = this.nReadPages
            if (this.nTotalPages === 0 || this.nTotalRows === 0)
                widthP = 1;
            else
                widthP = parseInt(((currentP - 1) / this.nTotalPages + this.nReadRows / (this.nTotalRows * this.nTotalPages)) * 100);
            reportInfoDiv.style.width = widthP + '%';
        }
    };

    CStat.prototype._AddEvents = function () {
        function OnDelGMValues() {
            try {
                var ValueList = GM_listValues();
                for (var name in ValueList) {
                    GM_deleteValue(ValueList[name]);
                }
                alert(Local.Text_DefaultMsg);
            } catch (e) {
                alert("OnDelGMValues(): " + e);
            }
        }
        document.getElementById("stat_options_default").addEventListener("click", OnDelGMValues, false);
    };
    CStat.OnTabClick = function (id) {
        var statdiv = document.getElementById('stat_all');
        var tabs = statdiv.getAttribute('tabs').split(',');
        var showall = false;
        var lishowall = document.getElementById('tab_showall');
        var lihideall = document.getElementById('tab_hideall');
        lishowall.className = 'not_selected';
        lihideall.className = 'not_selected';
        for (var i = 0; i < tabs.length; i++) {
            var tabid = tabs[i];
            var tab = document.getElementById(tabid);
            var li = document.getElementById('tab_' + tabid);
            li.className = 'not_selected';
            if (id == 'showall') {
                lishowall.className = 'selected';
                tab.style.display = '';
            }
            else if (id == 'hideall') {
                lihideall.className = 'selected';
                tab.style.display = 'none';
            }
            else {
                if (tabid == id) {
                    tab.style.display = '';
                    li.className = 'selected';
                }
                else {
                    tab.style.display = 'none';
                    li.className = 'not_selected';
                }
            }

        }
    };

    ///////////////////////////////////////////////////////////////////////////////
    function CTable(Title, Id, nColumns, isExport) {
        this._Title = Title;
        this._Id = Id;
        this._filterId = "filter_" + Id;
        this._nColumns = nColumns;
        this._HeadCellContents = new Array(nColumns);
        this._HeadCellLegends = new Array(nColumns);
        this._BodyCellContentTypes = new Array(nColumns);
        this._HeadCellContentFilters = [];
        this._BodyCellContents = [];
        this._HTML = '';
        this._isExport = isExport;
        this._bShow = true;
    }

    CTable._ContentAttrs = {
        string: 'left',
        number: 'right',
        button: 'center'
    };

    CTable.prototype.SetHeadCellContentFilters = function ( /* Content1, Content2, ... */) {
        for (var i = 0; i < arguments.length; ++i)
            if (arguments[i] != null)
                this._HeadCellContentFilters.push(arguments[i]);
    };

    CTable.prototype.SetHeadCellContents = function ( /* Content1, Content2, ... */) {
        for (var i = 0; i < this._nColumns; ++i)
            this._HeadCellContents[i] = arguments[i] != null ? arguments[i] : "";
    };

    CTable.prototype.SetHeadCellLegends = function ( /* Content1, Content2, ... */) {
        for (var i = 0; i < this._nColumns; ++i)
            this._HeadCellLegends[i] = arguments[i] != null ? arguments[i] : "";
    };

    // Type: a string that is the property name of CTable::ContentAttrs
    CTable.prototype.SetBodyCellContentTypes = function ( /* Type1, Type2, ... */) {
        for (var i = 0; i < this._nColumns; ++i)
            this._BodyCellContentTypes[i] =
                arguments[i] != null ? CTable._ContentAttrs[arguments[i]] : "";
    };

    CTable.prototype.SetBodyCellContents = function ( /* Content1, Content2, ... */) {
        var Contents = new Array(this._nColumns);
        for (var i = 0; i < this._nColumns; ++i)
            Contents[i] = arguments[i] != null ? arguments[i] : "";
        this._BodyCellContents.push(Contents);
    };

    CTable.prototype.CreateHTML = function () {
        function Factory(Id) {
            return function () {
                CTable.OnClickTitle(Id);
            };
        }
        function FactoryFilter(tableid, rowId) {
            return function () {
                CTable.OnChangeFilter(tableid, rowId);
            };
        }
        function FactorySort(tableid, colId, numberId) {
            return function () {
                CTable.OnChangeOrder(tableid, colId, numberId);
            };
        }
        function FactoryShowDetail(rowId, activeRows) {
            return function () {
                activeRows = activeRows.sort(compare_id)
                CTable.OnShowDetail(rowId, activeRows);
            };
        }
        var tableid = "table_" + this._Id;
        var outputDiv = document.createElement('div');
        outputDiv.id = this._Id;
        outputDiv.className = 'content';
        var headerDiv = document.createElement('div');
        outputDiv.appendChild(headerDiv);
        headerDiv.className = 'stat_header';
        headerDiv.style.textAlign = 'center';
        var spanTitle = document.createElement('span');
        spanTitle.className = "stat_title clickable";
        headerDiv.appendChild(spanTitle);
        spanTitle.innerHTML = this._Title;
        if (this._isExport)
            spanTitle.setAttribute('onclick', "ct('" + tableid + "');");
        else
            spanTitle.addEventListener("click", Factory(tableid), false);
        var table = document.createElement("table");
        table.className = "content_table";
        table.style.margin = '0px auto';
        table.id = tableid;
        if (!this._bShow)
            table.setAttribute('hide', 'hide');
        outputDiv.appendChild(table);

        var trHeader = table.insertRow(0);
        trHeader.className = "content_table_header";
        for (var i = 0; i < this._nColumns; ++i) {
            var th = document.createElement("th");
            th.className = "content_table stat_order";
            th.id = 'th_' + this._Id + i;
            th.setAttribute('order', '-1');
            var thSpan = document.createElement("span");
            thSpan.id = this._Id + "_col" + i;
            thSpan.innerHTML = this._HeadCellContents[i];
            th.appendChild(thSpan);
            var legend = this._HeadCellLegends[i];
            var infospan = document.createElement("span");
            infospan.id = tableid + '_orderInfo_' + i;
            infospan.innerHTML = '<span></span><span></span>';
            thSpan.appendChild(infospan);
            if (legend) {
                infospan.className = legend.className;
                if (i > 0) {
                    thSpan.innerHTML = thSpan.innerHTML + '<br />';
                    thSpan.appendChild(legend);
                    var legends = legend.getElementsByTagName('span');
                    for (var li = 0; li < legends.length; li++) {
                        var l = legends[li];
                        l.className = "clickable";
                        if (this._isExport)
                            l.setAttribute('onclick', "co('" + tableid + "'," + i + "," + li + ");");
                        else
                            l.addEventListener("click", FactorySort(tableid, i, li), false);
                    }
                }
                else {
                    th.appendChild(legend);
                    var checkboxs = legend.getElementsByTagName('input');
                    for (var ti = 0; ti < checkboxs.length; ti++) {
                        var c = checkboxs[ti];
                        c.id = tableid + '_checkbox_' + i + '_' + ti;
                        if (this._isExport) {
                            c.setAttribute('onclick', "cf('" + tableid + "','" + this._filterId + "');");
                            c.setAttribute('checked', '');
                        }
                        else
                            c.addEventListener("click", FactoryFilter(tableid, this._filterId), false);
                    }
                }
            }
            else {
                thSpan.className = "clickable";
                if (this._isExport)
                    thSpan.setAttribute('onclick', "co('" + tableid + "'," + i + ",0);");
                else
                    thSpan.addEventListener("click", FactorySort(tableid, i, 0), false);
            }
            trHeader.appendChild(th);
        }

        if (useFilter) {
            var trfilter = table.insertRow(-1);
            trfilter.id = this._filterId;
            trfilter.className = "content_table_filter_row";
            for (var i = 0; i < this._nColumns - 1; ++i) {
                var cell = trfilter.insertCell(-1);
                if (this._HeadCellContentFilters != null) {
                    if (this._HeadCellContentFilters[i] != null) {
                        var filter = this._HeadCellContentFilters[i];
                        var comboboxid = this._filterId + "_combobox_" + i;
                        var combobox = document.createElement("select");
                        cell.appendChild(combobox);
                        combobox.id = comboboxid;
                        if (this._isExport)
                            combobox.setAttribute('onchange', "cf('" + tableid + "','" + this._filterId + "');");
                        else
                            combobox.addEventListener("change", FactoryFilter(tableid, this._filterId), false);
                        combobox.options.add(new Option(Local.Text_Table_AllData, i + '_' + 'all'));
                        for (var j = 0; j < filter.length; j++)
                            combobox.options.add(new Option(filter[j], i + '_' + j));
                        var comboboxOrg = combobox.cloneNode(true);
                        comboboxOrg.id = 'org_' + comboboxid;
                        comboboxOrg.style.display = 'none';
                        cell.appendChild(comboboxOrg);
                    }
                    else {
                        var input = document.createElement("input");
                        var textid = this._filterId + "_textbox_" + i;
                        input.type = "text";
                        input.size = 6;
                        input.id = textid;
                        cell.appendChild(input);
                    }
                }
            }
            var searchButton = document.createElement("input");
            searchButton.type = 'button';
            searchButton.id = this._filterId + "_button";
            searchButton.value = "查询";
            searchButton.className = "button";
            if (this._isExport)
                searchButton.setAttribute('onclick', "cf('" + tableid + "','" + this._filterId + "');");
            else
                searchButton.addEventListener("click", FactoryFilter(tableid, this._filterId), false);
            var buttonCell = trfilter.insertCell(-1);
            buttonCell.appendChild(searchButton);
            buttonCell.style.textAlign = 'center';
        }
        for (var i = 0; i < this._BodyCellContents.length; ++i) {
            var row = table.insertRow(-1);
            row.className = "row" + i % 2;
            row.setAttribute('oriorder', i);
            var rowId = [];
            var infoRowid = tableid + '_' + i;
            var infoNode;
            for (var j = 0; j < this._nColumns; ++j) {
                var rowspan = "";
                var content = this._BodyCellContents[i][j];
                if (content.show) {
                    var cell = row.insertCell(-1);
                    cell.className = "content_table";
                    cell.style.textAlign = this._BodyCellContentTypes[j];
                    if (content.rowspan > 1) {
                        cell.rowSpan = content.rowspan;
                        cell.style.verticalAlign = 'middle';
                    }
                    var valueNode = content.value;
                    cell.appendChild(valueNode);
                    if (j == this._nColumns - 1) {
                        if (this._isExport)
                            valueNode.setAttribute("onclick", "sd('" + infoRowid + "',['" + content.activeRows.join("','") + "']);");
                        else
                            valueNode.addEventListener("click", FactoryShowDetail(infoRowid, this._BodyCellContents[i][0].activeRows), false);
                        infoNode = valueNode.getAttribute('data');
                        valueNode.removeAttribute('data');
                    }
                }
                rowId.push(j + "_" + this._BodyCellContents[i][j].filterId);
            }
            row.id = rowId.join(",");
            var infoRow = table.insertRow(-1);
            infoRow.className = row.className;
            infoRow.id = infoRowid;
            infoRow.style.display = 'none';
            var infoCell = infoRow.insertCell(-1);
            infoCell.colSpan = this._nColumns;
            if (infoNode) {
                infoCell.innerHTML = infoNode;
            }
        }
        return outputDiv;
        //this._HTML = outputDiv.outerHTML;
        //return     this._HTML;
    };

    CTable.prototype.GetHTML = function () {
        return this._HTML;
    };


    CTable.OnClickTitle = function (Id) {
        try {
            var Table = document.getElementById(Id);
            if (Table.hasAttribute("hide")) {
                Table.removeAttribute("hide");
            } else {
                Table.setAttribute("hide", "hide");
            }
        } catch (e) {
            alert("CTable.OnClickTitle(): " + e);
        }
    };

    CTable.GetNumber = function (cell) {
        var numberPatten = /^\s?([\d]+\.?[\d]*)\s?_?\s?([\d]*\.?[\d]*)\s?$/;
        var pairTable = cell.firstChild;
        var numberString = cell.textContent;
        var numbers;
        if (pairTable && pairTable.nodeName == "TABLE") {
            numberString = pairTable.id;
        }
        if (numberPatten.test(numberString)) {
            numbers = [];
            var numberres = numberPatten.exec(numberString);
            if (numberres[1])
                numbers.push(numberres[1]);
            if (numberres[2])
                numbers.push(numberres[2]);
        }
        return numbers;
    };
    CTable.OnChangeFilter = function (tableId, filterRowId) {
        try {
            var Table = document.getElementById(tableId);
            var filterRow = document.getElementById(filterRowId);
            var stringfilters = [];
            var orgstringfilters = [];
            var numberfilters = [];
            var filterString = "";
            var showIds = [];
            var refilter = 0;

            var showHero_0 = document.getElementById(tableId + "_checkbox_0_0");
            var showHero_1 = document.getElementById(tableId + "_checkbox_0_1");
            var showHero = [showHero_0.checked, showHero_1.checked];

            for (var i = 0; i < filterRow.cells.length; i++) {
                var cell = filterRow.cells[i];
                var stringfilter = document.getElementById(filterRow.id + "_combobox_" + i);
                var orgstringfilter = document.getElementById('org_' + filterRow.id + "_combobox_" + i);
                var numberfilter = document.getElementById(filterRow.id + "_textbox_" + i);
                if (stringfilter) {
                    stringfilters.push(stringfilter.value);
                    refilter += stringfilter.selectedIndex > 0 ? 1 : 0;
                }
                else
                    stringfilters.push(null);
                if (orgstringfilter) {
                    for (var ii = 0, ij = orgstringfilter.options.length; ii < ij; ++ii) {
                        if (orgstringfilter.options[ii].value === stringfilter.value) {
                            orgstringfilter.selectedIndex = ii;
                            break;
                        }
                    }
                    orgstringfilters.push([stringfilter, orgstringfilter]);
                    showIds.push([]);
                }
                if (numberfilter)
                    numberfilters.push(numberfilter.value);
                else
                    numberfilters.push(null);
            }
            var index = 0;
            var patten = /([\(|\[|>|<|=|]*)\s*([\d]*\.?[\d]*)\s*-?\s*([\d]*\.?[\d]*)\s*([\)|\]|\s]?)/;
            for (var i = 2; i < Table.rows.length; i = i + 2) {
                var row = Table.rows[i];
                var rowInfo = Table.rows[i + 1];
                var rowIds = row.id.split(",");
                var show = true;

                var hero = row.cells[0].getElementsByTagName('a')[0];
                var heroKind = hero.getAttribute("kind");
                show = showHero[heroKind];
                if (show) {
                    for (var fi = 0; fi < stringfilters.length; fi++) {
                        var sfilter = stringfilters[fi];
                        if (!sfilter)
                            continue;
                        if (sfilter != fi + "_all" && sfilter != rowIds[fi]) {
                            show = false;
                            break;
                        }
                    }
                }
                if (show) {
                    for (var fi = 0; fi < numberfilters.length; fi++) {
                        var nfilter = numberfilters[fi];

                        if (!nfilter)
                            continue;
                        else {
                            var numbers = CTable.GetNumber(row.cells[fi]);
                            var nfilters = nfilter.split(/\s*[,|,]\s*/);
                            for (ni = 0; ni < numbers.length; ni++) {
                                var theFilter = nfilters[ni];
                                var testString = "";
                                if (theFilter && patten.test(theFilter)) {
                                    var op = "==";
                                    var res = patten.exec(theFilter);
                                    if (res[1]) {
                                        op = res[1];
                                        if (res[3]) {
                                            if (op == "[") op = ">=";
                                            if (op == "(") op = ">";
                                            if (op == "=") op = "==";
                                        }
                                    }
                                    else {
                                        if (res[3])
                                            op = ">=";
                                    }
                                    testString = numbers[ni] + op + res[2];
                                    if (res[3]) {
                                        op = "<=";
                                        if (res[4]) {
                                            op = res[4];
                                            if (op == "]") op = "<=";
                                            if (op == ")") op = "<";
                                        }
                                        testString += " && " + numbers[ni] + op + res[3];
                                    }
                                    show = eval(testString);
                                    if (!show)
                                        break;
                                }
                            }
                        }
                        if (!show)
                            break;
                    }
                }
                row.style.display = show ? '' : 'none';
                rowInfo.style.display = show ? rowInfo.style.display : 'none';
                if (show) {
                    row.className = "row" + index % 2;
                    rowInfo.className = row.className;
                    index++;
                    for (var fi = 0; fi < orgstringfilters.length; fi++) {
                        var id = rowIds[fi];
                        if (showIds[fi].indexOf(id) <= -1)
                            showIds[fi].push(id);
                    }
                }
            }

            if (orgstringfilters.length - refilter > 1) {
                for (var fi = 0; fi < orgstringfilters.length; fi++) {
                    var sfilter = orgstringfilters[fi][0];
                    var sfilterorg = orgstringfilters[fi][1];
                    if (!sfilter)
                        continue;
                    if (refilter == 1 && sfilter.selectedIndex > 0)
                        continue;
                    for (var i = sfilter.options.length - 1; i > 0; i--)
                        sfilter.remove(i);
                    for (var i = 0; i < sfilterorg.options.length; i++) {
                        var opt = sfilterorg.options[i];
                        if (showIds[fi].indexOf(opt.value) > -1) {
                            var newopt = new Option(opt.text, opt.value);
                            newopt.selected = opt.selected;
                            sfilter.add(newopt);
                        }
                    }
                }
            }
        } catch (e) {
            alert("CTable.OnChangeFilter(): " + e);
        }
    };

    CTable.OnChangeOrder = function (tableId, columnIndex, numberIndex) {
        var Table = document.getElementById(tableId);
        var index = numberIndex;
        var ths = Table.getElementsByTagName("th");
        if (index === null)
            index = 0;
        var th = ths[columnIndex];
        var order = th.getAttribute("order");
        for (var i = 0; i < ths.length - 1; i++) {

            var span = document.getElementById(tableId + '_orderInfo_' + i);
            var spans = span.getElementsByTagName('span');
            if (spans && spans.length == 2) {
                spans[0].innerHTML = '';
                spans[1].innerHTML = '';
                if (i == columnIndex)
                    spans[numberIndex].innerHTML = order > 0 ? '&#9650;' : '&#9660;';
            }
        }
        for (var i = 2; i < Table.rows.length - 2; i = i + 2) {
            for (var j = i + 2; j < Table.rows.length; j = j + 2) {
                var row_1 = Table.rows[i];
                var row_1_info = Table.rows[i + 1];
                var row_2 = Table.rows[j];
                var row_2_info = Table.rows[j + 1];
                var cell_1 = row_1.cells[columnIndex];
                var cell_2 = row_2.cells[columnIndex];

                n1 = CTable.GetNumber(cell_1);
                n2 = CTable.GetNumber(cell_2);
                var change = false;

                if (columnIndex == ths.length - 1) {
                    var n11 = Number(row_1.getAttribute("oriorder"));
                    var n12 = Number(row_2.getAttribute("oriorder"));
                    change = n11 > n12;
                }
                else {
                    var c1 = row_1.cells[0].firstChild.className.replace("my", "");
                    var c2 = row_2.cells[0].firstChild.className.replace("my", "");
                    var s1 = cell_1.textContent;
                    var s2 = cell_2.textContent;
                    var cc = CompareString(c1, c2);
                    if (cc < 0)
                        change = false;
                    else if (cc > 0)
                        change = true;
                    else {
                        if (n1 && n2 && n1.length > 0 && n2.length > 0) {
                            var number_1 = n1[index] * order;
                            var number_2 = n2[index] * order;
                            change = number_1 > number_2;
                        }
                        else
                            change = (CompareString(s1, s2) == order);
                    }
                }

                if (change) {
                    row_2.parentNode.insertBefore(row_2, row_1);
                    row_2_info.parentNode.insertBefore(row_2_info, row_1);
                }
            }
            Table.rows[i].className = "row" + (i / 2) % 2;
            Table.rows[i + 1].className = Table.rows[i].className;
        }
        th.setAttribute("order", -1 * order);
    };

    CTable.OnShowDetail = function (rowid, activeRows) {
        var row = document.getElementById(rowid);
        var cell = row.cells[0];
        var button = row.previousSibling.getElementsByTagName('input')[0];
        activeRows = activeRows || [];
        if (cell) {
            if (row.style.display == '') {
                button.value = '显示';
                row.style.display = 'none';
            }
            else {
                button.value = '隐藏';
                row.style.display = '';
            }
            var table = cell.getElementsByTagName('table')[0];
            if (table.rows.length <= 1) {
                if (activeRows.length > 0) {
                    var ids = activeRows[0].split('_');
                    var level = Number(ids[1]);
                    var ac = table.insertRow(-1).insertCell(-1);
                    ac.colSpan = '3';
                    ac.innerHTML = '<hr/><br />层 ' + level + '<br /><hr/>';
                    for (var i = 0; i < activeRows.length; i++) {
                        ids = activeRows[i].split('_');
                        var newlevel = Number(ids[1]);
                        if (newlevel != level) {
                            ac = table.insertRow(-1).insertCell(-1);
                            ac.colSpan = '3';
                            ac.innerHTML = '<hr/><br />层 ' + newlevel + '<br /><hr/>';
                            level = newlevel;
                        }
                        var theRow = document.getElementById(activeRows[i]).cloneNode(true);
                        if (theRow) {
                            table.appendChild(theRow);
                            var c = table.insertRow(-1).insertCell(-1);
                            c.colSpan = '3';
                            c.innerHTML = '<hr/>';
                        }
                    }
                }
            }

        }

    };
    ///////////////////////////////////////////////////////////////////////////////
    function CActiveInfo() {
        this.nIniRoll;
        this.nCurrAction;
        this.nTotalActions;
        this.Char = new CChar();
        this.nCharId;
        this.ActionType = new CActionType();
        this.Skill = new CSkill();
        this.gAttackRoll;
        this.gPosition = new CKeyList();
        this.nSkillMP;
        this.nSkillHP;
        this.gItem = new CKeyList();
    }


    function CPassiveInfo() {
        this.Char = new CChar();
        this.nCharId;
        this.Skill = new CSkill();
        this.nDefenceRoll;
        this.nSkillMP;
        this.gItem = new CKeyList();
        this.HitType = new CHitType();
        this.bStruckDown;
        this.gDamage = [];
        this.DamagedItem = new CItem();
        this.nItemDamage;
        this.nHealedHP;
        this.nHealedMP;
    }


    function CNavi(nLevel, nRoom, nRound, nRow) {
        this.nLevel = nLevel;
        this.nRoom = nRoom;
        this.nRound = nRound;
        this.nRow = nRow;
        this.toString = function () {
            return this.nLevel + '_' + this.nRoom + '_' + this.nRound + '_' + this.nRow;
        };
    }


    function CActionInfo(Navi) {
        this.Navi = Navi;
        this.Active = new CActiveInfo();
        this.gPassive = [];
        this.ActiveRow = 'activeRow_' + this.Navi.toString();
    }

    function CActiveValue(ActiveRow, Value) {
        this.Value = Value;
        this.ActiveRow = ActiveRow;
    }
    ///////////////////////////////////////////////////////////////////////////////
    // Class: Key
    // Every key should have two function properties: compareTo() and toString(),
    //   and can work without initialization parameters

    var CKey = DefineClass({
        methods: {
            compareTo: function (that) {
                return this - that;
            },
            toString: function () {
                var theNode = this.toHTMLNode();
                return (theNode.nodeType == Node.COMMENT_NODE || theNode.nodeType == Node.TEXT_NODE) ? theNode.data : theNode.outerHTML;
            },
            toText: function () {
                return this.toHTMLNode().textContent;
            },
            toHTMLNode: function () {
                return document.createTextNode("");
            }
        }
    });
    CKey.UNKNOWN = -1;

    var CKeyList = DefineClass({
        extend: CKey,
        construct: function () {
            this._gKey = [];
        },
        methods: {
            push: function (Key) {
                return this._gKey.push(Key);
            },
            compareTo: function (that) {
                var result = this._gKey.length - that._gKey.length;
                if (result != 0)
                    return result;

                var i = 0;
                while (i < this._gKey.length && this._gKey[i].compareTo(that._gKey[i]) === 0)
                    ++i;
                if (i === this._gKey.length)
                    return 0;
                else
                    return this._gKey[i].compareTo(that._gKey[i]);
            },
            toHTMLNode: function () {
                var ret = document.createElement('span');
                ret.innerHTML = this._gKey.join(", ");
                return ret;
            }
        }
    });

    var CTypeKey = DefineClass({
        extend: CKey,
        construct: function (TypeText) {
            this._sType;
            this._nKind = CKey.UNKNOWN;
            this._text;
            if (TypeText != null) {
                this._sType = TypeText;
                this._text = TypeText;
                this.Init();
            }
        },
        methods: {
            Init: function () { },
            GetType: function () {
                return this._sType;
            },
            GetKind: function () {
                return this._nKind;
            },
            compareTo: function (that) {
                return CompareString(this._sType, that._sType);
            },
            toText: function () {
                return this._text;
            },
            setText: function (text) {
                this._text = text;
            },
            setKind: function (kind) {
                this._nKind = kind;
            },
            toHTMLNode: function () {
                return document.createTextNode(this._text);
            }
        }
    });

    // Attack position
    var CPositionType = DefineClass({
        extend: CTypeKey,
        construct: function (PositionText) {
            CTypeKey.call(this, PositionText);
        }
    });

    // Action type
    var CActionType = DefineClass({
        extend: CTypeKey,
        construct: function (ActionText) {
            CTypeKey.call(this, ActionText);
        },
        methods: {
            Init: function () {
                this._text = 'unknown';
                if (Local.OrigTextList_AttackActionType.indexOf(this._sType) > -1) {
                    this._nKind = CActionType.ATTACK;
                    this.setText(Local.TextList_AttackType[Local.OrigTextList_AttackActionType.indexOf(this._sType)]);
                }
                else if (Local.OrigTextList_HealActionType.indexOf(this._sType) > -1) {
                    this._nKind = CActionType.HEAL;
                    this.setText(Local.TextList_HealType);
                }
                else if (Local.OrigTextList_BuffActionType.indexOf(this._sType) > -1) {
                    this._nKind = CActionType.BUFF;
                    this.setText(Local.TextList_BuffType);
                }
                else if (Local.OrigTextList_WaitActionType.indexOf(this._sType) > -1) {
                    this._nKind = CActionType.WAIT;
                    this.setText(Local.TextList_WaitType);
                }
                else if (this._sType.indexOf("(") > -1) {
                    this._nKind = CActionType.ATTACK2;
                    this.setText("其他");
                }
            }
        }
    });
    CActionType.ATTACK = 0;
    CActionType.HEAL = 1;
    CActionType.BUFF = 2;
    CActionType.WAIT = 3;
    CActionType.ATTACK2 = 4;
    // hit type
    var CHitType = DefineClass({
        extend: CTypeKey,
        construct: function (HitClassText) {
            CTypeKey.call(this, HitClassText);
        },
        methods: {
            Init: function () {
                switch (this._sType) {
                    case "rep_miss":
                        this._nKind = CHitType.MISS;
                        break;
                    case "rep_hit":
                        this._nKind = CHitType.HIT;
                        break;
                    case "rep_hit_good":
                        this._nKind = CHitType.GOOD;
                        break;
                    case "rep_hit_crit":
                        this._nKind = CHitType.CRIT;
                        break;
                    default:
                        this._nKind = CKey.UNKNOWN;
                }
                if (this._nKind != CKey.UNKNOWN)
                    this.setText(Local.TextList_HitType[this._nKind]);
                else
                    this.setText('');

            },
            compareTo: function (that) {
                return this._nKind - that._nKind;
            }
        }
    });
    CHitType.MISS = 0;
    CHitType.HIT = 1;
    CHitType.GOOD = 2;
    CHitType.CRIT = 3;

    // heal type
    var CHealType = DefineClass({
        extend: CTypeKey,
        construct: function (HealText) {
            CTypeKey.call(this, HealText);
        },
        methods: {
            Init: function () {
                switch (this._sType) {
                    case "HP":
                        this._nKind = CHealType.HP;
                        break;
                    case "MP":
                        this._nKind = CHealType.MP;
                        break;
                    default:
                        this._nKind = CKey.UNKNOWN;
                }
            }
        }
    });
    CHealType.HP = 0;
    CHealType.MP = 1;

    // buff type
    var CBuffType = DefineClass({
        extend: CTypeKey,
        construct: function (BuffText) {
            CTypeKey.call(this, BuffText);
        }
    });

    // Damage Type
    var CDamageType = DefineClass({
        extend: CTypeKey,
        construct: function (DamageTypeText) {
            CTypeKey.call(this, DamageTypeText);
        },
        methods: {
            Init: function () {
                this.setText(this._sType.replace('伤害', ''));
            }
        }
    });

    var CElementKey = DefineClass({
        extend: CKey,
        construct: function (HTMLElement) {
            this._Name = '';
            this._Href;
            this._OnClick;
            this._Class;
            this._nKind = CKey.UNKNOWN;

            if (HTMLElement != null) {
                this._Name = HTMLElement.firstChild.data;
                this._Href = HTMLElement.href;
                this._OnClick = HTMLElement.getAttribute("onclick");
                this._Class = HTMLElement.className;
                this.Init();
            }
        },
        methods: {
            Init: function () { },
            GetKind: function () {
                return this._nKind;
            },
            compareTo: function (that) {
                var result = this._nKind - that._nKind;
                if (result !== 0)
                    return result;
                return CompareString(this._Name, that._Name);
            },
            toHTMLNode: function () {
                if (this._Name != null) {
                    var ret = document.createElement('A');
                    var name = document.createTextNode(this._Name);
                    ret.appendChild(name);
                    ret.href = this._Href;
                    ret.setAttribute("onclick", this._OnClick);
                    ret.setAttribute('kind', this._nKind);
                    if (this._Class)
                        ret.className = this._Class;
                    return ret;
                }
                else
                    return document.createTextNode('');
            },
            toText: function () {
                return this._Name;
            },
            setText: function (name) {
                this._Name = name;
            }
        }
    });

    var CChar = DefineClass({
        extend: CElementKey,
        construct: function (HTMLElement) {
            CElementKey.call(this, HTMLElement);
        },
        methods: {
            Init: function () {
                switch (this._Class) {
                    case "rep_hero":
                    case "rep_myhero":
                        this._nKind = CChar.HERO;
                        break;
                    case "rep_monster":
                    case "rep_myhero_defender":
                        this._nKind = CChar.MONSTER;
                        break;
                    default:
                        this._nKind = CKey.UNKNOWN;
                }
            }
        }
    });
    CChar.HERO = 0;
    CChar.MONSTER = 1;


    var CSkill = DefineClass({
        extend: CElementKey,
        construct: function (HTMLElement) {
            CElementKey.call(this, HTMLElement);
        }
    });


    var CItem = DefineClass({
        extend: CElementKey,
        construct: function (HTMLElement) {
            CElementKey.call(this, HTMLElement);
        }
    });

    var CDamage = DefineClass({
        extend: CKey,
        construct: function (HTMLElement) {
            this._nBasicDmg;
            this._nActualDmg;
            this._nArmor;
            this._sType;
            this._cType;

            if (HTMLElement != null) {
                var Str;
                if (HTMLElement.nodeType != Node.TEXT_NODE) {
                    Str = HTMLElement.getAttribute("onmouseover");
                    // \1    basic damage
                    var Patt_BasicDamage = Local.Pattern_BasicDamage;
                    var result = Patt_BasicDamage.exec(Str);
                    if (result == null)
                        throw "CDamage() :" + Str;
                    this._nBasicDmg = Number(result[1]);
                    Str = HTMLElement.firstChild.data;
                } else
                    Str = HTMLElement.data;

                // \1    actual damage
                // \2    armor
                // \3    damage type
                var Patt_Damage = Local.Pattern_Damage;
                var result = Patt_Damage.exec(Str);
                if (result == null)
                    throw "CDamage() :" + Str;
                this._nActualDmg = Number(result[1]);
                this._nArmor = result[2] != null ? Number(result[2]) : 0;
                this._sType = result[3] || "";
                this._cType = new CDamageType(this._sType);

                if (this._nBasicDmg == null)
                    this._nBasicDmg = this._nActualDmg + this._nArmor;
            }
        },
        methods: {
            GetType: function () {
                return this._sType;
            },
            GetDamageType: function () {
                return this._cType;
            },
            GetBasicDmg: function () {
                return this._nBasicDmg;
            },
            GetArmor: function () {
                return this._nArmor;
            },
            GetActualDmg: function () {
                return this._nActualDmg;
            },
            IsHPDamage: function () {
                return Local.OrigTextList_NoneHPDamageType.indexOf(this._sType) <= -1;
            },
            compareTo: function (that) {
                return this._nBasicDmg - that._nBasicDmg;
            },
            toHTMLNode: function () {
                var Str = '';
                if (this._sType != null) {
                    Str = String(this._nBasicDmg);
                    if (this._nArmor > 0)
                        Str += " - " + this._nArmor + " -> " + this._nActualDmg;
                    else if (this._nBasicDmg !== this._nActualDmg)
                        Str += " -> " + this._nActualDmg;
                    Str += " " + this._sType;
                }
                document.createTextNode(Str);
            }
        }
    });


    ///////////////////////////////////////////////////////////////////////////////
    // Class: Value list
    // Value list is a special key, it can contains any type of values, including keys

    var CValueList = DefineClass({
        extend: CKey,
        construct: function () {
            this._gValue = [];
            this._nValue = [];
            this._ActiveValue = [];
            this._nAvgValue; // unsure type
            this._nMaxValue; // unsure type
            this._nMinValue; // unsure type
            this._nSTDValue; // unsure type
            this._nTotalValue; // unsure type
        },
        methods: {
            GetLength: function () {
                return document.createTextNode(this._gValue.length);
            },
            Calculate: function () { },
            push: function (ActiveRow, Value) {
                this._nValue.push(Value);
                if (this._ActiveValue.indexOf(ActiveRow) <= -1)
                    this._ActiveValue.push(ActiveRow);
                var activeValue = new CActiveValue(ActiveRow, Value);
                return this._gValue.push(activeValue);
            },
            compareTo: function (that) {
                return this._nAvgValue - that._nAvgValue;
            },
            AvgValue: function () {
                return this.getNode(this._nAvgValue);
            },
            MaxValue: function () {
                return this.getNode(this._nMaxValue);
            },
            MinValue: function () {
                return this.getNode(this._nMinValue);
            },
            STDValue: function () {
                return this.getNode(this._nSTDValue);
            },
            TotalValue: function () {
                return this.getNode(this._nTotalValue);
            },
            ActiveValue: function () {
                return this._ActiveValue;
            },
            toHTMLNode: function () {
                var table = document.createElement('table');
                table.style.width = '100%';
                var cell = table.insertRow(-1).insertCell(-1);
                cell.colSpan = "3";
                cell.style.textAlign = 'center';
                var ret = document.createElement("span");
                var value = this.sortValue(this._nValue);
                ret.innerHTML = value.join(", ");
                cell.appendChild(ret);
                return table;
            },
            sortValue: function (value) {
                return value.sort();
            },
            getNode: function (data) {
                return document.createTextNode(String(data));
            },
            Legend: function () {
                return null;
            }
        }
    });

    var CVLString = DefineClass({
        extend: CValueList,
        construct: function () {
            CValueList.call(this);
        },
        methods: {
            Calculate: function () {
                this._nAvgValue = "";
                this._nMaxValue = "";
                this._nMinValue = "";
                this._nSTDValue = "";
                this._nTotalValue = "";
            },
            compareTo: function (that) {
                return 0;
            },
            toHTMLNode: function () {
                var table = document.createElement('table');
                table.style.width = '100%';
                var cell = table.insertRow(-1).insertCell(-1);
                cell.colSpan = "3";
                cell.style.textAlign = 'center';
                return table;
            }
        }
    });

    var CVLNumber = DefineClass({
        extend: CValueList,
        construct: function () {
            CValueList.call(this);
        },
        methods: {
            Calculate: function () {
                this._nTotalValue = 0;
                for (var i = 0; i < this._nValue.length; ++i)
                    this._nTotalValue += Number(this._nValue[i]);

                this._nAvgValue = Number((this._nTotalValue / this._gValue.length)).toFixed(2);
                this._nMaxValue = getMax(this._nValue);
                this._nMinValue = getMin(this._nValue);
                this._nSTDValue = getSTD(this._nValue);
            },
            sortValue: function (value) {
                return value.sort(function (a, b) { return b - a; });
            }
        }
    });


    // value: [Number1, Number2]
    var CVLPairNumber = DefineClass({
        extend: CValueList,
        construct: function () {
            CValueList.call(this);
            this._nTotalValue = [0, 0];
            this.gValueZero = [];
            this.gValueFirst = [];
            this.nSorting = [];
        },
        methods: {
            Calculate: function () {
                this.setPair();
                this.doCalculate();
            },
            setPair: function () {
                for (var i = 0; i < this._nValue.length; ++i) {
                    var theValue = this._nValue[i];
                    this.gValueZero.push(theValue[0]);
                    this.gValueFirst.push(theValue[1]);
                    this._nTotalValue[0] += theValue[0];
                    this._nTotalValue[1] += theValue[1];
                    this.nSorting.push(theValue[0], theValue[1]);
                };
            },
            doCalculate: function () {
                this._nAvgValue = new Array(2);
                this._nAvgValue[0] = Number((this._nTotalValue[0] / this._gValue.length).toFixed(2));
                this._nAvgValue[1] = Number((this._nTotalValue[1] / this._gValue.length).toFixed(2));
                this._nMaxValue = new Array(2);
                this._nMaxValue[0] = getMax(this.gValueZero);
                this._nMaxValue[1] = getMax(this.gValueFirst);
                this._nMinValue = new Array(2);
                this._nMinValue[0] = getMin(this.gValueZero);
                this._nMinValue[1] = getMin(this.gValueFirst);
                this._nSTDValue = new Array(2);
                this._nSTDValue[0] = getSTD(this.gValueZero);
                this._nSTDValue[1] = getSTD(this.gValueFirst);
            },
            compareTo: function (that) {
                if (this._nAvgValue[0] !== 0 || that._nAvgValue[0] !== 0)
                    return this._nAvgValue[0] - that._nAvgValue[0];
                else
                    return this._nAvgValue[1] - that._nAvgValue[1];
            },
            toHTMLNode: function () {
                var table = document.createElement('table');
                table.style.width = '100%';
                var cell = table.insertRow(-1).insertCell(-1);
                cell.style.textAlign = 'center';
                cell.colSpan = "3";
                this.nSorting.sort(function (a, b) { return b[0] - a[0]; });
                for (var i = 0; i < this.nSorting.length; ++i) {
                    var ret = document.createElement("span");
                    ret.className = "pair_value";
                    var theValue = this.nSorting[i];
                    var s = document.createElement('span');
                    s.innerHTML = (theValue[0] != null) ? theValue[0] : 0;
                    ret.appendChild(s);
                    s = document.createElement('span');
                    s.innerHTML = ' [ ' + ((theValue[1] != null) ? theValue[1] : 0) + ' ]';
                    ret.appendChild(s);
                    if (i < this.nSorting.length - 1)
                        ret.appendChild(document.createTextNode(', '));
                    cell.appendChild(ret);
                }
                return table;
            },
            getNode: function (data) {
                var table = document.createElement('table');
                table.className = "pair_value";
                table.id = data[0] + "_" + data[1];
                var row = table.insertRow(0);
                var cell1 = row.insertCell(0);
                var cell2 = row.insertCell(1);
                cell1.innerHTML = String(data[0]);
                cell2.innerHTML = String(data[1]);
                return table;
            },
            Legend: function () {
                var infospan = document.createElement("span");
                infospan.className = "pair_value";
                infospan.innerHTML = '<span>ROLL点</span><span>&bnsp;&bnsp;实际值</span>: ';
                return infospan;
            }
        }
    });


    // value: An Array of CDamage
    var CVLDamage = DefineClass({
        extend: CVLPairNumber,
        construct: function () {
            CVLPairNumber.call(this);
        },
        methods: {
            setPair: function () {
                for (var i = 0; i < this._nValue.length; ++i) {
                    var nSumOneAtkValue = [0, 0];
                    var theActiveValue = this._nValue[i];
                    for (var j = 0; j < theActiveValue.length; ++j) {
                        var theValue = theActiveValue[j];
                        this._nTotalValue[0] += theValue.GetBasicDmg();
                        this._nTotalValue[1] += theValue.GetActualDmg();
                        nSumOneAtkValue[0] = nSumOneAtkValue[0] + theValue.GetBasicDmg();
                        nSumOneAtkValue[1] = nSumOneAtkValue[1] + theValue.GetActualDmg();
                    }
                    this.gValueZero.push(nSumOneAtkValue[0]);
                    this.gValueFirst.push(nSumOneAtkValue[1]);
                    this.nSorting.push(nSumOneAtkValue);
                }
            }
        }
    });
    ///////////////////////////////////////////////////////////////////////////////
    // Class: Info list
    function CCellContent(value, rowspan, show, filterId, activeRows) {
        this.value = value;
        this.rowspan = rowspan;
        this.show = show;
        this.filterId = filterId;
        this.activeRows = activeRows || [];
    }

    function CKeyType(name, type, legend) {
        this.Name = name;
        this.Type = type;
        this.legend = legend ? legend.cloneNode(true) : null;
        this.getValue = function (info) {
            var value = info.ValueList;
            switch (this.Name) {
                case Local.Text_Table_AvgRoll:
                case Local.Text_Table_ItemDamagePoints:
                    return value.AvgValue();
                case Local.Text_Table_Times:
                    return value.GetLength();
                case Local.Text_Table_MaxRoll:
                    return value.MaxValue();
                case Local.Text_Table_MinRoll:
                    return value.MinValue();
                case Local.Text_Table_STDRoll:
                    return value.STDValue();
                case Local.Text_Table_Total:
                    return value.TotalValue();
                case Local.Text_Table_RollList:
                case Local.Text_Table_DetailList:
                    var ret = document.createElement("input");
                    ret.type = "button";
                    ret.className = "button";
                    ret.value = Local.Text_Button_Show;
                    ret.setAttribute("data", value.toString());
                    return ret;
                default:
                    return document.createTextNode(this.Name);
            }
        };
    }

    CKeyType.AvgRoll = function (legend) {
        return new CKeyType(Local.Text_Table_AvgRoll, "number", legend);
    };

    CKeyType.Times = function (legend) {
        return new CKeyType(Local.Text_Table_Times, "number", legend);
    };

    CKeyType.MaxRoll = function (legend) {
        return new CKeyType(Local.Text_Table_MaxRoll, "number", legend);
    };

    CKeyType.MinRoll = function (legend) {
        return new CKeyType(Local.Text_Table_MinRoll, "number", legend);
    };

    CKeyType.STDRoll = function (legend) {
        return new CKeyType(Local.Text_Table_STDRoll, "number", legend);
    };

    CKeyType.TotalRoll = function (legend) {
        return new CKeyType(Local.Text_Table_Total, "number", legend);
    };

    CKeyType.RollList = function (legend) {
        return new CKeyType(Local.Text_Table_RollList, "button", legend);
    };

    CKeyType.DetailList = function (legend) {
        return new CKeyType(Local.Text_Table_DetailList, "button", legend);
    };

    CKeyType.Char = function (legend) {
        var _legend = legend;
        if (!legend) {
            var info = document.createElement("table");
            info.className = 'pair_hero';
            var hero = info.insertRow(-1).insertCell(-1);
            var check = document.createElement("INPUT");
            check.type = 'checkbox';
            check.value = CChar.HERO;
            check.checked = true;
            hero.appendChild(check);
            hero.appendChild(document.createTextNode('英雄'));
            var npc = info.rows[0].insertCell(-1);
            check = document.createElement("INPUT");
            check.type = 'checkbox';
            check.value = CChar.MONSTER;
            check.checked = true;
            npc.appendChild(check);
            npc.appendChild(document.createTextNode('对手'));
            _legend = info;
        }

        return new CKeyType(Local.Text_Table_Char, "string", _legend);
    };

    CKeyType.ProviderChar = function (legend) {
        return new CKeyType(Local.Text_Table_Buffer, "string", legend);
    };

    CKeyType.ReceiverChar = function (legend) {
        return new CKeyType(Local.Text_Table_BuffeReceiver, "string", legend);
    };

    CKeyType.AttackType = function (legend) {
        return new CKeyType(Local.Text_Table_AttackType, "string", legend);
    };

    CKeyType.Skill = function (legend) {
        return new CKeyType(Local.Text_Table_Skill, "string", legend);
    };

    CKeyType.Item = function (legend) {
        return new CKeyType(Local.Text_Table_Item, "string", legend);
    };

    CKeyType.Position = function (legend) {
        return new CKeyType(Local.Text_Table_Position, "string", legend);
    };

    CKeyType.HealType = function (legend) {
        return new CKeyType(Local.Text_Table_HealType, "string", legend);
    };

    CKeyType.BuffType = function (legend) {
        return new CKeyType(Local.Text_Table_BuffType, "string", legend);
    };

    CKeyType.DamageType = function (legend) {
        return new CKeyType(Local.Text_Table_DamageType, "string", legend);
    };

    CKeyType.DefenceType = function (legend) {
        return new CKeyType(Local.Text_Table_DefenceType, "string", legend);
    };

    CKeyType.ItemDamagePoints = function (legend) {
        return new CKeyType(Local.Text_Table_ItemDamagePoints, "string", legend);
    };

    CKeyType.ValueName = function (legend) {
        return [CKeyType.AvgRoll(legend), CKeyType.Times(), CKeyType.MaxRoll(legend), CKeyType.MinRoll(legend), CKeyType.STDRoll(legend), CKeyType.RollList()];
    };

    CKeyType.TotalValueName = function (legend) {
        return [CKeyType.TotalRoll(legend), CKeyType.AvgRoll(legend), CKeyType.Times(), CKeyType.MaxRoll(legend), CKeyType.MinRoll(legend), CKeyType.STDRoll(legend), CKeyType.RollList()];
    };

    var CInfoList = DefineClass({
        construct: function (CValueList, Title, Id, gKeyName, gValueName) {
            this._gInfo = [];
            this._gKeyName = gKeyName || [];
            this._nKeys = this._gKeyName.length;
            this._CValueList = CValueList || [];
            this._Table = null;
            this._Title = Title || "";
            this._Id = Id || "";
            this._gValueName = gValueName || [];
            this._Allkey = this._gKeyName.concat(this._gValueName);
        },
        methods: {
            _CompareKeys: function (gKeyA, gKeyB) {
                for (var i = 0; i < this._nKeys; ++i) {
                    var result = gKeyA[i].compareTo(gKeyB[i]);
                    if (result !== 0)
                        return result;
                }
                return 0;
            },
            _SetTableBodyCellContents: function () {
                if (this._gInfo.length <= 0)
                    return;
                var tablecontent = [];
                var keys = this._gInfo[0].gKey.length;
                var filters = new Array(keys);
                for (var i = 0; i < keys; i++) {
                    var filter = [];
                    filters[i] = filter;
                }
                for (var i = 0; i < this._gInfo.length; ++i) {
                    for (var j = 0; j < this._gInfo[i].gKey.length; ++j) {
                        var value = this._gInfo[i].gKey[j];
                        var filter = value.toText();
                        if (filters[j].indexOf(filter) <= -1)
                            filters[j].push(filter);
                    }
                }
                for (var i = 0; i < this._gInfo.length; ++i) {
                    var gBodyCellContent = [];
                    for (var j = 0; j < this._gInfo[i].gKey.length; ++j) {
                        if (j > 0)
                            filters[j].sort(CompareString);
                        var value = this._gInfo[i].gKey[j];
                        var filter = value.toText();
                        gBodyCellContent.push(new CCellContent(value.toHTMLNode(), 1, true, filters[j].indexOf(filter), this._gInfo[i].ValueList.ActiveValue()));
                    }
                    for (var j = 0; j < this._gValueName.length; ++j)
                        gBodyCellContent.push(new CCellContent(this._gValueName[j].getValue(this._gInfo[i]), 1, true, -1, this._gInfo[i].ValueList.ActiveValue()));

                    tablecontent.push(gBodyCellContent);
                }

                this._Table.SetHeadCellContentFilters.apply(this._Table, filters);

                if (groupData) {
                    for (var i = tablecontent.length - 1; i > 0; i--) {
                        for (var j = 0; j < keys; j++) {
                            if (tablecontent[i][j].value.compareTo(tablecontent[i - 1][j].value) === 0) {
                                tablecontent[i][j].show = false;
                                tablecontent[i - 1][j].rowspan = tablecontent[i][j].rowspan + 1;
                            }
                            else
                                break;
                        }
                    }
                }
                for (var i = 0; i < tablecontent.length; i++)
                    this._Table.SetBodyCellContents.apply(this._Table, tablecontent[i]);
            },
            SaveInfo: function (Info) { },
            Show: function (isExport) {
                if (this._gInfo.length > 0) {
                    this.CalculateValue();
                    this.sort();
                    return this.CreateTable(isExport);
                }
                return "";
            },
            getTab: function (isExport) {
                function FactoryTabClick(Id) {
                    return function () {
                        CStat.OnTabClick(Id);
                    };
                }
                var tab = document.createElement('li');
                tab.id = 'tab_' + this._Id;
                tab.className = 'not_selected';
                var a = document.createElement('A');
                var name = document.createTextNode(this._Title);
                a.appendChild(name);
                a.href = '#';
                if (isExport)
                    a.setAttribute("onclick", "st('" + this._Id + "');");
                else
                    a.addEventListener("click", FactoryTabClick(this._Id), false);
                tab.appendChild(a);
                return tab;
            },
            // Call this function when read all data, and before sort and export data
            CalculateValue: function () {
                for (var i = 0; i < this._gInfo.length; ++i)
                    this._gInfo[i].ValueList.Calculate();
            },
            CreateTable: function (isExport) {
                // Key1, Key2, ..., AverageValue, Times, MaxValue, MinValue, STDValue, ValueList
                this._Table = new CTable(this._Title, this._Id, this._Allkey.length, isExport);

                var gHeadCellContent = new Array(this._Allkey.length);
                var gHeadCellLegend = new Array(this._Allkey.length);
                var gBodyCellContentType = new Array(this._Allkey.length);
                for (var i = 0; i < this._Allkey.length; ++i) {
                    gHeadCellContent[i] = this._Allkey[i].Name;
                    gBodyCellContentType[i] = this._Allkey[i].Type;
                    gHeadCellLegend[i] = this._Allkey[i].legend;
                }

                this._Table.SetHeadCellContents.apply(this._Table, gHeadCellContent);
                this._Table.SetHeadCellLegends.apply(this._Table, gHeadCellLegend);
                this._Table.SetBodyCellContentTypes.apply(this._Table, gBodyCellContentType);

                this._SetTableBodyCellContents();

                return this._Table.CreateHTML();
            },
            // Call this function when edited the info list (for example, re-sorted it)
            ReCreateTableHTML: function () {
                this._SetTableBodyCellContents();
                return this._Table.CreateHTML();
            },
            push: function (ActiveRow, gKey, Value) {
                for (var i = 0; i < this._gInfo.length; ++i) {
                    if (this._CompareKeys(this._gInfo[i].gKey, gKey) === 0) {
                        this._gInfo[i].ValueList.push(ActiveRow, Value);
                        return this._gInfo.length;
                    }
                }

                var ValueList = new this._CValueList();
                ValueList.push(ActiveRow, Value);
                return this._gInfo.push(new CInfoList._CInfo(gKey, ValueList));
            },
            sort: function (gSortKeyId) {
                function Factory(gId) {
                    return function (A, B) {
                        return CInfoList._CompareInfo(A, B, gId);
                    };
                }
                return this._gInfo.sort(Factory(gSortKeyId));
            }
        },
        statics: {
            _CInfo: function (gKey, ValueList) {
                this.gKey = gKey;
                this.ValueList = ValueList;
            },
            // SortKeyId: Id of keys, or null
            // The list will be sorted in this way: sort them by the first key, if there are
            //   elements are still equal, then sort them by the second key, and so on.
            // If SortKeyId is null, then sort the list by value
            // If gSortKeyId is null, then sort the list by default order of keys
            _CompareInfo: function (InfoA, InfoB, gSortKeyId) {
                if (gSortKeyId == null) {
                    for (var i = 0; i < InfoA.gKey.length; ++i) {
                        var result = InfoA.gKey[i].compareTo(InfoB.gKey[i]);
                        if (result !== 0) return result;
                    }
                    return 0;
                } else {
                    for (var i = 0; i < gSortKeyId.length; ++i) {
                        var KeyId = gSortKeyId[i];
                        var result = (KeyId != null) ?
                            InfoA.gKey[KeyId].compareTo(InfoB.gKey[KeyId]) :
                            InfoA.ValueList.compareTo(InfoB.ValueList);
                        if (result !== 0) return result;
                    }
                    return 0;
                }
            }
        }
    });


    ///////////////////////////////////////////////////////////////////////////////
    // Sub classes of CInfoList
    //
    // var CIL_ = DefineClass({
    //    extend: CInfoList,
    //    construct: function(_nKeys, CValueList) {this.superclass(_nKeys, CValueList);},
    //    methods:
    //        {
    //        _SetTableBodyCellContents: function() {},
    //        SaveInfo: function(Info) {},
    //        Show: function() {},
    //        CreateTable: function(Title, Id, gKeyName) {}
    //        }
    //    });

    var CILIni = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Ini, "stat_ini", [CKeyType.Char()],
                CKeyType.ValueName());
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.nCurrAction === 1)
                    this.push(Info.ActiveRow, [Info.Active.Char], Info.Active.nIniRoll);
            }
        }
    });


    var CILAttackRoll = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Attack, "stat_attack", [CKeyType.Char(), CKeyType.AttackType(), CKeyType.Skill(), CKeyType.Item(), CKeyType.Position()],
                CKeyType.ValueName());
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.ATTACK && Info.Active.gAttackRoll.length != 0) {
                    for (var i = 0; i < Info.Active.gAttackRoll.length; ++i) {
                        this.push(Info.ActiveRow, [Info.Active.Char, Info.Active.ActionType, Info.Active.Skill, Info.Active.gItem, Info.Active.gPosition._gKey[i]],
                            Info.Active.gAttackRoll[i]);
                    }
                }
            }
        }
    });


    var CILDefenceRoll = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Defence, "stat_defence", [CKeyType.Char(), CKeyType.DefenceType(), CKeyType.Skill(), CKeyType.Item()],
                CKeyType.ValueName());
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.ATTACK) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].nDefenceRoll != null)
                            this.push(Info.ActiveRow, [Info.gPassive[i].Char, Info.Active.ActionType,
                            Info.gPassive[i].Skill, Info.gPassive[i].gItem], Info.gPassive[i].nDefenceRoll);
                    }
                }
            }
        }
    });


    var CILDamage = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            var infospan = document.createElement("span");
            infospan.className = "pair_value";
            infospan.innerHTML = '<span>ROLL点</span>&nbsp;&nbsp;<span>实际值</span>';

            this.superclass(CValueList, Local.Text_Table_Damage, "stat_damage", [CKeyType.Char(), CKeyType.AttackType(), CKeyType.Skill(), CKeyType.Item(), CKeyType.DamageType()],
                CKeyType.TotalValueName(infospan));
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.ATTACK) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].gDamage.length > 0) {
                            //var damage = [];
                            //damage.push(Info.gPassive[i].gDamage);
                            for (var index = 0; index < Info.gPassive[i].gDamage.length; index++) {
                                this.push(Info.ActiveRow, [Info.Active.Char, Info.Active.ActionType, Info.Active.Skill, Info.Active.gItem, Info.gPassive[i].gDamage[index].GetDamageType()], [Info.gPassive[i].gDamage[index]]);
                            }
                        }

                    }
                }
            }
        }
    });

    var CILDamaged = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            var infospan = document.createElement("span");
            infospan.className = "pair_value";
            infospan.innerHTML = '<span>ROLL点</span>&nbsp;&nbsp;<span>实际值</span>';

            this.superclass(CValueList, Local.Text_Table_Damaged, "stat_damaged", [CKeyType.Char(), CKeyType.AttackType(), CKeyType.Skill(), CKeyType.Item(), CKeyType.DamageType()],
                CKeyType.TotalValueName(infospan));
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.ATTACK) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].gDamage.length > 0) {
                            //var damage = [];
                            //damage.push(Info.gPassive[i].gDamage);
                            for (var index = 0; index < Info.gPassive[i].gDamage.length; index++) {
                                this.push(Info.ActiveRow, [Info.gPassive[i].Char, Info.Active.ActionType, Info.Active.Skill, Info.Active.gItem, Info.gPassive[i].gDamage[index].GetDamageType()], [Info.gPassive[i].gDamage[index]]);
                            }
                        }

                    }
                }
            }
        }
    });

    var CILHeal = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Heal, "stat_heal", [CKeyType.Char(), CKeyType.Skill(), CKeyType.Item(), CKeyType.HealType()],
                CKeyType.TotalValueName());
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.HEAL) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].nHealedHP != null)
                            this.push(Info.ActiveRow, [Info.Active.Char, Info.Active.Skill, Info.Active.gItem, new CHealType('HP')], [Number(Info.gPassive[i].nHealedHP)]);

                        if (Info.gPassive[i].nHealedMP != null)
                            this.push(Info.ActiveRow, [Info.Active.Char, Info.Active.Skill, Info.Active.gItem, new CHealType('MP')], [Number(Info.gPassive[i].nHealedMP)]);
                    }
                }
            }
        }
    });

    var CILHealed = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Healed, "stat_healed", [CKeyType.Char(), CKeyType.HealType()],
                CKeyType.TotalValueName());
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.HEAL) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].nHealedHP != null)
                            this.push(Info.ActiveRow, [Info.gPassive[i].Char, new CHealType('HP')], [Number(Info.gPassive[i].nHealedHP)]);

                        if (Info.gPassive[i].nHealedMP != null)
                            this.push(Info.ActiveRow, [Info.gPassive[i].Char, new CHealType('MP')], [Number(Info.gPassive[i].nHealedMP)]);
                    }
                }
            }
        }
    });

    var CILBuff = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Buff, "stat_buff", [CKeyType.Char(), CKeyType.Skill(), CKeyType.Item(), CKeyType.BuffType()],
                [CKeyType.Times(), CKeyType.DetailList()]);
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.BUFF) {
                    this.push(Info.ActiveRow, [Info.Active.Char, Info.Active.Skill, Info.Active.gItem, new CBuffType(Info.Active.nIniRoll == null ? '回合前' : '回合中')], [0]);
                }
            }
        }
    });
    var CILBuffed = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_Buffed, "stat_buffed", [CKeyType.Char(), CKeyType.Skill(), CKeyType.Item(), CKeyType.BuffType()],
                [CKeyType.Times(), CKeyType.DetailList()]);
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.BUFF) {
                    for (var i = 0; i < Info.gPassive.length; ++i)
                        this.push(Info.ActiveRow, [Info.gPassive[i].Char, Info.Active.Skill, Info.Active.gItem, new CBuffType(Info.Active.nIniRoll == null ? '回合前' : '回合中')], [0]);
                }
            }
        }
    });


    var CILItemDamage = DefineClass({
        extend: CInfoList,
        construct: function (CValueList) {
            this.superclass(CValueList, Local.Text_Table_DamagedItems, "stat_item_damage", [CKeyType.Char(), CKeyType.Item()], [CKeyType.TotalRoll(), CKeyType.Times(), CKeyType.ItemDamagePoints(), CKeyType.RollList()]);
        },
        methods: {
            SaveInfo: function (Info) {
                if (Info.Active.ActionType.GetKind() === CActionType.ATTACK) {
                    for (var i = 0; i < Info.gPassive.length; ++i) {
                        if (Info.gPassive[i].nItemDamage != null)
                            this.push(Info.ActiveRow, [Info.gPassive[i].Char, Info.gPassive[i].DamagedItem],
                                Info.gPassive[i].nItemDamage);
                    }
                }
            }
        }
    });


    // FUNCTIONS //////////////////////////////////////////////////////////////////
    function CountStat(page, bLastSubPage, alsoSaveEntire) {
        // Read the last round only when reading the last sub page
        if (!bLastSubPage) RemoveLastRound(page);
        var nLevel = GetHiddenInfo(page, "current_level", 1);
        var ret = GetRepPageInfo(page, [1, 1]);
        var nCurrRepPage = ret[0];

        var Navi = new CNavi(nLevel, nCurrRepPage, 0, 0);
        var allRows = page.getElementsByTagName("tr");
        Stat.nTotalRows = allRows.length;
        for (var i = 0; i < allRows.length; ++i) {
            Stat.nReadRows = i + 1;
            var Info = new CActionInfo(Navi);
            var IniColumn = first_child(allRows[i]);
            if (!GetIniInfo(IniColumn, Info))
                continue;
            Navi.nRow++;
            allRows[i].setAttribute('id', Info.ActiveRow);
            if (Stat.iscurrentPage == false) {
                Stat._reportInfoDiv.firstChild.appendChild(allRows[i].cloneNode(true));
            }
            if (alsoSaveEntire && StatEntire.iscurrentPage == false) {
                StatEntire._reportInfoDiv.firstChild.appendChild(allRows[i].cloneNode(true));
            }

            var ActiveColumn = node_after(IniColumn);
            GetActiveInfo(ActiveColumn, Info);

            switch (Info.Active.ActionType.GetKind()) {
                case CActionType.ATTACK: // Attack
                    {
                        var PassiveColumn = node_after(ActiveColumn);
                        if (PassiveColumn)
                            GetAttackedInfo(PassiveColumn, Info);
                        break;
                    }
                case CActionType.HEAL: // Heal
                    {
                        if (includeFilter.Healed) {
                            var PassiveColumn = node_after(ActiveColumn);
                            GetHealedBuffedInfo(PassiveColumn, Info);
                            break;
                        }
                    }
                case CActionType.BUFF: // Buff
                    {
                        if (includeFilter.Buffed) {
                            var PassiveColumn = node_after(ActiveColumn);
                            GetHealedBuffedInfo(PassiveColumn, Info);
                            break;
                        }
                    }
                case CActionType.WAIT: // Wait
                default: // Unknown
                    ;
            }
            Stat.SaveInfo(Info);
            if (alsoSaveEntire)
                StatEntire.SaveInfo(Info);
        };
    }


    function RemoveLastRound(page) {
        var allRows = page.getElementsByTagName("tr");
        for (var i = 0; i < allRows.length; ++i) {
            if (allRows[i].className != null &&
                rValue.Pattern_logRow.test(allRows[i].className)) {
                var allH1 = allRows[i].getElementsByTagName("h1");
                if (allH1[0] != null &&
                    allH1[0].firstChild != null &&
                    allH1[0].firstChild.nodeType == Node.TEXT_NODE &&
                    allH1[0].firstChild.data == Local.OrigText_LastRound) {
                    allRows[i].parentNode.removeChild(allRows[i]);
                    break;
                }
            }
        };
    }


    function GetIniInfo(Node, Info) {
        if (Node == null || Node.className != "rep_initiative")
            return false;

        if (Node.innerHTML == "&nbsp;")
            return true;

        if (includeFilter.Init) {
            // \1    ini
            // \2    current action
            // \3    total actions
            var Patt_Ini = Local.Pattern_Ini;
            var result = Patt_Ini.exec(Node.innerHTML);
            if (result == null) {
                DbgMsgAction(Info, "IniInfo: " + Node.innerHTML);
                return false;
            }

            var active = Info.Active;
            active.nIniRoll = Number(result[1]);
            active.nCurrAction = Number(result[2]);
            active.nTotalActions = Number(result[3]);
            return active.nIniRoll != null;
        }
        return false;
    }


    // return: whether the format is right
    function GetActiveInfo(Node, Info) {
        if (Node === null) {
            DbgMsgAction(Info, "ActiveInfo: null");
            return false;
        }
        var nStartNode = 0;
        var Str = Node.innerHTML;
        var active = Info.Active;

        // \1    span node
        // \2    npc Id
        var Patt_Char = Local.Pattern_Active_Char;
        var result = Patt_Char.exec(Str);
        if (result === null) {
            DbgMsgAction(Info, "ActiveInfo (Char): " + Node.innerHTML);
            return true;
        }
        var CharNode = result[1] != null ? Node.childNodes[nStartNode].childNodes[0] :
            Node.childNodes[nStartNode];
        active.Char = new CChar(CharNode);
        active.nCharId = result[2] != null ? Number(result[2]) : null;
        nStartNode += result[1] != null ? 1 : (result[2] != null ? 2 : 1);
        Str = Str.substring(result[0].length);

        // \1    attack
        // \2    heal or buff
        // \3    left parenthesis
        var Patt_Action1 = Local.Pattern_Active_Action1;
        result = Patt_Action1.exec(Str);
        if (result === null) {
            // \1    other action
            var Patt_Action2 = Local.Pattern_Active_Action2;
            result = Patt_Action2.exec(Str);
            if (result === null) {
                DbgMsgAction(Info, "ActiveInfo (Action2): " + Node.innerHTML);
                return false;
            }
            if (result[3] != null) {
                active.ActionType.setKind(CActionType.ATTACK);
                active.ActionType.setText("");
                active.gAttackRoll = [];
                active.gAttackRoll.push(Number(result[3]));
                active.gPosition.push(new CPositionType(""));
                active.Skill = new CSkill(null);
            }
            else
                active.ActionType = new CActionType(result[1]);
            return true;
        }
        if (result[1] != null) {
            active.ActionType = new CActionType(result[1]);
            if (active.ActionType.GetKind() !== CActionType.ATTACK) {
                DbgMsgAction(Info, "ActiveInfo (Attack Type): " + result[1]);
                return false;
            }
            nStartNode += 1;

            Str = Str.substring(result[0].length);

        } else {
            active.ActionType = new CActionType(result[2]);
            if (active.ActionType.GetKind() !== CActionType.HEAL && active.ActionType.GetKind() !== CActionType.BUFF) {
                DbgMsgAction(Info, "ActiveInfo (Heal/Buff Type): " + result[2]);
                return false;
            }
            active.Skill = new CSkill(Node.childNodes[nStartNode + 1]);
            if (result[3] === null)
                return true;
            nStartNode += 3;
            Str = Str.substring(result[0].length);
        }

        switch (active.ActionType.GetKind()) {
            case CActionType.ATTACK: // attack
                {
                    // \1    single roll
                    // \2   multiple positions and rolls
                    // \3    position n (only the last one)
                    // \4    multiple roll n (only the last one)
                    // \5    MP
                    // \6    item list
                    // \7   HP
                    if (includeFilter.Attack) {
                        var Patt_ActtackDetails = Local.Pattern_Active_AttackDetails;
                        result = Patt_ActtackDetails.exec(Str);
                        if (result === null) {
                            DbgMsgAction(Info, "ActiveInfo (ActtackDetails): " + Node.innerHTML);
                            return false;
                        }
                        active.Skill = new CSkill(Node.childNodes[nStartNode]);
                        active.gAttackRoll = [];
                        active.gPosition = new CKeyList();
                        if (result[1] != null) {
                            active.gAttackRoll.push(Number(result[1]));
                            active.gPosition.push(new CPositionType(''));
                        }
                        if (result[2] != null) {
                            var pattern_pos_atk = /^([^\u0000-\u007F]+): ([\d]+)$/;
                            var gPos_Atk = result[2].split('/');
                            for (var i = 1; i < gPos_Atk.length; ++i) {
                                var inner_result = pattern_pos_atk.exec(gPos_Atk[i]);
                                active.gAttackRoll.push(Number(inner_result[2]));
                                active.gPosition.push(new CPositionType(inner_result[1]));
                            }
                        }
                        active.nSkillMP = result[5] != null ? Number(result[5]) : null;
                        active.nSkillHP = result[7] != null ? Number(result[7]) : null;
                        if (result[6] != null) {
                            active.gItem = new CKeyList();
                            nStartNode += result[5] != null ? 4 : 2;
                            var ItemNode;
                            while ((ItemNode = Node.childNodes[nStartNode]) != null) {
                                if (ItemNode.tagName == 'A') {
                                    active.gItem.push(new CItem(ItemNode));
                                    nStartNode += 2;
                                }
                                else {
                                    nStartNode++;
                                }
                            }
                        }
                    }
                    return true;
                }
            case CActionType.HEAL: // heal
            case CActionType.BUFF: // buff
                {
                    // \1    MP
                    // \2    normal item list
                    // \3    magical potion
                    if ((includeFilter.Heal && active.ActionType.GetKind() === CActionType.HEAL) || (includeFilter.Buff && active.ActionType.GetKind() === CActionType.BUFF)) {
                        var Patt_HealBuffDetails = Local.Pattern_Active_HealBuffDetails;
                        result = Patt_HealBuffDetails.exec(Str);
                        if (result === null) {
                            DbgMsgAction(Info, "ActiveInfo (HealBuffDetails): " + Node.innerHTML);
                            return false;
                        }
                        active.nSkillMP = result[1] != null ? Number(result[1]) : null;
                        if (result[2] != null) {
                            active.gItem = new CKeyList();
                            nStartNode += result[1] != null ? 2 : 0;
                            var ItemNode;
                            while ((ItemNode = Node.childNodes[nStartNode]) != null) {
                                if (ItemNode.tagName == 'A') {
                                    active.gItem.push(new CItem(ItemNode));
                                    nStartNode += 2;
                                }
                                else {
                                    nStartNode++;
                                }
                            }
                        } else if (result[3] != null) {
                            active.gItem = new CKeyList();
                            nStartNode += result[1] != null ? 2 : 0;
                            active.gItem.push(new CItem(Node.childNodes[nStartNode]));
                            // nStartNode: determine by the number of reagents
                        }
                    }
                    return true;
                }
            default: // impossible, the value can only be 0, 1, or 2
                return false;
        }
    }


    // return: whether the format is right
    function GetAttackedInfo(Node, Info) {
        if (Node === null) {
            DbgMsgAction(Info, "AttackedInfo: null");
            return false;
        }
        var nStartNode = 0;
        var Str = Node.innerHTML;

        // \1    char span node
        // \2    char Id
        // \3    skill
        // \4    defence roll
        // \5    MP
        // \6    item list
        // \7    hit type
        // \8    struck down
        // \9    damage list
        // \10    item damage
        // \11    next flag
        var Patt_Attacked = Local.Pattern_Passive_Attacked;
        var bEnd = false;
        while (!bEnd) {
            var PassiveInfo = new CPassiveInfo();
            var result = Patt_Attacked.exec(Str);
            if (result === null) {
                DbgMsgAction(Info, "AttackedInfo: " + Node.innerHTML);
                return true;
            }
            var CharNode = result[1] != null ? Node.childNodes[nStartNode].childNodes[0] :
                Node.childNodes[nStartNode];
            PassiveInfo.Char = new CChar(CharNode);
            PassiveInfo.nCharId = result[2] != null ? Number(result[2]) : null;
            nStartNode += result[1] != null ? 1 : (result[2] != null ? 2 : 1);
            if (result[3] != null) {
                PassiveInfo.Skill = new CSkill(Node.childNodes[nStartNode + 1]);
                nStartNode += 2;
            }
            PassiveInfo.nDefenceRoll = Number(result[4]);
            if (result[5] != null) {
                PassiveInfo.nSkillMP = Number(result[5]);
                nStartNode += 2;
            }
            if (result[6] != null) {
                PassiveInfo.gItem = new CKeyList();
                nStartNode += 1;
                var ItemNode = Node.childNodes[nStartNode];
                while (ItemNode != null && ItemNode.nodeName == "A") {
                    PassiveInfo.gItem.push(new CItem(ItemNode));
                    nStartNode += 2;
                    ItemNode = Node.childNodes[nStartNode];
                }
            } else
                nStartNode += 1;
            PassiveInfo.HitType = new CHitType(result[7]);
            PassiveInfo.bStruckDown = (result[8] != null);
            nStartNode += result[8] != null ? 2 : 1;
            if (result[9] != null) {
                PassiveInfo.gDamage = [];
                nStartNode += 1;
                var DamageNode = Node.childNodes[nStartNode];
                while (DamageNode != null && (DamageNode.nodeType == Node.TEXT_NODE ||
                    (DamageNode.nodeName == "SPAN" &&
                        DamageNode.firstChild != null && DamageNode.firstChild.nodeType == Node.TEXT_NODE))) {
                    PassiveInfo.gDamage.push(new CDamage(DamageNode));
                    nStartNode += 2;
                    DamageNode = Node.childNodes[nStartNode];
                }
                nStartNode -= 1;
            }
            if (result[10] != null) {
                PassiveInfo.DamagedItem = new CItem(Node.childNodes[nStartNode + 1]);
                PassiveInfo.nItemDamage = Number(result[10]);
                nStartNode += 3;
            }
            if (result[11] != null)
                nStartNode += 1;
            else
                bEnd = true;

            Info.gPassive.push(PassiveInfo);
            Str = Str.substring(result[0].length);
        }
        return true;
    }


    // return: whether the format is right
    function GetHealedBuffedInfo(Node, Info) {
        if (Node === null) {
            DbgMsgAction(Info, "HealedBuffedInfo: null");
            return false;
        }
        var nStartNode = 0;
        var Str = Node.innerHTML;

        // \1    span node
        // \2    char Id
        // \3    self
        // \4    HP
        // \5    MP
        // \6    next flag
        var Patt_HealedBuffed = Local.Pattern_Passive_Healed_Buffed;
        var bEnd = false;
        while (!bEnd) {
            var PassiveInfo = new CPassiveInfo();
            var result = Patt_HealedBuffed.exec(Str);
            if (result === null) {
                DbgMsgAction(Info, "HealedBuffedInfo: " + Node.innerHTML);
                return true;
            }
            if (result[3] != null) {
                PassiveInfo.Char = Info.Active.Char;
                PassiveInfo.nCharId = Info.Active.nCharId;
            } else {
                var CharNode = result[1] != null ? Node.childNodes[nStartNode].childNodes[0] :
                    Node.childNodes[nStartNode];
                PassiveInfo.Char = new CChar(CharNode);
                PassiveInfo.nCharId = result[2] != null ? Number(result[2]) : null;
                nStartNode += result[1] != null ? 1 : (result[2] != null ? 2 : 1);
            }
            PassiveInfo.nHealedHP = result[4] != null ? Number(result[4]) : null;
            PassiveInfo.nHealedMP = result[5] != null ? Number(result[5]) : null;
            nStartNode += 1;
            if (result[6] != null)
                nStartNode += 1;
            else
                bEnd = true;

            Info.gPassive.push(PassiveInfo);
            Str = Str.substring(result[0].length);
        }
        return true;
    }


    function DbgMsgAction(Info, Text) {
        if (DEBUG)
            alert("[" + Info.Navi.nLevel + "." + Info.Navi.nRoom + "." +
                Info.Navi.nRound + "." + Info.Navi.nRow + "] " + Text);
    }


    // GLOBAL VARIABLES ///////////////////////////////////////////////////////////

    var DEBUG = false;

    var groupData = false;

    var useFilter = true;

    var Contents = {
      OrigText_Button_DungeonDetails: ["details", "详细资料"],
      OrigText_Button_DuelDetails: ["Details", "详细"],
      OrigText_Button_DungeonStat: ["statistics", "统计表"],
      OrigText_Level: ["Level", "层数"],
      OrigText_LastRound: ["Last round:", "最后回合:"],
      OrigTextList_AttackActionType: [
        [
          "attacks",
          "ranged attacks",
          "attacks with magic",
          "socially attacks",
          "cunningly attacks",
          "activates on",
          "works as a force of nature upon",
          "infected",
          "casts an explosion at",
          "deactivated",
          "magic projectile",
          "curse",
          "scare",
          "other",
        ],
        [
          "近战攻击",
          "远程攻击",
          "魔法攻击",
          "心理攻击",
          "偷袭",
          "触发",
          "作为自然灾害",
          "散布",
          "制造爆炸",
          "解除",
          "魔法投射",
          "诅咒",
          "恐吓",
          "冲击",
          "魔法弹",
        ],
      ],
      OrigTextList_HealActionType: [["heals with"], ["治疗"]],
      OrigTextList_BuffActionType: [
        ["uses", "summons with"],
        ["使用", "召唤"],
      ],
      OrigTextList_WaitActionType: [
        ["is unable to do anything.", "looks around in boredom and waits."],
        ["不能执行任何动作.", "无聊的打量四周,等待着."],
      ],
      OrigTextList_NoneHPDamageType: [
        ["mana damage", "mana"],
        ["法力伤害", "法力"],
      ],
      Pattern_Ini: [
        /^Initiative ([\d]+)<br><span .*?>Action ([\d]+) of ([\d]+)<\/span>$/,
        /^先攻([\d]+)<br><span .*?>第([\d]+)步行动 \/ 共([\d]+)步<\/span>$/,
      ],
      Pattern_Active_Char: [
        /^(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?/,
        /^(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?/,
      ],
      Pattern_Active_Action1: [
        /^\s*(?:([A-Za-z][A-Za-z ]+[A-Za-z]) +\(|([A-Za-z][A-Za-z ]+[A-Za-z]) +<a .*?>.*?<\/a>(?:( \()|$| on $))/,
        /^\s*(?:([^\u0000-\u007F]+) +\(|([^\u0000-\u007F]+)<a .*?>.*?<\/a>(?:( \()|$|给$))/,
      ],
      Pattern_Active_Action2: [
        /^\s*([\S][^/(^/)]*[\S])([/(]([\d]+)[/)])?\s*$/,
        /^\s*([\S][^/(^/)]*[\S])([/(]([\d]+)[/)])?\s*$/,
      ],
      Pattern_Active_AttackDetails: [
        /^<a .*?>.*?<\/a>(?:\/([\d]+)|(?:\/([A-Za-z ]+): ([\d]+))+)(?:\/<span .*?>([\d]+) MP<\/span>)?(\/(?:<a .*?>.*?<\/a>,)*<a .*?>.*?<\/a>)?\)$/,
        /^<a .*?>.*?<\/a>(?:\/([\d]+)|((?:\/([^\u0000-\u007F]+): ([\d]+))+))(?:\/<span .*?>([\d]+) (?:法力|神力|怒气|灵能|动力|魂能|动能|动能)<\/span>)?(\/(?:<a .*?>.*?<\/a>\s*(?:<img .*?>)*,)*<a .*?>.*?<\/a>\s*(?:<img .*?>)*)?(?:\/<span .*?>(?:<b>)?(?:-|\+)([\d]+) HP(?:<\/b>)?<\/span>)?(?:\/<span .*?>(?:<b>)?(?:-|\+)([\d]+) (?:法力|神力|怒气|灵能|动力|魂能|动能)(?:<\/b>)?<\/span>)?\)$/,
      ],
      Pattern_Active_HealBuffDetails: [
        /^(?:<span .*?>([\d]+) MP<\/span>)?(?:\/)?(?:((<a .*?>.*?<\/a>,)*<a .*?>.*?<\/a>)|(<a .*?>.*?<\/a>\s+(?:<img .*?>)+))?\)(?: on )?$/,
        /^(?:<span .*?>(?:-|\+)?([\d]+) (?:法力|神力|怒气|灵能|动力|魂能|动能)<\/span>)?(?:\/)?(?:((<a .*?>.*?<\/a>\s*(?:<img .*?>)*,)*<a .*?>.*?<\/a>\s*(?:<img .*?>)*))?\)(?:给)?$/,
      ],
      Pattern_Passive_Attacked: [
        /^(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?\s*\((<a .*?>.*?<\/a>\/)?([\d]+)(?:\/<span .*?>([\d]+) MP<\/span>)?(\/(?:<a .*?>.*?<\/a>,)*<a .*?>.*?<\/a>)?\): <span class="([A-Za-z_]+)">[A-Za-z ]+<\/span>( - [A-Za-z ]+)?(<br>(?:<span .*?>)?(?:-)?[\d]+ (?:\[(?:\+|-)[\d]+\] )?[A-Za-z ]+(?:<img .*?><\/span>)?)*(?:<br><a .*?>.*?<\/a> -([\d]+) HP)?(?:(<br>)|$)/,
        /^(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?\s*\((<a .*?>.*?<\/a>\/)?([\d]+)(?:\/<span .*?>([\d]+) (?:法力|神力|怒气|灵能|动力|魂能|动能)<\/span>)?(\/(?:<a .*?>.*?<\/a>\s*(?:<img .*?>)*,)*<a .*?>.*?<\/a>\s*(?:<img .*?>)*)?\): <span class="([A-Za-z_]+)">[^\u0000-\u007F]+<\/span>( - [^\u0000-\u007F]+ *)?(<br>(?:<span .*?>)?(?:-)?[\d]+ (?:\[(?:\+|-)[\d]+\] )?[^\u0000-\u007F]+(?:<img .*?><\/span>)?)*(?:<br><a .*?>.*?<\/a> (?:-|\+)([\d]+) HP)?(?:(<br>)|$)/,
      ],
      Pattern_BasicDamage: [/causes: <b>([\d]+)<\/b>/, /造成: <b>([\d]+)<\/b>/],
      Pattern_Damage: [
        /^((?:-)?[\d]+) (?:\[((?:\+|-)[\d]+)\] )?([A-Za-z][A-Za-z ]+[A-Za-z])$/,
        /^((?:-)?[\d]+) (?:\[((?:\+|-)[\d]+)\] )?([^\u0000-\u007F]+)$/,
      ],
      Pattern_Passive_Healed_Buffed: [
        /^(?:(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?\s+|(themselves))(?: \+([\d]+) HP)?(?: \+([\d]+) MP)?(?:(<br>)|$)/,
        /^(?:(<span .*?>)?<a .*?>.*?<\/a>(?:<span .*?>([\d]+)<\/span>)?(?:<img .*?><\/span>)?\s+|(自己|自身))(?: \+([\d]+) HP)?(?: \+([\d]+) (?:法力|神力|怒气|灵能|动力|魂能|动能))?(?:(<br>)|$)/,
      ],
      Text_Button_ExtraStat: ["Extra Stat", "额外统计"],
      Text_Button_EntireStat: ["Entire Extra Stat", "全城额外统计"],
      Text_Button_Show: ["Show", "显示"],
      Text_Button_Hide: ["Hide", "隐藏"],
      Text_Button_ShowAll: ["Show All", "全部显示"],
      Text_Button_HideAll: ["Hide All", "全部隐藏"],
      Text_Button_Default: ["Default", "默认"],
      TextList_AttackType: [
        [
          "melee",
          "ranged",
          "spell",
          "social",
          "ambush",
          "trap",
          "nature",
          "disease",
          "detonate",
          "disarm trap",
          "magic projectile",
          "curse",
          "scare",
          "other",
        ],
        [
          "近战",
          "远程",
          "魔法",
          "心理",
          "偷袭",
          "陷阱",
          "自然",
          "疾病",
          "爆破",
          "解除陷阱",
          "魔法投射",
          "诅咒",
          "恐吓",
          "冲击",
          "魔法弹",
        ],
      ],
      TextList_HealType: [["heal"], ["治疗"]],
      TextList_BuffType: [["buff"], ["使用"]],
      TextList_WaitType: [["wait"], ["等待"]],
      TextList_HitType: [
        ["failed", "success", "good success", "critical success"],
        ["闪避", "普通", "重击", "致命一击"],
      ],
      Text_Loading: ["Loading", "载入中"],
      Text_Options: ["Options:", "选项:"],
      Text_DefaultMsg: [
        "All the data this script stored in your machine has been cleared.",
        "此脚本储存在你的机器上的所有数据已被清除。",
      ],
      Text_Table_Ini: ["Initiative", "先攻权"],
      Text_Table_Attack: ["Attack", "攻击骰"],
      Text_Table_Defence: ["Defence", "防御骰"],
      Text_Table_Damage: ["Damage", "造成伤害"],
      Text_Table_Damaged: ["Damage", "受到伤害"],
      Text_Table_DamageType: ["Damage Type", "伤害类型"],
      Text_Table_HealType: ["Heal Type", "治疗类型"],
      Text_Table_BuffType: ["Buff Type", "增益类型"],
      Text_Table_Heal: ["Healing By The Hero", "给予治疗"],
      Text_Table_Healed: ["Healing On The Hero", "接受治疗"],
      Text_Table_Buff: ["Buffing By The Hero", "给予增益"],
      Text_Table_Buffed: ["Buffing On The Hero", "接受增益"],
      Text_Table_Buffer: ["Buffing Provider", "提供者"],
      Text_Table_BuffeReceiver: ["Buffing Receiver", "提供给"],
      Text_Table_DamagedItems: ["Damaged Items", "物品损坏"],
      Text_Table_Char: ["Character", "人物"],
      Text_Table_AttackType: ["Attack type", "攻击类型"],
      Text_Table_DefenceType: ["Defence type", "防御类型"],
      Text_Table_Skill: ["Skill", "技能"],
      Text_Table_Item: ["Item", "物品"],
      Text_Table_Position: ["Pos", "位置"],
      Text_Table_AvgRoll: ["Average roll", "平均值"],
      Text_Table_MaxRoll: ["Max roll", "Max值"],
      Text_Table_MinRoll: ["Min roll", "Min值"],
      Text_Table_STDRoll: ["STD roll", "STD值"],
      Text_Table_Total: ["Total", "总数"],
      Text_Table_Times: ["Times", "次数"],
      Text_Table_RollList: ["Roll list", "数值列表"],
      Text_Table_DetailList: ["Detail list", "详细列表"],
      Text_Table_ItemDamagePoints: ["Damage Points", "损坏点数"],
      Text_Table_AllData: ["All", "全部"],
    };

    var Style = "div.stat_all {font-size:14px;} " +
        "div.stat_header {margin:1em auto 0.5em auto;} " +
        "span.stat_title {margin: auto 1em auto 0em; font-size:20px; font-weight:bold; color:#FFF;} span.clickable {cursor:pointer;} " +
        "span.pair_value {width:100%; font-size:12px;} span.pair_value span {width:50%; min-width:3em; text-align:right; color:#F8A400;} span.pair_value span + span {color:#00CC00;} " +
        "table.pair_hero {width:100%; font-size:12px;} table.pair_hero td {width:50%; min-width:3em; text-align:right; color:#00CC00;} table.pair_hero td + td {color:#F8A400;} " +
        "table[hide] {display:none;} " +
        "table.pair_value {width:100%;} table.pair_value td {width:50%; min-width:3em; text-align:right; color:#F8A400;} table.pair_value td + td {color:#00CC00;} " +
        "#myProgress {position: relative; width: 100%;  height: 3px; background-color: #ddd;} " +
        "#myBar { position: absolute;  width: 1%;  height: 100%;  background-color: #4CAF50;}";

    var Local;
    var Stat;
    var includeFilter = {
        Init: true,
        Attack: true,
        Defence: true,
        Damage: true,
        Damaged: true,
        Heal: true,
        Healed: true,
        Buff: true,
        Buffed: true,
        DamagedItems: true
    };
    var localInfo = document;
    var reportInfoDiv;
    var progress;
    var dataPage;
    var heroes = [];
    var heroLvMap = {};

    if (typeof GM_addStyle == "undefined") {
      function GM_addStyle(styles) {
        var S = document.createElement("style");
        S.type = "text/css";
        var T = "" + styles + "";
        T = document.createTextNode(T);
        S.appendChild(T);
        document.body.appendChild(S);
        return;
      }
    }

    var clickedButton;

    // FUNCTIONS //////////////////////////////////////////////////////////////////
    function CreateStat(node, infoNode, isExport) {
      // Stat initialization
      includeFilter.Init =
        document.getElementById("chk_Text_Table_Ini").checked;
      includeFilter.Attack = document.getElementById(
        "chk_Text_Table_Attack"
      ).checked;
      includeFilter.Defence = document.getElementById(
        "chk_Text_Table_Defence"
      ).checked;
      includeFilter.Damage = document.getElementById(
        "chk_Text_Table_Damage"
      ).checked;
      includeFilter.Damaged = document.getElementById(
        "chk_Text_Table_Damaged"
      ).checked;
      includeFilter.Heal = document.getElementById(
        "chk_Text_Table_Heal"
      ).checked;
      includeFilter.Healed = document.getElementById(
        "chk_Text_Table_Healed"
      ).checked;
      includeFilter.Buff = document.getElementById(
        "chk_Text_Table_Buff"
      ).checked;
      includeFilter.Buffed = document.getElementById(
        "chk_Text_Table_Buffed"
      ).checked;
      includeFilter.DamagedItems = document.getElementById(
        "chk_Text_Table_DamagedItems"
      ).checked;

      var theStat = new CStat(node, infoNode);

      if (includeFilter.Init)
        theStat.RegInfoList(new CILIni(CVLNumber, isExport));

      if (includeFilter.Attack)
        theStat.RegInfoList(new CILAttackRoll(CVLNumber, isExport));

      if (includeFilter.Defence)
        theStat.RegInfoList(new CILDefenceRoll(CVLNumber, isExport));

      if (includeFilter.Damage)
        theStat.RegInfoList(new CILDamage(CVLDamage, isExport));

      if (includeFilter.Damaged)
        theStat.RegInfoList(new CILDamaged(CVLDamage, isExport));

      if (includeFilter.Heal)
        theStat.RegInfoList(new CILHeal(CVLNumber, isExport));

      if (includeFilter.Healed)
        theStat.RegInfoList(new CILHealed(CVLNumber, isExport));

      if (includeFilter.Buff)
        theStat.RegInfoList(new CILBuff(CVLString, isExport));

      if (includeFilter.Buffed)
        theStat.RegInfoList(new CILBuffed(CVLString, isExport));

      if (includeFilter.DamagedItems)
        theStat.RegInfoList(new CILItemDamage(CVLNumber, isExport));
      return theStat;
    }

    function Main() {
      // Language selection
      Local = GetLocalContents(Contents);
      if (Local === null) return;
      //GM_log(Local);
      // Add CSS
      GM_addStyle(Style);

      // Add buttons
      var KeyButton = AddButtonBesideDisabledButton(
        [
          Local.OrigText_Button_DungeonDetails,
          Local.Text_Button_ExtraStat,
          OnCountStat,
        ],
        [
          Local.OrigText_Button_DungeonStat,
          Local.Text_Button_EntireStat,
          OnCountEntireStat,
        ],
        [
          Local.OrigText_Button_DuelDetails,
          Local.Text_Button_ExtraStat,
          OnCountStat,
        ]
      );
      if (KeyButton === null) return;
      KeyButton = addFilter(KeyButton);
      addStatusBar(KeyButton);
      dataPage = document.createElement("div");
      dataPage.innerHTML = "";
    }

    function addStatusBar(node) {
      var progressDiv = document.createElement("div");
      progressDiv.id = "myProgress";
      reportInfoDiv = document.createElement("div");
      reportInfoDiv.id = "myBar";
      progressDiv.appendChild(reportInfoDiv);
      node.parentNode.insertBefore(progressDiv, node.nextSibling);
    }
    function hideFilter() {
      // 隐藏复选项
      var AllFilter = document.getElementsByName("ExtraFilter");
      for (var i = 0, l = AllFilter.length; i < l; i++) {
        AllFilter[i].style.display = "none";
      }
    }
    function addFilter(KeyButton) {
      if (isaddFilter) {
        //首次执行插入复选项
        var newLine = document.createElement("br");
        newLine.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newLine,
          KeyButton.nextSibling
        );
        var newSpan = document.createElement("span");
        newSpan.innerHTML = "选择要统计的项目:";
        newSpan.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //先攻
        var newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        newCheckBox.setAttribute("name", "ExtraFilter");
        if (includeFilter.Init) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Ini";
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Ini;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //攻击骰
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Attack) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Attack";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Attack;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //防御骰
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Defence) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Defence";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Defence;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //造成伤害
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Damage) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Damage";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Damage;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //受到伤害
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Damaged) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Damaged";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Damaged;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //给予治疗
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Heal) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Heal";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Heal;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //接受治疗
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Healed) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Healed";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Healed;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //给予增益
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Buff) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Buff";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Buff;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //接受增益
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.Buffed) newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_Buffed";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_Buffed;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );

        //物品损坏
        newCheckBox = document.createElement("input");
        newCheckBox.setAttribute("type", "checkbox");
        newCheckBox.setAttribute("class", "checkbox");
        newCheckBox.setAttribute("value", "");
        if (includeFilter.DamagedItems)
          newCheckBox.setAttribute("checked", "true");
        newCheckBox.id = "chk_Text_Table_DamagedItems";
        newCheckBox.setAttribute("name", "ExtraFilter");
        KeyButton = KeyButton.parentNode.insertBefore(
          newCheckBox,
          KeyButton.nextSibling
        );
        newSpan = document.createElement("label");
        newSpan.innerHTML = Local.Text_Table_DamagedItems;
        newSpan.setAttribute("name", "ExtraFilter");
        newSpan.setAttribute("for", newCheckBox.id);
        KeyButton = KeyButton.parentNode.insertBefore(
          newSpan,
          KeyButton.nextSibling
        );
        isaddFilter = false;
        return KeyButton;
      } else {
        //多次执行重新显示复选项
        var AllFilter = document.getElementsByName("ExtraFilter");
        for (var i = 0, l = AllFilter.length; i < l; i++) {
          AllFilter[i].style.display = "";
        }
        return KeyButton;
      }
    }

    // It will only add the first eligible button
    // return: the node of the first eligible disabled button, or null if didn't find anyone
    function AddButtonBesideDisabledButton(/* [DisabledButtonText, ButtonText, ClickEvent], [...], ... */) {
      var allInputs = document.getElementsByTagName("input");
      for (var i = 0; i < allInputs.length; ++i) {
        if (allInputs[i].className == "button_disabled") {
          for (var j = 0; j < arguments.length; ++j) {
            if (allInputs[i].getAttribute("value") == arguments[j][0]) {
              return AddButton(allInputs[i], arguments[j][1], arguments[j][2]);
            }
          }
        }
      }
      return null;
    }

    // Add a button to the end of the given node's parent node
    function AddButton(SiblingNode, Value, OnClick) {
      var newButton = document.createElement("input");
      newButton.setAttribute("type", "button");
      newButton.setAttribute("class", "button");
      newButton.setAttribute("value", Value);
      newButton.addEventListener("click", OnClick, false);
      var newBlank = document.createTextNode("            ");
      SiblingNode.parentNode.appendChild(newBlank);
      SiblingNode.parentNode.appendChild(newButton);
      return newButton;
    }

    function OnCountStat() {
      try {
        clickedButton = this;
        if (this.className == "button_disabled") return;
        else this.className = "button_disabled";
        var InfoDiv = document.createElement("div");
        InfoDiv.id = "report_info";
        Stat = CreateStat(node_after(this.parentNode), InfoDiv, false);
        reportInfoDiv.style.width = "1%";
        Stat.nTotalPages = 1;
        //Stat.nReadPages = 1;
        Stat.iscurrentPage = true;
        Stat.ShowProgress();
        if (progress) clearInterval(progress);
        progress = setInterval(Stat.ShowProgress.bind(Stat), 1);
        ReadPage(document, true);
      } catch (e) {
        alert("OnCountStat(): " + e);
      }
    }

    function OnCountEntireStat() {
      try {
        clickedButton = this;
        if (this.className == "button_disabled") return;
        else this.className = "button_disabled";
        var InfoDiv = document.createElement("div");
        InfoDiv.id = "report_Entire_info";
        Stat = CreateStat(node_after(this.parentNode), InfoDiv, false);
        reportInfoDiv.style.width = "1%";
        CountEntireStat();
      } catch (e) {
        alert("OnCountEntireStat(): " + e);
      }
    }

    function CountEntireStat() {
      var nCurrRepId = GetHiddenInfo(document, "report_id[0]", "");
      var nMaxLevel = (Stat.nTotalPages = GetStatPageMaxLevel(document, 1));
      Stat.ShowProgress();
      if (progress) clearInterval(progress);
      progress = setInterval(Stat.ShowProgress.bind(Stat), 1);
      for (var CurrLevel = 1; CurrLevel <= nMaxLevel; ++CurrLevel)
        GetPage(nCurrRepId, CurrLevel, 1, true);

      //Stat.ShowProgress();
    }

    function GetPage(nRepId, nLevel, nRepPage, bFirstRead) {
      var XmlHttp = new XMLHttpRequest();

      XmlHttp.onreadystatechange = function () {
        try {
          if (XmlHttp.readyState == 4 && XmlHttp.status == 200) {
            dataPage.innerHTML = XmlHttp.responseText;
            //Stat.nReadPages = nRepPage;
            Stat.iscurrentPage = false;
            ReadPage(dataPage, bFirstRead);
          }
        } catch (e) {
          alert("XMLHttpRequest.onreadystatechange(): " + e);
        }
      };

      var URL =
        location.protocol +
        "//" +
        location.host +
        "/wod/spiel/dungeon/report.php" +
        "?cur_rep_id=" +
        nRepId +
        "&gruppe_id=&current_level=" +
        nLevel +
        "&REPORT_PAGE=" +
        nRepPage +
        "&IS_POPUP=1";
      Stat.ShowProgress();
      XmlHttp.open("GET", URL, true);
      XmlHttp.send(null);
    }

    function ReadPage(page, bFirstRead) {
      var ret = GetRepPageInfo(page, [1, 1]);
      var nCurrRepPage = ret[0];
      var nMaxRepPage = ret[1];
      if (bFirstRead && nMaxRepPage > 1) {
        var nRepId = GetHiddenInfo(page, "report_id[0]", "");
        var nLevel = GetHiddenInfo(page, "current_level", 1);

        Stat.nTotalPages += nMaxRepPage - 1;
        for (var i = 1; i <= nMaxRepPage; ++i) {
          Stat.ShowProgress();
          if (i !== nCurrRepPage) GetPage(nRepId, nLevel, i, false);
        }
      }
      Stat.nReadPages++;
      Stat.ShowProgress();
      CountStat(page, nCurrRepPage === nMaxRepPage);
      if (Stat.nReadPages >= Stat.nTotalPages) {
        Stat.Show(false);
        if (clickedButton) clickedButton.className = "button";
      }
    }

    function GetHiddenInfo(page, InfoName, DefaultValue) {
      var allInputs = page.getElementsByTagName("input");
      for (var i = 0; i < allInputs.length; ++i) {
        if (
          allInputs[i].getAttribute("type") == "hidden" &&
          allInputs[i].name == InfoName
        )
          return allInputs[i].value;
      }
      return DefaultValue;
    }

    function GetStatPageMaxLevel(page, DefaultValue) {
      var allTds = page.getElementsByTagName("td");
      for (var i = 0; i < allTds.length; ++i) {
        if (first_child(allTds[i].parentNode) != allTds[i]) continue;
        var LevelNode = first_child(allTds[i]);
        if (
          LevelNode != null &&
          LevelNode.nodeType == Node.TEXT_NODE &&
          LevelNode.data == Local.OrigText_Level
        ) {
          var Patt_Level = /^(?:<span .*?>)?(?:[\d]+)\/([\d]+)(?:<\/span>)?$/;
          var result = Patt_Level.exec(node_after(allTds[i]).innerHTML);
          if (result === null) return DefaultValue;
          return Number(result[1]);
        }
      }
      return DefaultValue;
    }

    // return: an array, [0]: nCurrRepPage, [1]: nMaxRepPage
    function GetRepPageInfo(page, DefaultValue) {
      var ret = [DefaultValue[0], DefaultValue[1]];
      var allInputs = page.getElementsByTagName("input");
      var IndexPatt = /\D*([\d]+)\D*/;
      var pages = [];
      for (var i = 0; i < allInputs.length; ++i) {
        var theInput = allInputs[i];
        if (theInput.className == "paginator_selected clickable") {
          var Result = IndexPatt.exec(theInput.value);
          if (Result && Result[1]) {
            pages.push(Number(Result[1]));
            ret[0] = Number(Result[1]);
          }
        }
      }
      allInputs = page.getElementsByTagName("a");
      for (var i = 0; i < allInputs.length; ++i) {
        var theInput = allInputs[i];
        if (theInput.className == "paginator") {
          var Result = IndexPatt.exec(theInput.textContent);
          if (Result && Result[1]) pages.push(Number(Result[1]));
        }
      }
      if (pages.length > 0) ret[1] = Math.max.apply(Math, pages);

      return ret;
    }

    //===============================================================================================
    // code for save report only.
    //===============================================================================================

    var StatEntire;
    var StatEntireDiv;

    function es_addStyle(page, styles) {
      var S = document.createElement("style");
      S.type = "text/css";
      var T = "" + styles + "";
      T = document.createTextNode(T);
      S.appendChild(T);
      page.appendChild(S);
      return;
    }

    function InsertButton(Node, Value, OnClick) {
      var newButton = document.createElement("input");
      newButton.setAttribute("type", "button");
      newButton.setAttribute("class", "button");
      newButton.setAttribute("value", Value);
      newButton.addEventListener("click", OnClick, false);
      Node.parentNode.insertBefore(newButton, Node.nextSibling);
    }

    function changeAllSelection(select) {
      var allCheckbox = document.getElementsByTagName("input");
      for (var i = 0; i < allCheckbox.length; ++i) {
        var theCheckbox = allCheckbox[i];
        if (
          rValue.Pattern_checkboxName.test(theCheckbox.getAttribute("name"))
        ) {
          theCheckbox.checked = select;
        }
      }
    }

    function selectAll() {
      if (!gIsWorking) changeAllSelection(true);
    }

    function cleartAll() {
      if (!gIsWorking) changeAllSelection(false);
    }

    function getTimeStrFromWodTimeStr(report) {
      if (report == null) {
        return;
      }
      let timestr = $(report.getAttribute("reporttime")).text();
      const today = dayjs();
      if (timestr.includes("昨天")) {
        timestr = timestr.replace(
          "昨天",
          today.subtract(1, "d").format("YYYY年MM月DD日")
        );
      } else if (timestr.includes("今天")) {
        timestr = timestr.replace("今天", today.format("YYYY年MM月DD日"));
      } else if (timestr.includes("明天")) {
        timestr = timestr.replace(
          "明天",
          today.add(1, "d").format("YYYY年MM月DD日")
        );
      }
      return timestr;
    }

    function parseTime(timeStr) {
      timeStr = timeStr.replace("年", "-").replace("月", "-").replace("日", "");
      return dayjs(timeStr, "YYYY-MM-DD HH:mm");
    }

    function getTimeFromWodTimeStr(report) {
      const timestr = getTimeStrFromWodTimeStr(report);
      return parseTime(timestr);
    }

    function formatTime(date, format) {
      return date.format(format);
    }

    function formatTime4report(report, format) {
      return formatTime(getTimeFromWodTimeStr(report), format);
    }

    function getOverviewDiv() {
      let $overviewDiv = $("div#overview");
      if (!$overviewDiv.length) {
        $("body:first").append(
          $(
            '<div style="visibility: hidden;"><div id="overview"><div id="progressbar"><div class="progress-label">初始化...</div></div><div id="detailMsg"></div></div></div>'
          )
        );
        $overviewDiv = $("div#overview");
      }
      return $overviewDiv;
    }

    function exportLog() {
      if (gIsWorking && !DEBUG) return;
      gIsWorking = true;
      // 获取前缀
      var prefix = document.getElementById("prefix").value;
      var timeFormatSuffix = document.getElementById("suffix").value;
      var includeCheckbox = document.getElementById(rValue.Chk_includeData);
      var singleCheckbox = document.getElementById(rValue.Chk_single);
      var withLvCheckbox = document.getElementById(rValue.Chk_withLv);
      var singleCheck = singleCheckbox.checked;
      includeData = includeCheckbox.checked;
      gWithLv = withLvCheckbox.checked;
      var allCheckbox = document.getElementsByTagName("input");

      // 单文件导出走特殊流程
      if (singleCheck) {
        gIsWorking = false;
        let $overviewDiv = getOverviewDiv();
        let $processbar = $overviewDiv.find("#progressbar");
        let $processLabel = $overviewDiv.find("#progressbar .progress-label");
        let $detailMsg = $overviewDiv.find("#detailMsg");
        $overviewDiv.dialog({
          autoOpen: true, // 是否自动弹出窗口
          modal: true, // 设置为模态对话框
          resizable: true,
          width: 540, //弹出框宽度
          height: 620, //弹出框高度
          title: "导出进度", //弹出框标题
          position: { my: "center", at: "center", of: window }, //窗口显示的位置
          buttons: {
            关闭: function () {
              $(this).dialog("close");
            },
          },
        });
        $processbar.progressbar({
          value: false,
          change: function () {
            $processLabel.text($processbar.progressbar("value") + "%");
          },
          complete: function () {
            $processLabel.text("任务已全部完成!");
          },
        });
        let total = 0;
        let remain = 0;
        // 设置定时任务,每秒检测导出进度
        var interval = setInterval(function () {
          $detailMsg.html($("#infoDiv").html());
          if (!gIsWorking) {
            allCheckbox = document.getElementsByTagName("input");
            allCheckbox = Array.prototype.slice
              .call(allCheckbox)
              .filter(
                (c) =>
                  rValue.Pattern_checkboxName.test(c.getAttribute("name")) &&
                  c.checked
              );

            if (allCheckbox.length) {
              gIsWorking = true;
              if (total < allCheckbox.length) total = allCheckbox.length;
              remain = allCheckbox.length;
              var report = allCheckbox[0];
              gSelectedReport = [report];
              heroes = [];
              heroLvMap = {};
              default_gTitle =
                prefix +
                (gWithLv ? rValue.Holder_Lv : "") +
                report
                  .getAttribute("reportname")
                  .replace(/<[^>]+>/g, "")
                  .replace(/[ ]|[\r\n\t]/g, "");
              if (timeFormatSuffix) {
                var firstTimeStr = formatTime4report(report, timeFormatSuffix);
                default_gTitle += firstTimeStr;
              }
              gTitle = default_gTitle;
              // 更新进度
              $processbar.progressbar(
                "value",
                parseFloat((((total - remain) * 100) / total).toFixed(0))
              );

              gZip = new JSZip();
              headDiv = document
                .getElementsByTagName("head")[0]
                .cloneNode(true);
              handleHead(headDiv);

              gIndexDiv = gIndexTemplateDiv.cloneNode(true);
              var table = document.createElement("div");
              gCurrentReport = gSelectedReport[0];
              if (includeData) {
                StatEntireDiv = document.createElement("div");
                var EntireInfoDiv = document.createElement("div");
                EntireInfoDiv.id = "report_entire_info";
                StatEntire = CreateStat(StatEntireDiv, EntireInfoDiv, true);
                StatEntire.iscurrentPage = false;
              }
              if (!isDuel) GetLevelPage(1, 1);
              else GetDuelPage();
            } else {
              clearInterval(interval);
              $overviewDiv.dialog("close");
            }
          }
        }, 1000);
        return;
      }

      gSelectedReport = [];
      for (var i = 0; i < allCheckbox.length; ++i) {
        var theCheckbox = allCheckbox[i];
        if (
          rValue.Pattern_checkboxName.test(theCheckbox.getAttribute("name"))
        ) {
          if (theCheckbox.checked) {
            gSelectedReport.push(theCheckbox);
            default_gTitle =
              prefix +
              (gWithLv ? rValue.Holder_Lv : "") +
              theCheckbox
                .getAttribute("reportname")
                .replace(/<[^>]+>/g, "")
                .replace(/[ ]|[\r\n\t]/g, "");
            // 获取前缀和最下面勾选的战报名字作为默认,正则去除html标签,空格,换行,制表符
          }
        }
      }

      if (gSelectedReport.length > 0) {
        heroes = [];
        heroLvMap = {};
        // 处理后缀
        if (timeFormatSuffix) {
          var firstReport = gSelectedReport[0];
          var lastReport = gSelectedReport[gSelectedReport.length - 1];
          var firstTimeStr = formatTime4report(firstReport, timeFormatSuffix);
          var lastTimeStr = formatTime4report(lastReport, timeFormatSuffix);
          default_gTitle += firstTimeStr;
          if (firstReport != lastReport && firstTimeStr != lastTimeStr) {
            default_gTitle += "-" + lastTimeStr;
          }
        }

        gTitle = window.prompt("输入战报名称", default_gTitle);
        if (gTitle != null) {
          gZip = new JSZip();
          headDiv = document.getElementsByTagName("head")[0].cloneNode(true);
          handleHead(headDiv);

          gIndexDiv = gIndexTemplateDiv.cloneNode(true);
          var table = document.createElement("div");
          gCurrentReport = gSelectedReport[0];
          if (includeData) {
            StatEntireDiv = document.createElement("div");
            var EntireInfoDiv = document.createElement("div");
            EntireInfoDiv.id = "report_entire_info";
            StatEntire = CreateStat(StatEntireDiv, EntireInfoDiv, true);
            StatEntire.iscurrentPage = false;
          }
          if (!isDuel) GetLevelPage(1, 1);
          else GetDuelPage();
        } else gIsWorking = false;
      } else {
        window.alert("没有选择任何战报");
        gIsWorking = false;
      }
    }

    function GetDuelPage() {
      var XmlHttp = new XMLHttpRequest();

      XmlHttp.onreadystatechange = function () {
        try {
          if (XmlHttp.readyState == 4 && XmlHttp.status == 200) {
            gResponseDiv.innerHTML = XmlHttp.responseText;
            ReadDuelPage();
          }
        } catch (e) {
          alert("GetLevelPage XMLHttpRequest.onreadystatechange(): " + e);
        }
      };

      XmlHttp.open(
        "GET",
        gCurrentReport.getAttribute("href") + "&IS_POPUP=1&current_view[1]",
        true
      );
      XmlHttp.send(null);
    }

    function ReadDuelPage() {
      var rows = gIndexDiv.getElementsByTagName("tr");
      var row = rows[rows.length - 1];
      row.parentNode.appendChild(row.cloneNode(true));

      row.setAttribute("class", gIndexRowclass);
      if (gIndexRowclass == "row0") gIndexRowclass = "row1";
      else gIndexRowclass = "row0";
      row.cells[0].innerHTML = replaceDate(gCurrentReport.getAttribute("c0"));
      row.cells[1].innerHTML = gCurrentReport.getAttribute("c1");
      row.cells[2].innerHTML = gCurrentReport.getAttribute("reporttime");
      var cell = row.cells[3];

      cell.innerHTML = "";
      addIndexNewButton(
        cell,
        "细节",
        "document.location.href='" + gCurrentReport.value + "/detail.html';"
      );

      if (includeData) {
        var EntireInfoDiv = document.createElement("div");
        EntireInfoDiv.id = "report_entire_info";
        Stat = CreateStat(
          node_before(gResponseDiv.getElementsByTagName("h2")[0].nextSibling),
          EntireInfoDiv,
          true
        );
        Stat.nTotalPages = 1;
        Stat.iscurrentPage = true;
      }
      multiPageDiv.push(gResponseDiv);

      infodiv.innerHTML = "保存决斗:&nbsp;" + gTitle;

      if (includeData) CountStat(gResponseDiv, true, true);

      for (var i = 0; i < multiPageDiv.length; i++) {
        var thepage = multiPageDiv[i];
        var theFileName = gCurrentReport.value + "/detail.html";
        if (i > 0) {
          theFileName = gCurrentReport.value + "/detail_" + (i + 1) + ".html";
          Stat.iscurrentPage = false;
        }
        if (includeData) {
          Stat.setNode(
            node_before(thepage.getElementsByTagName("h1")[0].nextSibling)
          );
          Stat.Show(true);
        }
        gZip.file(theFileName, handlePage(thepage, null));
      }
      multiPageDiv = [];
      gCurrentReport.checked = false;
      for (var i = 0; i < gSelectedReport.length; ++i) {
        var theCheckbox = gSelectedReport[i];
        if (theCheckbox.checked) {
          gCurrentReport = theCheckbox;
          if (includeData) {
            StatEntireDiv = document.createElement("div");
            var EntireInfoDiv = document.createElement("div");
            EntireInfoDiv.id = "report_entire_info";
            StatEntire = CreateStat(StatEntireDiv, EntireInfoDiv, true);
            StatEntire.iscurrentPage = false;
          }
          GetDuelPage();
          return;
        }
      }

      handleIndexPage();
      infodiv.innerHTML =
        "保存决斗战报:&nbsp;" +
        gTitle +
        "<br/>" +
        gCurrentReport.getAttribute("title");
      var indexStr =
        "<html>\n" +
        headDiv.outerHTML +
        "\n<body>\n<h1>" +
        gTitle +
        "</h1><br/>\n" +
        gIndexDiv.innerHTML +
        "\n</body>\n</html>";
      gZip.file("index.html", indexStr);
      gZip
        .generateAsync({
          type: "blob",
          compression: "DEFLATE",
          compressionOptions: { level: 7 },
        })
        .then(function (content) {
          //saveAs(content, "wodlog" + '_' + Math.random().toString(36).substr(2, 9) + ".zip");
          saveAs(content, "wodlog" + "_" + gTitle + ".zip");
        });
      // alert("zip文件生成完毕");
      console.log("zip文件生成完毕");
      infodiv.innerHTML = "";
      gResponseDiv.innerHTML = "";
      gIsWorking = false;
    }

    function GetLevelPage(nLevel, nRepPage) {
      var XmlHttp = new XMLHttpRequest();

      XmlHttp.onreadystatechange = function () {
        try {
          if (XmlHttp.readyState == 4 && XmlHttp.status == 200) {
            gResponseDiv.innerHTML = XmlHttp.responseText;
            ReadLevelPage(nLevel, nRepPage);
          }
        } catch (e) {
          alert("GetLevelPage XMLHttpRequest.onreadystatechange(): " + e);
        }
      };

      var URL =
        location.protocol +
        "//" +
        location.host +
        "/wod/spiel/dungeon/report.php" +
        "?cur_rep_id=" +
        gCurrentReport.value +
        "&gruppe_id=&current_level=" +
        nLevel +
        "&REPORT_PAGE=" +
        nRepPage +
        "&IS_POPUP=1";

      XmlHttp.open("GET", URL, true);
      XmlHttp.send(null);
    }

    function ReadLevelPage(nLevel, nRepPage) {
      if (nLevel == 1) {
        gCurrentReport.setAttribute("maxLevel", GetMaxLevel(gResponseDiv, 1));
        var rows = gIndexDiv.getElementsByTagName("tr");
        var row = rows[rows.length - 1];
        row.parentNode.appendChild(row.cloneNode(true));

        row.setAttribute("class", gIndexRowclass);
        if (gIndexRowclass == "row0") gIndexRowclass = "row1";
        else gIndexRowclass = "row0";
        row.cells[0].innerHTML = replaceDate(
          gCurrentReport.getAttribute("reporttime")
        );
        row.cells[1].innerHTML = gCurrentReport.getAttribute("reportname");
        var cell = row.cells[2];

        cell.innerHTML = "";
        addIndexNewButton(
          cell,
          "统计表",
          "document.location.href='" +
            gCurrentReport.value +
            "/statistics.html';"
        );
        addIndexNewButton(
          cell,
          "获得物品",
          "document.location.href='" + gCurrentReport.value + "/items.html';"
        );
        for (var i = 1; i <= gCurrentReport.getAttribute("maxLevel"); i++) {
          addIndexNewButton(
            cell,
            "层 " + i,
            "document.location.href='" +
              gCurrentReport.value +
              "/level" +
              i +
              ".html';"
          );
        }
      }
      var ret = GetRepPageInfo(gResponseDiv, [1, 1]);
      var nCurrRepPage = ret[0];
      var nMaxRepPage = ret[1];

      if (includeData && nCurrRepPage == 1) {
        var EntireInfoDiv = document.createElement("div");
        EntireInfoDiv.id = "report_entire_info";
        Stat = CreateStat(
          node_before(gResponseDiv.getElementsByTagName("h2")[0].nextSibling),
          EntireInfoDiv,
          true
        );
        Stat.nTotalPages = nMaxRepPage;
        Stat.iscurrentPage = true;
      }
      if (nMaxRepPage > 1) {
        var copyDiv = document.createElement("div");
        copyDiv.innerHTML = gResponseDiv.innerHTML;
        multiPageDiv.push(copyDiv);
        if (includeData) Stat.iscurrentPage = false;
      } else multiPageDiv.push(gResponseDiv);

      var maxLevel = gCurrentReport.getAttribute("maxLevel");
      infodiv.innerHTML =
        "保存战报:&nbsp;" +
        gTitle +
        "<br/>" +
        gCurrentReport.getAttribute("title") +
        " - 第 " +
        nLevel +
        "/" +
        maxLevel +
        " 层详细资料";

      if (includeData)
        CountStat(gResponseDiv, nCurrRepPage === nMaxRepPage, true);
      if (nCurrRepPage === nMaxRepPage) {
        for (var i = 0; i < multiPageDiv.length; i++) {
          var thepage = multiPageDiv[i];
          var theFileName = gCurrentReport.value + "/level" + nLevel + ".html";
          if (i > 0) {
            theFileName =
              gCurrentReport.value +
              "/level" +
              nLevel +
              "_" +
              (i + 1) +
              ".html";
            if (includeData) Stat.iscurrentPage = false;
          }
          if (includeData) {
            Stat.setNode(
              node_before(thepage.getElementsByTagName("h2")[0].nextSibling)
            );
            Stat.Show(true);
          }
          gZip.file(theFileName, handlePage(thepage, nLevel));
        }
        multiPageDiv = [];
      }

      if (nCurrRepPage < nMaxRepPage) GetLevelPage(nLevel, nCurrRepPage + 1);
      else if (nLevel < maxLevel) GetLevelPage(nLevel + 1, 1);
      else GetStatPage();
    }

    function GetStatPage() {
      var queryString =
        $("form[name='the_form']").formSerialize() +
        "&IS_POPUP=1&" +
        gCurrentReport.getAttribute(rValue.Text_Stat) +
        "=" +
        rValue.Text_Stat;
      var XmlHttp = new XMLHttpRequest();

      XmlHttp.onreadystatechange = function () {
        try {
          if (XmlHttp.readyState == 4 && XmlHttp.status == 200) {
            gResponseDiv.innerHTML = XmlHttp.responseText;
            infodiv.innerHTML =
              "保存战报:&nbsp;" +
              gTitle +
              "<br/>" +
              gCurrentReport.getAttribute("title") +
              " - 统计表";
            if (includeData) {
              StatEntire.setNode(
                node_before(
                  gResponseDiv.getElementsByTagName("h2")[0].nextSibling
                )
              );
              StatEntire.Show(true);
            }
            gZip.file(
              gCurrentReport.value + "/statistics.html",
              handlePage(gResponseDiv)
            );
            GetItemPage();
          }
        } catch (e) {
          alert("GetItemPage XMLHttpRequest.onreadystatechange(): " + e);
        }
      };

      var URL =
        location.protocol +
        "//" +
        location.host +
        "/wod/spiel/dungeon/report.php";

      XmlHttp.open("POST", URL, true);
      XmlHttp.setRequestHeader(
        "Content-type",
        "application/x-www-form-urlencoded"
      );
      // XmlHttp.setRequestHeader("Content-length", queryString.length);
      // XmlHttp.setRequestHeader("Connection", "close");
      XmlHttp.send(queryString);
    }

    function getAvgLvTip() {
      if (!heroes.length) {
        return "";
      }
      let finalMap = {};
      for (let heroName of heroes) {
        heroLvMap[heroName] &&
          (finalMap[heroName] = parseInt(heroLvMap[heroName]));
      }
      let avgLv =
        Math.round(
          (100 * Object.values(finalMap).reduce((a, b) => a + b)) /
            Object.keys(finalMap).length
        ) / 100;
      return "「" + avgLv.toFixed(2) + "级」";
    }

    function GetItemPage() {
      var queryString =
        $("form[name='the_form']").formSerialize() +
        "&IS_POPUP=1&" +
        gCurrentReport.getAttribute(rValue.Text_Item) +
        "=" +
        rValue.Text_Item;
      var XmlHttp = new XMLHttpRequest();

      XmlHttp.onreadystatechange = function () {
        try {
          if (XmlHttp.readyState == 4 && XmlHttp.status == 200) {
            gResponseDiv.innerHTML = XmlHttp.responseText;
            infodiv.innerHTML =
              "保存战报:&nbsp;" +
              gTitle +
              "<br/>" +
              gCurrentReport.getAttribute("title") +
              " - 获得物品";
            gZip.file(
              gCurrentReport.value + "/items.html",
              handlePage(gResponseDiv)
            );
            gCurrentReport.checked = false;
            for (var i = 0; i < gSelectedReport.length; ++i) {
              var theCheckbox = gSelectedReport[i];
              if (theCheckbox.checked) {
                gCurrentReport = theCheckbox;
                if (includeData) {
                  StatEntireDiv = document.createElement("div");
                  var EntireInfoDiv = document.createElement("div");
                  EntireInfoDiv.id = "report_entire_info";
                  StatEntire = CreateStat(StatEntireDiv, EntireInfoDiv, true);
                  StatEntire.iscurrentPage = false;
                }
                GetLevelPage(1, 1);
                return;
              }
            }
            handleIndexPage();

            // 导出前把平均等级替换上去
            if (gWithLv) {
              gTitle = gTitle.replace(rValue.Holder_Lv, getAvgLvTip());
            }
            infodiv.innerHTML =
              "保存战报:&nbsp;" + gTitle + "<br/>" + "生成Zip文件";
            var indexStr =
              "<html>\n" +
              headDiv.outerHTML +
              "\n<body>\n<h1>" +
              gTitle +
              "</h1><br/>\n" +
              gIndexDiv.innerHTML +
              "\n</body>\n</html>";
            gZip.file("index.html", indexStr);

            gZip
              .generateAsync({
                type: "blob",
                compression: "DEFLATE",
                compressionOptions: { level: 7 },
              })
              .then(function (content) {
                //saveAs(content, "wodlog" + '_' + Math.random().toString(36).substr(2, 9) + ".zip");
                saveAs(content, "wodlog" + "_" + gTitle + ".zip");
                gIsWorking = false;
              });
            // alert("zip文件生成完毕");
            console.log("zip文件生成完毕");
            infodiv.innerHTML = "";
            gResponseDiv.innerHTML = "";
          }
        } catch (e) {
          alert("GetItemPage XMLHttpRequest.onreadystatechange(): " + e);
        }
      };

      var URL =
        location.protocol +
        "//" +
        location.host +
        "/wod/spiel/dungeon/report.php";

      XmlHttp.open("POST", URL, true);
      XmlHttp.setRequestHeader(
        "Content-type",
        "application/x-www-form-urlencoded"
      );
      // XmlHttp.setRequestHeader("Content-length", queryString.length);
      // XmlHttp.setRequestHeader("Connection", "close");
      XmlHttp.send(queryString);
    }

    function GetMaxLevel(page, DefaultValue) {
      var ret = DefaultValue;

      var allInputs = page.getElementsByTagName("input");
      for (var i = 0; i < allInputs.length; ++i) {
        var name = allInputs[i].getAttribute("name");

        if (rValue.Pattern_level.test(name)) {
          var levelnumber = Number(rValue.Pattern_idNumber.exec(name)[1]);
          if (levelnumber > ret) ret = levelnumber;
        }
      }
      return ret;
    }

    function handlePage(page, nLevel) {
      if (gWithLv) {
        let $page = $(page);

        if ($page.find('h1:contains("战斗统计: ")').length) {
          let $heroThs = $page.find(
            "h2 ~ table.content_table table tr:first th:gt(0)"
          );
          heroes = [];
          $heroThs.each(function (i, el) {
            heroes.push($(el).text().trim());
          });
          console.log(heroes);
        }

        let $heroLvTrs = $page.find("table.rep_status_table tr:not(:first)");
        if ($heroLvTrs.length) {
          heroLvMap = {};
          $heroLvTrs.each(function (i, el) {
            let heroName = $(el).find("td:eq(1)").text().trim();
            let heroLv = parseInt($(el).find("td:eq(2)").text().trim());
            if (heroName && heroLv) {
              heroLvMap[heroName] = parseInt(heroLv);
            }
          });
          console.log(heroLvMap);
        }
      }

      var thepage = page.getElementsByTagName("form")[0];
      var h2 = thepage.getElementsByTagName("h2")[0];
      if (h2) {
        h2.innerHTML = replaceDate(h2.innerHTML);
      }

      //removePageInput(thepage);
      replaceURL(thepage, "link", "href");
      replaceURL(thepage, "script", "src");
      replaceURL(thepage, "img", "src");
      replaceURL(thepage, "a", "href", "#");
      replaceButton(thepage);
      if (nLevel) replaceLevelPage(thepage, nLevel);
      return (
        "<html>\n" +
        headDiv.outerHTML +
        "\n<body>\n" +
        replaceOther(thepage.outerHTML) +
        "\n</body>\n</html>"
      );
    }

    function handleHead(head) {
      if (gTitle == null) gTitle = default_gTitle;
      head.getElementsByTagName("title")[0].innerHTML = gTitle.replace(
        rValue.Holder_Lv,
        ""
      );

      replaceURL(head, "link", "href");

      var bodyScript = document
        .getElementsByTagName("body")[0]
        .cloneNode(true)
        .getElementsByTagName("script");
      for (var i = 0; i < bodyScript.length; i++)
        head.appendChild(bodyScript[i]);
      var scripts = head.getElementsByTagName("script");
      for (var i = scripts.length - 1; i >= 0; i--) {
        script = scripts[i];
        handleScript(script);
      }
      replaceURL(head, "script", "src");

      var metas = head.getElementsByTagName("meta");
      for (var i = metas.length - 1; i >= 0; i--) {
        var meta = metas[i];
        if (!meta.httpEquiv) meta.parentNode.removeChild(meta);
      }
      if (includeData) es_addStyle(head, Style);
      head.innerHTML = head.innerHTML + "\n";
      return head;
    }

    function handleScript(script) {
      var patten = /wod_standard.js|wodtooltip.js/;
      var scriptPatten = /(wodToolTipInit\(.*\);)\s*(wodInitialize\([^;]*\);)/;
      if (script.src) {
        if (!patten.test(script.src)) script.parentNode.removeChild(script);
      } else if (script.firstChild) {
        var scriptStr = script.firstChild.data;
        var result = scriptPatten.exec(scriptStr);
        if (result != null)
          if (result[1] && result[2])
            scriptStr =
              "window.onload = function(e){" + result[1] + result[2] + "}";

        scriptStr = scriptStr
          .replace(
            /wodInitialize\(''/g,
            "wodInitialize('" + location.host + "'"
          )
          .replace("'0'", "'1'");
        scriptStr +=
          '\nfunction o(t,n){ \n var url="' +
          location.origin +
          '/wod/spiel/";\n';
        scriptStr += 'if(t=="n"){url += "help/npc"}\n';
        scriptStr += 'if(t=="s"){url += "hero/skill"}\n';
        scriptStr += 'if(t=="i"){url += "hero/item"}\n';
        scriptStr += 'return wo(url + ".php?name=" + n + "&IS_POPUP=1");}\n';

        if (includeData) {
          scriptStr += "var CTable=function(){};\n";
          scriptStr +=
            "CTable.GetNumber = " + CTable.GetNumber.toString() + "\n";
          scriptStr += "CompareString = " + CompareString.toString() + "\n";
          scriptStr += "ct = " + CTable.OnClickTitle.toString() + "\n";
          scriptStr += "cf = " + CTable.OnChangeFilter.toString() + "\n";
          scriptStr += "co = " + CTable.OnChangeOrder.toString() + "\n";
          scriptStr += "sd = " + CTable.OnShowDetail.toString() + "\n";
          scriptStr += "st = " + CStat.OnTabClick.toString() + "\n";
        }
        script.firstChild.data = scriptStr;
      }
    }
    function removePageInput(page) {
      var inputs = page.getElementsByTagName("input");
      for (var i = inputs.length - 1; i >= 0; i--) {
        var theInput = inputs[i];
        if (theInput.type == "hidden") {
          theInput.parentNode.removeChild(theInput);
          break;
        }
      }
      return page;
    }

    function replaceURL(page, tag, attr, value) {
      var test_pattern = /^\//;
      var test1_pattern = /^#/;
      var onclick_pattern = /([^\/]*)\.php\?name=([^&]*)/;
      var allLink = page.getElementsByTagName(tag);
      var path = location.origin + location.pathname;
      var m = path.match(/(.*)[\/\\]([^\/\\]+)\.\w+$/);

      for (var i = 0; i < allLink.length; i++) {
        var link = allLink[i];
        if (link.hasAttribute(attr)) {
          var uri = link.getAttribute(attr);
          if (value) {
            if (!test1_pattern.test(uri)) link.setAttribute(attr, value);
            if (link.hasAttribute("onclick")) {
              var result = onclick_pattern.exec(link.getAttribute("onclick"));
              if (result && result[1] && result[2])
                link.setAttribute(
                  "onclick",
                  "return o('" +
                    result[1].substr(0, 1) +
                    "','" +
                    result[2] +
                    "');"
                );
            }
          } else {
            if (!rValue.pattern_http.test(uri)) {
              if (test_pattern.test(uri)) {
                link.setAttribute(attr, location.origin + uri);
              } else if (!test1_pattern.test(uri)) {
                link.setAttribute(attr, m[1] + "/" + uri);
              }
            }
          }
        }
      }
    }

    function replaceButton(page) {
      var allInputs = page.getElementsByTagName("input");
      for (var i = 0; i < allInputs.length; ++i) {
        var name = allInputs[i].getAttribute("name");

        if (rValue.Pattern_level.test(name)) {
          var levelURL =
            "document.location.href='level" +
            rValue.Pattern_idNumber.exec(name)[1] +
            ".html';";
          var button = allInputs[i];
          button.setAttribute("type", "button");
          button.setAttribute("onclick", levelURL);
        }
        if (rValue.Pattern_item.test(name)) {
          var levelURL = "document.location.href='items.html';";
          var button = allInputs[i];
          button.setAttribute("type", "button");
          button.setAttribute("onclick", levelURL);
        }
        if (rValue.Pattern_stat.test(name)) {
          var levelURL = "document.location.href='statistics.html';";
          var button = allInputs[i];
          button.setAttribute("type", "button");
          button.setAttribute("onclick", levelURL);
        }
        if (rValue.Pattern_detail.test(name)) {
          var levelURL = "document.location.href='level1.html';";
          var button = allInputs[i];
          button.setAttribute("type", "button");
          button.setAttribute("onclick", levelURL);
        }
        if (name == "overview" || name == "") {
          var levelURL = "document.location.href='../index.html';";
          var button = allInputs[i];
          button.setAttribute("type", "button");
          button.setAttribute("onclick", levelURL);
        }
      }
    }
    function replaceLevelPage(page, nLevel) {
      var IndexPatt = /\D*([\d]+)\D*/;
      allURL = page.getElementsByTagName("a");
      for (var i = 0; i < allURL.length; ++i) {
        var theURL = allURL[i];
        if (theURL.className == "paginator") {
          var Result = IndexPatt.exec(theURL.textContent);
          var pageNum = Number(Result[1]);
          theURL.href =
            "level" + nLevel + (pageNum > 1 ? "_" + pageNum : "") + ".html";
        }
      }
    }

    function replaceDate(sDate) {
      var today = new Date();
      var yesterday = new Date();
      yesterday.setDate(yesterday.getDate() - 1);
      var ret = sDate.replace(
        "今天",
        today.getFullYear() +
          "年" +
          (today.getMonth() + 1) +
          "月" +
          today.getDate() +
          "日"
      );
      ret = ret.replace(
        "昨天",
        yesterday.getFullYear() +
          "年" +
          (yesterday.getMonth() + 1) +
          "月" +
          yesterday.getDate() +
          "日"
      );
      return ret;
    }

    function replaceOther(sHTML) {
      var ret = sHTML.replace(
        /wodInitialize\(''/g,
        "wodInitialize('" + location.host + "'"
      );
      ret = ret.replace(/wo\('\/wod/g, "wo('" + location.origin + "/wod");
      return ret;
    }

    function prepareIndexPageTemplate() {
      var allRow = gIndexTemplateDiv.getElementsByTagName("tr");
      for (var j = allRow.length - 1; j > 1; --j) {
        var row = allRow[j];
        if (rValue.Pattern_logRow.test(row.getAttribute("class"))) {
          row.parentNode.removeChild(row);
        }
      }
    }

    function addIndexNewButton(cell, buttonText, url) {
      var newButton = document.createElement("input");
      newButton.setAttribute("type", "button");
      newButton.setAttribute("class", "button clickable");
      newButton.setAttribute("value", buttonText);
      newButton.setAttribute("onclick", url);
      cell.appendChild(newButton);
    }

    function handleIndexPage() {
      var allRow = gIndexDiv.getElementsByTagName("tr");
      var row = allRow[allRow.length - 1];
      row.parentNode.removeChild(row);
    }

    function insertFilter() {
      if (this.checked) addFilter(this.nextSibling);
      else hideFilter();
    }

    var gIndexRowclass = "row0";
    var gCurrentReport;
    var gZip;
    var rLocal;
    var infodiv;
    var gTitle;
    var gSelectedReport = [];
    var gIndexTemplateDiv;
    var gResponseDiv;
    var multiPageDiv = [];
    var gIndexDiv;
    var gIsWorking = false;
    var includeData = false;
    var gWithLv = false;
    var headDiv;
    var world = "";
    var default_gTitle;
    var isaddFilter = true;
    var rContents = {
      OrigText_H1_DungeonLog: ["Battle Report", "战报"],
      OrigText_H1_DuelLog: ["Battle Report", "您的决斗"],
      OrigText_Button_DungeonDetails: ["details", "详细资料"],
      Text_Button_Exportlog: ["Export Log", "导出战报"],
      Text_Button_SelectAll: ["Select All", "全选"],
      Text_Button_ClearAll: ["Clear All", "清除"],
    };

    var rValue = {
      Text_Item: "items",
      Text_Stat: "stats",
      Text_Checkbox: "chkLog",
      Chk_includeData: "export_with_data",
      Chk_single: "single_export",
      Chk_withLv: "withLv_export",
      Holder_Lv: "「等级」",
      Pattern_level: /^level\[[\d]+\]/,
      Pattern_stat: /^stats\[[\d]+\]/,
      Pattern_item: /^items\[[\d]+\]/,
      Pattern_detail: /^details\[[\d]+\]/,
      Pattern_checkboxName: /^chkLog/,
      Pattern_logRow: /^row\d/,
      Pattern_idNumber: /([\d]+)/,
      pattern_http: /^http/i,
    };

    var isDuel = false;
    //-----------------------------------------------------------------------------
    // "main"
    //-----------------------------------------------------------------------------
    function ReprotMain() {
      rLocal = GetLocalContents(rContents);
      if (rLocal === null) return;
      var allH1 = document.getElementsByTagName("h1");
      var i = 0;
      var h1;
      var shouldContinue = false;
      if (allH1 === "undefined") return;
      for (i = 0; i < allH1.length; ++i) {
        h1 = allH1[i];
        if (
          h1.innerHTML == rLocal.OrigText_H1_DungeonLog ||
          h1.innerHTML == rLocal.OrigText_H1_DuelLog
        ) {
          infodiv = document.createElement("div");
          infodiv.innerHTML = "";
          infodiv.id = "infoDiv";
          h1.parentNode.insertBefore(infodiv, h1.nextSibling);

          var newLabel = document.createElement("label");
          newLabel.innerHTML = "同时保存统计信息";
          h1.parentNode.insertBefore(newLabel, h1.nextSibling);
          var newCheckBox = document.createElement("input");
          newCheckBox.setAttribute("type", "checkbox");
          newCheckBox.setAttribute("class", "checkbox");
          newCheckBox.setAttribute("value", "同时保存统计信息");
          newCheckBox.addEventListener("change", insertFilter, false);
          newCheckBox.id = rValue.Chk_includeData;
          newLabel.setAttribute("for", newCheckBox.id);
          h1.parentNode.insertBefore(newCheckBox, h1.nextSibling);

          // 插入单文件导出复选框
          var singleCheckLabel = document.createElement("label");
          singleCheckLabel.innerHTML = "单文件导出";
          h1.parentNode.insertBefore(singleCheckLabel, h1.nextSibling);
          var singleCheckBox = document.createElement("input");
          singleCheckBox.setAttribute("type", "checkbox");
          singleCheckBox.setAttribute("class", "checkbox");
          singleCheckBox.setAttribute("value", "单文件导出");
          singleCheckBox.id = rValue.Chk_single;
          singleCheckLabel.setAttribute("for", singleCheckBox.id);
          h1.parentNode.insertBefore(singleCheckBox, h1.nextSibling);

          // 插入文件名包含平均等级复选框
          var withLvLabel = document.createElement("label");
          withLvLabel.innerHTML = "平均等级";
          h1.parentNode.insertBefore(withLvLabel, h1.nextSibling);
          var withLvCheckBox = document.createElement("input");
          withLvCheckBox.setAttribute("type", "checkbox");
          withLvCheckBox.setAttribute("class", "checkbox");
          withLvCheckBox.setAttribute("value", "平均等级");
          withLvCheckBox.setAttribute("checked", true);
          withLvCheckBox.id = rValue.Chk_withLv;
          withLvLabel.setAttribute("for", withLvCheckBox.id);
          h1.parentNode.insertBefore(withLvCheckBox, h1.nextSibling);

          // 插入日期后缀框
          var suffixBox = document.createElement("input");
          suffixBox.setAttribute("id", "suffix");
          suffixBox.setAttribute("size", "14");
          suffixBox.setAttribute("type", "text");
          suffixBox.setAttribute("placeholder", "日期后缀");
          // 默认使用年月日时分为后缀
          suffixBox.setAttribute("value", "YYYYMMDDHHmm");
          suffixBox.innerHTML = "YYYYMMDDHHmm";
          h1.parentNode.insertBefore(suffixBox, h1.nextSibling);
          var suffixSpan = document.createElement("span");
          suffixSpan.innerHTML = "日期后缀:";
          h1.parentNode.insertBefore(suffixSpan, h1.nextSibling);

          // 插入前缀框
          var prefixBox = document.createElement("input");
          prefixBox.setAttribute("id", "prefix");
          prefixBox.setAttribute("size", "14");
          prefixBox.setAttribute("type", "text");
          prefixBox.setAttribute("placeholder", "战报导出命名前缀");
          // 默认使用团队名作为前缀
          prefixBox.setAttribute(
            "value",
            "[" + document.getElementsByName("gruppe_name")[0].value + "]"
          );
          prefixBox.innerHTML =
            "[" + document.getElementsByName("gruppe_name")[0].value + "]";
          h1.parentNode.insertBefore(prefixBox, h1.nextSibling);
          var newLabel = document.createElement("span");
          newLabel.innerHTML = "战报导出命名前缀:";
          h1.parentNode.insertBefore(newLabel, h1.nextSibling);

          InsertButton(h1, rLocal.Text_Button_Exportlog, exportLog);
          InsertButton(h1, rLocal.Text_Button_ClearAll, cleartAll);
          InsertButton(h1, rLocal.Text_Button_SelectAll, selectAll);

          gResponseDiv = document.createElement("div");
          gResponseDiv.innerHTML = "";
          gIndexTemplateDiv = document.createElement("div");
          gIndexTemplateDiv.innerHTML = "";

          shouldContinue = true;
          break;
        }
      }
      if (!shouldContinue) return;
      var allTable = document.getElementsByTagName("table");
      for (i = 0; i < allTable.length; ++i) {
        var theTable = allTable[i];
        if (theTable.getAttribute("class") == "content_table") {
          gIndexTemplateDiv.innerHTML = theTable.outerHTML;
          prepareIndexPageTemplate();
          var allRow = theTable.getElementsByTagName("tr");
          for (var j = 0; j < allRow.length; ++j) {
            var row = allRow[j];
            var newCheckbox = document.createElement("input");
            newCheckbox.setAttribute("type", "checkbox");
            if (rValue.Pattern_logRow.test(row.getAttribute("class"))) {
              if (isDuel) {
                var reportName =
                  "<span>" +
                  row.cells[0].innerHTML.replace("<hr>", " <b>对战</b> ") +
                  " " +
                  row.cells[1].innerText +
                  "</span>";
                var reportTime = "<span>" + row.cells[2].innerText + "</span>";
                var title = reportName + "&nbsp;-&nbsp;" + reportTime;
                var allduel = row.cells[3].getElementsByTagName("a");
                var href = allduel[0].href;
                var onclick_pattern = /\?DuellId=([^&]*)/;
                var result = onclick_pattern.exec(href);
                newCheckbox.setAttribute(
                  "name",
                  rValue.Text_Checkbox + "[" + j + "]"
                );
                newCheckbox.setAttribute(
                  "id",
                  rValue.Text_Checkbox + "[" + j + "]"
                );
                newCheckbox.setAttribute("value", result[1]);
                newCheckbox.setAttribute("href", href);
                newCheckbox.setAttribute("title", title);
                newCheckbox.setAttribute("reportname", reportName);
                newCheckbox.setAttribute("reporttime", reportTime);
                newCheckbox.setAttribute(
                  "c0",
                  row.cells[0].innerHTML.replace("<hr>", " <b>对战</b> ")
                );
                newCheckbox.setAttribute("c1", row.cells[1].innerHTML);
                newCheckbox.setAttribute("maxLevel", 1);
                row.cells[0].insertBefore(newCheckbox, row.cells[0].firstChild);
              } else {
                var reportName =
                  "<span>" + row.cells[1].firstChild.innerHTML + "</span>";
                var reportTime =
                  "<span>" + row.cells[0].firstChild.innerHTML + "</span>";
                var title = reportName + "&nbsp;-&nbsp;" + reportTime;
                var allInput = row.cells[2].getElementsByTagName("input");
                var id = "";
                var index = "";
                for (var k = 0; k < allInput.length; ++k) {
                  var input = allInput[k];
                  var name = input.getAttribute("name");
                  var value = input.getAttribute("value");
                  if (name.indexOf("report_id") != -1) {
                    var Result = rValue.Pattern_idNumber.exec(name);
                    index = Number(Result[1]);
                    id = value;
                    break;
                  }
                }
                newCheckbox.setAttribute(
                  "name",
                  rValue.Text_Checkbox + "[" + index + "]"
                );
                newCheckbox.setAttribute(
                  "id",
                  rValue.Text_Checkbox + "[" + index + "]"
                );
                newCheckbox.setAttribute("value", id);
                newCheckbox.setAttribute("title", title);
                newCheckbox.setAttribute("reportname", reportName);
                newCheckbox.setAttribute("reporttime", reportTime);
                newCheckbox.setAttribute("maxLevel", 1);
                newCheckbox.setAttribute(
                  rValue.Text_Item,
                  rValue.Text_Item + "%5B" + index + "%5D"
                );
                newCheckbox.setAttribute(
                  rValue.Text_Stat,
                  rValue.Text_Stat + "%5B" + index + "%5D"
                );
                row.cells[0].insertBefore(newCheckbox, row.cells[0].firstChild);
              }
            }
          }
          break;
        }
      }
      var allInput = document.getElementsByTagName("input");
      for (var i = 0; i < allInput.length; i++) {
        var input = allInput[i];
        if (input.name && input.name == "wod_post_world") {
          world = input.value;
          break;
        }
      }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////

    function test(o) {
        alert('test');
    }
    function filterMain() {
        function Factoryfilter(o) {
            return function () {
                filterReport(o);
            };
        }

        var tables = document.getElementsByTagName("table");
        for (var i = 0; i < tables.length; i++) {
            var table = tables[i];
            if (table.className == "rep_status_table") {
                for (var j = 0; j < table.rows.length; j++) {
                    var row = table.rows[j];
                    for (var k = 0; k < row.cells.length; k++) {
                        var cell = row.cells[k];
                        if (cell.className == "hero") {
                            var newCheckbox = document.createElement("input");
                            newCheckbox.setAttribute("type", "checkbox");
                            newCheckbox.setAttribute("class", "filter");
                            newCheckbox.value = cell.firstChild.textContent;
                            cell.insertBefore(newCheckbox, cell.firstChild);
                            //newCheckbox.setAttribute("onclick", "test(this);");
                            newCheckbox.addEventListener("click", Factoryfilter(newCheckbox), false);
                            break;
                        }
                    }
                }
            }
        }
    }

    filterReport = function (o) {
        var checkboxlist = document.getElementsByTagName("input");
        var selectedheros = [];
        var classNamepattern = /^[rep_monster|rep_hero|rep_myhero]/;
        for (var i = 0; i < checkboxlist.length; i++) {
            var checkbox = checkboxlist[i];
            if (checkbox.getAttribute("type") == "checkbox" && checkbox.getAttribute("class") == "filter") {
                if (checkbox.value == o.value)
                    checkbox.checked = o.checked;
                if (checkbox.checked && selectedheros.indexOf(checkbox.value) <= -1)
                    selectedheros.push(checkbox.value);
            }
        }
        var tables = document.getElementsByTagName("table");
        for (var i = 0; i < tables.length; i++) {
            var table = tables[i];
            if (table.className != "rep_status_table") {
                var istheTable = false;
                for (var j = 0; j < table.rows.length; j++) {
                    var row = table.rows[j];
                    var show = false;
                    if (row.cells.length <= 0 || row.cells[0].colSpan > 1)
                        continue;
                    if (istheTable || row.cells[0].className == "rep_initiative") {
                        istheTable = true;
                        for (var k = 0; k < row.cells.length; k++) {
                            var cell = row.cells[k];
                            var links = cell.getElementsByTagName("a");
                            for (var index = 0; index < links.length; index++) {
                                var link = links[index];
                                if (classNamepattern.test(link.className)) {
                                    var name = link.textContent;
                                    if (selectedheros.length <= 0 || selectedheros.indexOf(name) > -1) {
                                        show = true;
                                        break;
                                    }
                                }
                            }
                            if (show)
                                break;
                        }
                        row.style.display = show ? '' : 'none';
                    }
                }
            }
        }
    };

    try {
        isDuel = (window.location.href.indexOf("duell.php") > 0);
        Main();
        ReprotMain();
        //filterMain();
    } catch (e) {
        alert("Main(): " + e);
    }
})();